Find consecutive numbers and turn them into ranges I have array

int[] arr = { -2, 0, 1, 2, 3, 4, 5, 8, 9, 11, 13, 15, 18, 22, 25, 28, 29, 30 };


I need to write function that shows it as string and if numbers are near, for example 1,2,3,4,5 - I need to show them 1-5.

I wrote this function in repository

public string[] GetArray()
{
    int[] arr = { -2, 0, 1, 2, 3, 4, 5, 8, 9, 11, 13, 15, 18, 22, 25, 28, 29, 30 };

    int? lastValue = arr.FirstOrDefault();

    var groupDataIntoAdjacentBlocks = arr.Segment(i =&gt;
    {
        var result = lastValue != null &amp;&amp; (lastValue != (i - 1));
        lastValue = i;
        return result;
    }
    );

    var convertSetsIntoRanges = groupDataIntoAdjacentBlocks.Select(z =&gt;
    {
        var data = z.ToList();
        if (data.Count() == 1)
            return data.First().ToString();

        return data.First().ToString() + "-" + data.Last().ToString();
    });

    var finalresult = (string.Join(",", convertSetsIntoRanges));
    return new[] { finalresult };

}


And call it like this in controller

public JsonResult GetArray()
{
    var arrayresult = _arrayrepo.GetArray();
    return Json(arrayresult, JsonRequestBehavior.AllowGet);
}


Any suggestions how I can improve my code?
Letting a single method do everything is not such a good idea. You should rather separate the logic for finding consecutive numbers from creating a string from ranges and let them produce the final result by chaining them as extensions.



The first extension only knows how to find ranges. If you don't have range class/struct yet then you can use tuples.

public static IEnumerable&lt;(int Min, int Max)&gt; ToRanges(this IEnumerable&lt;int&gt; source)
{
    using (var e = source.GetEnumerator())
    {
        if (!e.MoveNext())
        {
            yield break;
        }

        var previous = e.Current;

        var range = (Min: previous, Max: e.Current);

        while (e.MoveNext())
        {
            var isConsecutive = e.Current - previous == 1;
            if (isConsecutive)
            {
                range = (range.Min, e.Current);
            }
            else
            {
                yield return range;
                range = (e.Current, e.Current);
            }
            previous = e.Current;
        }

        yield return range;
    }
}


The other extension consumes the result of ToRanges and returns a string.

public static string Stringify(this IEnumerable&lt;(int Min, int Max)&gt; ranges)
{
    var values = ranges.Select(range =&gt; $"{(IsEmpty(range) ? $"{range.Min}" : $"{range.Min}-{range.Max}")}");
    return string.Join(", ", values);

    bool IsEmpty((int Min, int Max) range)
    {
        return range.Max - range.Min == 0;
    }
}




The advantages of having two separated methods are:
- you can focus on one task at a time
- you are now able to test only one feature at a time
- you can reuse ToRange to calculate their lengths if necessary



Other things that you should address are:
- variable names should not sound like they were methods, e.g. groupDataIntoAdjacentBlocks; a much better name would be adjacentBlocks or something along the same lines
- int? lastValue = arr.FirstOrDefault(); this is wrong since the default value for int is 0 and not null.
- the result of the GetArray() method does not make much sense; why would you return an array of strings if there is always only one item? I guess this is not your real code but the range finding part and you edited it for the sake of the question so I won't comment on that one.