This was just going to be a quick answer... but I seem to have written a big wall of text: sorry about that.
GetArray
API
I'm wondering whether there is a good reason that this is an instance method; rather, I'm assuming this is a very specific implementation of the GetArray
method on some interface. I say this, because without that context this looks like a terrible API! You have a bizarrely named instanced method (what does GetArray
mean?) which isn't using any instance state, and which has a very unusual return type: all of this makes sense, however, if this is an implementation of an interface which is specifically designed for producing JSON, but I'd still reckon you can come up with a better name!
Even if this is a very specific interface implementation, I'd be very much inclined to break most of this out into a (static) helper function, because currently you have all of the complexity of performing the task stuffed into a method which really only cares about the result, not how you got there, and which has some weird return value which is nothing to do with the grouping logic. I would be strongly inclined to break out 2 methods, one to perform the grouping and another to produce the strings from the groups, just because one feels like "data processing" and the other feels like "data formatting" and those are different ideas working with different types (just as producing the string and stuffing it into an array for the benefit of JSON are different ideas). You can go with whatever granularity you like, but I'd certainly want to pull the code for finalResult
out:
/// <summary>
/// Produces a string representation of a sequence of integers, combining contiguous values into a single range
/// For example: {1, 2, 3, 5} -> "1-3,5"
/// </summary>
public static string ProduceContiguousIntegersString(IEnumerable<int> integers)
{
if (integers == null)
throw new ArgumentException("The list of integers may not be null", nameof(integers));
// snip
}
Note the parameter type: you are only performing forward passes over the array, so there is no reason to limit the consumer to passing you an array: IEnumerable<T>
is the simplest type you can get away with here as input. As always, some inline documentation (\\\
) is always appreciated on any public API. I threw a null
check in there as well just as an example: public APIs should always check input and throw meaningful exceptions; if this is just being used internally then it is less important, but still useful.
This is how you might call this new method:
public string[] GetArray()
{
int[] arr = { -2, 0, 1, 2, 3, 4, 5, 8, 9, 11, 13, 15, 18, 22, 25, 28, 29, 30 };
var finalResult = ProduceContiguousIntegersString(arr);
return new[] { finalResult };
}
As RadarBob said, with arr
baked in, this method just looks wrong. I've taken the liberty of changing "finalresult" to "finalResult", because it's two words, but I'm sure you could come up with a more meaningul name if you gave it some more thought.
lastValue
lastValue
is weird. You seem to have made it nullable so that you can cope with empty arrays (which screams "arr isn't meant to be hard-coded"), but this is all a bit cryptic, and it isn't clear what the output will be if you do pass in an empty array. I would be inclined to provide an explicit "is the array empty" check.
A 'better' use of the nullability would to make it explicit that it isn't a meaningful value yet: indeed, you can replace arr.FirstOrDefault();
with null
and your code will still work, and will be less confusing even! In fact... you can remove the check altogether, because that code will never run if the array is empty! Any such checks (e.g. that it isn't null
) are overly defensive, and will only work to obscure bugs in future.
The 'real problem' with this code, is that Segment
has a slightly odd API. I this Segment
is a great idea, but it's kind-of-horrendous expecting the segmentation function to keep track of its own state, and it's not clear whether it calls this lambda for the first element or not. Assuming we can't mess with Segment
, my instinct is to go with something like this:
public static string ProduceContiguousIntegersString(IEnumerable<int> integers)
{
// handle special case of empty enumerable
if (!integers.Any())
{
return "";
}
int previousValue = integers.First();
var groupDataIntoAdjacentBlocks = integers.Segment(i =>
{
bool newSegment = previousValue != (i - 1);
previousValue = i;
return newSegment;
}
);
// ...
To be really picky, I'd prefer it was called "previousValue", because "lastValue" might imply the last value in the array.
z.ToList()
What is z
? I can forgive i
in the first loop, but what is z
? And why are you turning it into a List<T>
? At worst you should be turning it into an array, since you don't need a dynamic data type, and rather than assigning to var, you could assign to IReadOnlyList<int>
, which ensures you can't accidently change anything. Anyway, you are only performing simple operations on z
(which I've renamed to block
below), so there isn't much harm in leaving it as IEnumerable<T>
or whatever it already is. (If you are concerned about the cost of Count()
, then go with the T[]
as IReadOnlyList
approach).
// ...
var convertSetsIntoRanges = groupDataIntoAdjacentBlocks.Select(block =>
{
if (block.Count() == 1)
return segment.First().ToString();
return block.First().ToString() + "-" + block.Last().ToString();
});
var finalResult = (string.Join(",", convertSetsIntoRanges));
return finalResult;
}
I'd also reconsider the variable name groupDataIntoAdjacentBlocks
if I were you: to me it reads as though it produces some blocks which are adjacent, but really you are producing some blocks which contain adjacent elements.
Sorting??
As RadarBob suggests, it looks as though this code is expecting a sorted array. Perhaps you ought to sort the array before processing it? If you are worried about performance, you can do a first pass to check if the array is already sorted, and only sort it if necessary; otherwise, just document the parameters clearly.
arr
actually baked into the method, or is it a member of_arrayrepo
? I also wonder if you could come up with a better name: "Sort array" sounds suspiciously like sorting, which is not what is going on here (though I can imagine that sorting an array before segmenting it is a likely use case). "Grouping contiguous integers" or something alone those lines perhaps? \$\endgroup\$Segment
do? \$\endgroup\${ 1, 3, 2, 4 }
would that become"1-4"
? \$\endgroup\$