2016-02-22 9 views
5

Załóżmy, że mam następujące array (moje sekwencje są sortowane w kolejności rosnącej i zawierają liczby całkowite dodatnie)LINQ kwerendy do identyfikacji fragmentów w serii

var mySequence = new [] {1, 2, 3, 7, 8, 9, 15, 16, 17}; 

Chcę napisać kwerendę LINQ wybierz ciągła liczby w serii traktowanej jako grupa. W powyższym przykładzie otrzymam {[1, 2, 3], [7, 8, 9], [15, 16, 17]}.

Mogę napisać sekwencję foreach(), przechodzić przez każdy element i zobaczyć, gdzie sekwencja wykonuje skok i zgrupować tam grupę. Ale czy jest jakiś sposób na zrobienie tego LINQ? Być może będę w stanie przenieść mój kod foreach() do nowej metody rozszerzenia, więc mój kod nadal wygląda LINQy, ale zastanawiam się, czy jest już coś dostępnego w System.Linq.

EDYCJA: Stworzyłem własne rozszerzenie (w następujący sposób), ale Me.Name wymyślił coś bardzo inteligentnego w swojej odpowiedzi.

internal class Sequence 
{ 
    public int Start { get; set; } 
    public int End { get; set; } 
} 

internal static class EnumerableMixins 
{ 
    public static IEnumerable<Sequence> GroupFragments(this IEnumerable<int> sequence) 
    { 
     if (sequence.Any()) 
     { 
      var lastNumber = sequence.First(); 
      var firstNumber = lastNumber; 

      foreach(var number in sequence.Skip(1)) 
      { 
       if (Math.Abs(number - lastNumber) > 1) 
       { 
        yield return new Sequence() { Start = firstNumber, End = lastNumber }; 

        firstNumber = lastNumber = number; 
       } 
       else 
       { 
        lastNumber = number; 
       } 
      } 


      yield return new Sequence() { Start = firstNumber, End = lastNumber }; 
     } 
    } 
} 

Odpowiedz

10

Starą sztuczką do znalezienia tego rodzaju wysp jest odjęcie indeksu i wartości liczbowej. Wynik będzie reprezentował unikalne grupy. Używając wybierz przeciążenie tym indeksie:

var mySequence = new [] {1, 2, 3, 7, 8, 9, 15, 16, 17}; 

var groups = mySequence 
    .Select((val,ind) => new{val, group = val - ind}) 
    .GroupBy(v=>v.group, v=>v.val) 
    .Select(v=> v.ToList()).ToList(); 

(Używany ToList tutaj, ale oczywiście ToArray mogą być stosowane, jeżeli są korzystne tablice)

+0

Jest to ładny trick integers-- jeśli wynik (numer - indeks) wynosi 0, wtedy ta liczba znajduje się w oczekiwanym miejscu tablicy, jeśli liczba jest ujemna, to jest to dużo wcześniej, niż się spodziewano. – Hogan

+0

Co się stanie, jeśli moja sekwencja nie rozpocznie się od 1? załóżmy, że to jest moja seria var x = new [] {20, 21, 22, 25,26,27, 30,31,32}; – fahadash

+1

@Fahadash: to również zadziała, intencją jest, aby różnica między indeksem a wartością była większa, jeśli istnieje luka między wartościami. Dla tej sekwencji: 20-0 = 20, 21-1 = 20, 22 -2 = 20, 25 -3 = 22, 26-4 = 22, itd. Tak więc wartość "grupy" skacze od 20 do 22 –

1

ja trochę późno, ale i tak chcę podzielić się ten pomysł. Można również utworzyć iterator takiego:

public static class Extensions 
{ 
    public static IEnumerable<IEnumerable<int>> ContinuousNumbers(this IEnumerable<int> source) 
    { 
     using (var e = source.GetEnumerator()) 
     { 
      if (e.MoveNext()) 
      { 
       var list = new List<int> { e.Current}; 
       int temp = e.Current; 
       while (e.MoveNext()) 
       { 
        if (e.Current == temp+1) 
        { 
         list.Add(e.Current); 
         temp++; 
        } 
        else 
        { 
         yield return list; 
         list = new List<int> { e.Current}; 
         temp = e.Current; 
        } 
       } 

       if (list.Count > 0) 
       { 
        yield return list; 
       } 
      } 
     } 
    } 
} 

Innym wariantem może być przy użyciu metody Aggregate rozszerzenia:

var result = mySequence.Aggregate(new List<List<int>>(), 
            (r, current) => 
            { 
             if (r.Count==0 || (r.Last().Count>0 && r.Last().Last() != current-1)) 
              r.Add(new List<int> { current}); 
             else 
              r.Last().Add(current); 
             return r; 
            }); 
Powiązane problemy