Get consecutive integer number ranges from list of int This is an algorithm to return a list of consecutive integer ranges from a list of integers.

The practical use of such an algorithm is to filter lists more efficiently, i.e. rather than check e.g. 1000 items (1..1000; x==1 || x==2 || ... || x==1000), it might be possible to check only 2 items (x >= 1 &amp;&amp; x &lt;= 1000).

Does this algorithm have any mistakes, can it be optimized, or are there any other improvements you can suggest?

(As a side note: I know this code does not follow the standard C# naming conventions.  I do not like to follow that particular convention; I prefer "snake_case" as it is easier on my eyes.)

Sample output:

(x &gt;= 0 &amp;&amp; x &lt;= 1690)
(x &gt;= 13642 &amp;&amp; x &lt;= 15331)
(x &gt;= 27283 &amp;&amp; x &lt;= 27296)
(x &gt;= 27769 &amp;&amp; x &lt;= 27776)
(x &gt;= 28249 &amp;&amp; x &lt;= 28256)
(x &gt;= 28729 &amp;&amp; x &lt;= 28736)
(x &gt;= 29209 &amp;&amp; x &lt;= 29222)


The algorithm (built Visual Studio 2017, C# 7.3, .NET 4.7.2):

public static List&lt;(int from, int to)&gt; get_consecutive_ranges(List&lt;int&gt; fids)
{
    if (fids == null || fids.Count == 0) return null;

    fids = fids.OrderBy(a =&gt; a).Distinct().ToList();

    var fids_fast = new List&lt;(int from, int to)&gt;();

    var is_conseq_with_last = true;
    var start_index = 0;
    var end_index = 0;
    for (var fids_index = 0; fids_index &lt; fids.Count; fids_index++)
    {
        var first = fids_index == 0;
        var last = fids_index == fids.Count - 1;
        if (!first &amp;&amp; fids[fids_index - 1] == fids[fids_index] - 1)
        {
            is_conseq_with_last = true;
        }
        else if (!first)
        {
            is_conseq_with_last = false;
        }

        if (!is_conseq_with_last &amp;&amp; !first &amp;&amp; !last)
        {
            end_index = !first &amp;&amp; !last ? fids_index - 1 : fids_index;
            fids_fast.Add((fids[start_index], fids[end_index]));
            start_index = fids_index;
        }
        else if (last)
        {
            if (!is_conseq_with_last)
            {
                if (!first)
                {
                    end_index = fids_index - 1;
                }

                fids_fast.Add((fids[start_index], fids[end_index]));
                start_index = fids_index;
            }

            end_index = fids_index;
            fids_fast.Add((fids[start_index], fids[end_index]));
        }
    }

    fids_fast.ForEach(a =&gt; Console.WriteLine($"(x &gt;= {a.@from} &amp;&amp; x &lt;= {a.to})"));

    return fids_fast;
}


Example use:

// slow:
body = body.Where(a =&gt; fids.Contains(a.fid)).ToList();

// fast:
body = body.Where(a =&gt; fids_fast.Any(x =&gt; a.fid &gt;= x.from &amp;&amp; a.fid &lt;= x.to)).ToList();
Your code takes a list, ensures it's sorted and distinct, then assembles and returns another list. This approach is problematic when the input is in fact a generator, in which case your code forces the entire list to memory first. Also, the data might already be sorted or the caller might not be interested in the entire result.

public static List&lt;(int from, int to)&gt; get_consecutive_ranges(List&lt;int&gt; fids)
{
    if (fids == null || fids.Count == 0) return null;

    fids = fids.OrderBy(a =&gt; a).Distinct().ToList();


A much more idiomatic approach is to build an extension method that works on IEnumerable and yields its results. The call to Distinct().OrderBy(a =&gt; a) should be left to the caller. Also note that Distinct doesn't guarantee any particular order, so the sorting must always happen afterwards. The actual logic is overly complicated in your code and be greatly simplified as well. Here is how I would do it:

public static IEnumerable&lt;(int begin, int end)&gt; Ranges(this IEnumerable&lt;int&gt; nums)
{
    var e = nums.GetEnumerator();
    if (e.MoveNext())
    {
        var begin = e.Current;
        var end = begin + 1;
        while (e.MoveNext())
        {
            if (e.Current != end)
            {
                yield return (begin, end);
                begin = end = e.Current;
            }
            end++;
        }
        yield return (begin, end);
    }
}


Add a simple helper function for pretty printing, which should also be an extension method:

public static string Show(this IEnumerable&lt;(int begin, int end)&gt; ranges)
{
    return "[" + string.Join(",", ranges.Select(r =&gt; r.end - r.begin == 1 ? $"{r.begin}" : $"{r.begin}-{r.end-1}")) + "]";
}


Example usage:

Console.WriteLine(new int[] { }.Ranges().Show());                             -&gt; "[]"
Console.WriteLine(new int[] { 1 }.Ranges().Show());                           -&gt; "[1]"
Console.WriteLine(new int[] { 1, 2, 3, 4, 5 }.Ranges().Show());               -&gt; "[1-5]"
Console.WriteLine(new int[] { 1, 2, 3, 5 }.Ranges().Show());                  -&gt; "[1-3,5]"
Console.WriteLine(new int[] { 1, 3, 5, 6 }.Ranges().Show());                  -&gt; "[1,3,5-6]"
Console.WriteLine(new int[] { 1, 3, 3, 3, 5, 6 }.Distinct().Ranges().Show()); -&gt; "[1,3,5-6]"
Console.WriteLine(new int[] { 6, 3, 5, 1 }.OrderBy(i =&gt; i).Ranges().Show());  -&gt; "[1,3,5-6]"