Home > database >  How to retrieve consecutive numbers from a list (not LINQ preferred)?
How to retrieve consecutive numbers from a list (not LINQ preferred)?

Time:01-20

List<int> ints = new List<int>();
ints.Add(1,5,6);

How to get 5 and 6 in a new list?

I tried this but it didn't work

List<int> newList = new List<int>();
newList = ints.Select((i, j) => i - j).Skip(1).ToList();

Thanks and please don't judge too harshly, not an expert.

CodePudding user response:

I suggest to split your problem into two easier problems:

  1. Find all the indices where a new sequence of consecutive numbers starts. To do that, loop through the list and remember (= put into a new list) all indices where the number is not equal to the previous number 1.

  2. Split your list on these indices. To do that, loop through the list of indices you created in step 1 and create new lists based on those indices.

CodePudding user response:

I suspect this is where you were going with your LINQ:

An array of sorted, unique numbers, formatted so it's more obvious which the consecutives are

var ints = new[] { 
  1, 2, 3,  
  5, 6,  
  8, 9, 10, 11,  
  15, 16, 17,  
  20, 21, 22,  
  25,  
  27,  
  30, 31 
};

And a LINQ that collects consecutives:

var consecs = ints
  .Select((item, idx) => new { I = item, G = item - idx })
  .GroupBy(
    ig => ig.G, 
    ig => ig.I,  
    (k, g) => g.ToArray()
  )
  .Where(a => a.Length > 1)
  .ToArray();

How does it work?

  • The Select produces a new object that numbers the ints in the array, from 0 i.e. you get the number itself in item, and the index it's at in idx. We subtract the idex from the number. Because indexes increment by one each time, any run of numbers that also increments by one each time will produce a fixed value for G:
    • item == 1, idx == 0, G == 1, item == 2, idx == 1, G == 1, item == 3, idx == 2, G == 1,
    • item == 5, idx == 3, G == 2, item == 6, idx == 4, G == 2,
    • and so on
  • The group has 3 parameters:
    • what to group by: ig => ig.G - groups by G, which makes LINQ collect all items in the same G into a list. The input parameter is called ig because it's an anonymous type of I and G
    • what to put in the list-of-I-with-same-G: ig => ig.I - just the I item, meaning the output will be a list of ints, i.e. the original items
    • what to do to the output list: (k, g) => g.ToArray() - turn it from a grouping into an array. k is the grouping key (formerly called G); we don't need it. g is the list of numbers groupby collected wothether with the same G. It's normally a Grouping but we can transform it to an int[] at this stage
  • Then we run a Where to exclude arrays of only one item, because they aren't a consecutive run of two or more numbers. The group by output an Enumerable<int[]> so a is an int[]
  • ToArray at the end just turns the enumerable of arrays, that Where filtered for us, into an array of arrays, so the output is an int[][]

This means you end up with groups like:

consecs[0] -> new [] { 1,2,3 };
consecs[1] -> new [] { 5,6 };
consecs[2] -> new [] { 5,6 };

--

So you want to do it without LINQ?

OK, let's use a Dictionary<int,List<int>> to collect items under the same group number. We'll calculate the key of the dictionary from item-index like before, and we'll make sure it exists as a new list, and then we'll add items to it. Then we'll skip over any lists that are only 1 big

var ints = new[] { 
  1, 2, 3,  
  5, 6,  
  8, 9, 10, 11,  
  15, 16, 17,  
  20, 21, 22,  
  25,  
  27,  
  30, 31 
};

var d = new Dictionary<int, List<int>>();
for(int idx = 0; idx < ints.Length; idx  ){
  var g = ints[idx] - idx;

  d.TryAdd(g, new List<int>()); //make sure a list exists for g; does nothing if g is known

  d[g].Add(ints[idx]);
  
}

foreach(var kvp in d){
  if(kvp.Value.Count == 1)
    continue; //skip lists of only one item

  //do whatever with your list of consecutive ints
}
  •  Tags:  
  • Related