1

I have a set of numbers List<int> (for example) : 1, 3, 4, 5, 7, 12, 13, 14, 15, 20, 22, 24, 28, 29, 30

I want to have them grouped as sequential like :

Sequence1 = from 1 amount 1 (1)
Sequence2 = from 3 amount 3 (3, 4, 5)
Sequence3 = from 7 amount 1 (7)
Sequence4 = from 12 amount 4 (12, 13, 14, 15)
Sequence5 = from 20 amount 1 (20)
Sequence6 = from 22 amount 1 (22)
Sequence7 = from 24 amount 1 (24)
Sequence8 = from 28 amount 3 (28, 29, 30)

I know how to do it with a for and checking for each number. Is there an more elegant way or an algorithm, or some sql/lambda command that would help me ?

CodesInChaos
  • 106,488
  • 23
  • 218
  • 262
Bart Calixto
  • 19,210
  • 11
  • 78
  • 114
  • A for loop is the simplest way I can think of. You probably could do it with LINQ, but that would most likely be much uglier. – CodesInChaos Dec 02 '11 at 13:41
  • 1
    Why not foreach? Idk what do you think is more "elegant"? – nikola-miljkovic Dec 02 '11 at 13:42
  • after reading this : http://stackoverflow.com/questions/3139323/finding-continuous-ranges-in-a-set-of-numbers i completelly missed that way (for example) so I thought there may be a more elegant solution like check for the number - index of an array or something like that. I simply don't know if foreach is the best way. – Bart Calixto Dec 02 '11 at 13:47
  • Are your numbers sorted in the input? – CodesInChaos Dec 02 '11 at 14:07
  • Bart, how are your "from" and "amount" values determined? Is it by another function of based on your list? – MPelletier Dec 02 '11 at 14:16
  • @MPelletier The subsequences he wants are those where each item is 1 larger than the previous. – CodesInChaos Dec 02 '11 at 14:18
  • Also, are there limit cases? Can numbers from list be excluded? – MPelletier Dec 02 '11 at 14:18
  • @CodeInChaos: Then why is amount 1, 3, 1, 4, 1, 1, 1, 3, and not 1, 2, 3, 4, 5, 6...? I just don't see his algorithm here... – MPelletier Dec 02 '11 at 14:20
  • 1
    i want a pack of numbers that are correlative, get the first number of the sequence and the amount. Ordering the sequence beforehand is a valid option. I just want to get the base number and the amount of number correlatives after theres a gap. – Bart Calixto Dec 02 '11 at 14:25
  • You are aware that even if you find a 'shorter' way of expressing this, it still comes down to a loop? And as it's a very specifica and ightly bounded problem, writing a simple loop over the ordered list is most likely the most efficient way of doing it. – MatBailie Dec 02 '11 at 14:34
  • @MPelletier: The value following amount in his output is the number of items in each sub-sequence. – Chris Dunaway Dec 02 '11 at 15:39
  • @ChrisDunaway I had figured as much, I just didn't know what made the amount what it was. I finally got it with Bart's last explanation: it's the number of consecutive items starting at a certain value. – MPelletier Dec 02 '11 at 15:43
  • @jsobo the reason I downvoted your answer is that you posted how to extract a subsequence where you already know the start and length, not how to find *sequential* numbers in the given sequence. I commented on sll's equivalent answer, but he deleted it before you you noticed the downvote. – CodesInChaos Dec 02 '11 at 15:49

3 Answers3

5

If the input is sorted, and you really want to avoid a foreach loop, you can use:

list.Select((value,index)=>new {value,index}).GroupBy(x=>x.value-x.index,x=>x.value).Select(g=>g.AsEnumerable())

One could also write a general helper method:

public static IEnumerable<IEnumerable<T>> SplitBetween<T>(this IEnumerable<T> sequence, Func<T,T,bool> predicate)
{
  T previous=default(T);
  List<T> list=new List<T>();
  int index=0;
  foreach(T current in sequence)
  {
    if((index>0)&&predicate(previous,current))
    {
      yield return list.ToArray();
      list.Clear();
    }
    list.Add(current);
    previous=current;
    index++;
  }
  if(list.Count>0)
    yield return list.ToArray();
}

And then use it with list.SplitBetween((previous,current) => previous+1 != current)

CodesInChaos
  • 106,488
  • 23
  • 218
  • 262
  • +1 And you may as well provide the code to sort the list as it's trivial. – Yuck Dec 02 '11 at 14:13
  • @Yuck No that wouldn't work. I think for `1, 4, 2, 3` the expected output is `(1) (4) (2,3)` and not `(1,2,3,4)` as sorting would return. – CodesInChaos Dec 02 '11 at 14:14
  • 1
    Excelent, except the zeros in the return... where they come from? – Bart Calixto Dec 02 '11 at 14:23
  • What zeros are you talking about? – CodesInChaos Dec 02 '11 at 14:33
  • that returns all elements, i just want the base number and count. like: Dictionary() where the key is the number (value) and the value is the amount of numbers after the key that are sequential – Bart Calixto Dec 02 '11 at 14:35
  • 1
    Then add a `Select(group=>new {group.First(),group.Count()})`. In your examples you listed the elements in each group, so I thought you were interested in them. – CodesInChaos Dec 02 '11 at 14:37
  • Good Idea, unfortunately in all groups containing more than 1 element, there is a trailing `0`. How comes that? – Fischermaen Dec 02 '11 at 15:04
  • @Fischermaen I can't reproduce any trailing `0`s. – CodesInChaos Dec 02 '11 at 15:13
  • I get the trailing zeros too (in some sequence) if you use my example, you will see trailing zeros in 2 sequences. – Bart Calixto Dec 02 '11 at 15:18
  • @Bart I don't get any `0`s with your example. My whole code: http://nopaste.info/a276ed9200_nl.html running in LinqPad 4.31 (32 bit) – CodesInChaos Dec 02 '11 at 15:39
  • I do not get any trailing zeroes when I run the code, either. – Chris Dunaway Dec 02 '11 at 15:43
  • That's funny. I test it under Windows 7, .NET 4 and I get in every seequence containing more than 1 item a trailing `0`. – Fischermaen Dec 02 '11 at 21:45
  • Same here, Win7 .NET 4 - All sequence containing more than 1 item has a trailing 0 :S... bug? I don't set as the answer because it doesn't work on 'some systems (?) ' still +1 – Bart Calixto Dec 02 '11 at 22:25
  • 1
    @Bart The capacity is larger than the size of these collections. If you check the `count` it is small enough to exclude the trailing `0`s. That's just like `List` has an underlying array that's larger than than the size of the list due to its size doubling growth strategy. The only reason why you see those `0`s is because you peek at private data with your debugger. If enumerate the sequences you won't get them. – CodesInChaos Dec 03 '11 at 11:30
3

I don't think that this is very "elegant", but here is my suggestion, hopefully it helps you:

var list = new List<int> { 1, 3, 4, 5, 7, 12, 13, 14, 15, 20, 22, 24, 28, 29, 30 };

int offset = 0;
int sequence = 0;
do
{
    int offset1 = offset;
    var subList = list.Skip(offset).TakeWhile((item, index) => (index == 0) || (item == (list[offset1 + index - 1] + 1))).ToList();
    offset += subList.Count();
    sequence++;
    Debug.WriteLine("Sequence {0} from {1} amount {2} ({3})", sequence, subList[0], subList.Count(), string.Join(" ", subList));
}
while (offset < list.Count);
Fischermaen
  • 12,238
  • 2
  • 39
  • 56
1
int n = 12;//your number
int x = list.IndexOf(n);
var result = list.Skip(x).TakeWhile((value, index) => value - index == n);
Reza ArabQaeni
  • 4,848
  • 27
  • 46