2011-02-02 15 views
6

Mam listę ciągów znaków i chcę przekonwertować ją na listę zgrupowaną, w której wartości będą pogrupowane według ich lokalizacji na liście (nie w normalnym grupowaniu, ale w pewnym sensie, że te same przedmioty są w grupie tylko wtedy, gdy są razem). Rozważmy następujący przykład:Grupowanie "inteligentne" z LINQ

LinkedList<string> myList = new LinkedList<string>(); 
myList.AddLast("aaa"); 
myList.AddLast("aaa"); 
myList.AddLast("bbb"); 
myList.AddLast("bbb"); 
myList.AddLast("aaa"); 
myList.AddLast("aaa"); 
myList.AddLast("aaa"); 

LinkedList<MyTuple> groupedList = new LinkedList<MyTuple>(); 
groupedList.AddLast(new MyTuple("aaa", 2)); 
groupedList.AddLast(new MyTuple("bbb", 2)); 
groupedList.AddLast(new MyTuple("aaa", 3)); 

Czy ta przemiana być wykonane z LINQ lub powinienem napisać algorytm zwykły sposób z pętli?

+2

Drugi fragment nie zostanie uruchomiony, ponieważ dwukrotnie dodajesz wartość z tym samym kluczem. –

+0

Tak, widziałem to, zredagowałem pytanie :) – sventevit

Odpowiedz

4

Metoda rozszerzenie od this answer ma dość dużo, co pytasz (Microsoft również stanowić implementation to group contiguous items in a sequence):

public static IEnumerable<IGrouping<int, T>> 
    GroupConsecutive<T>(this IEnumerable<T> set, Func<T, T, bool> predicate) 
{ 
    var i = 0; 
    var k = 0; 
    var ranges = from e in set 
       let idx = ++i 
       let next = set.ElementAtOrDefault(idx) 
       let key = (predicate(e, next)) ? k : k++ 
       group e by key into g 
       select g; 
    return ranges; 
} 

Można go używać w następujący sposób:

void Main() 
{ 
    LinkedList<string> myList = new LinkedList<string>(); 
    myList.AddLast("aaa"); 
    myList.AddLast("aaa"); 
    myList.AddLast("bbb"); 
    myList.AddLast("bbb"); 
    myList.AddLast("aaa"); 
    myList.AddLast("aaa"); 
    myList.AddLast("aaa"); 
    IGrouping<int,string> ggg; 

    var groups=myList.GroupConsecutive((a,b)=>a==b); 

    ILookup<string,int> lookup=groups.ToLookup(g=>g.First(),g=>g.Count()); 

    foreach(var x in lookup["aaa"]) 
    { 
     Console.WriteLine(x); //outputs 2 then 3 
    } 
    foreach(var x in lookup["bbb"]) 
    { 
     Console.WriteLine(x); //outputs 2 
    } 

} 

Zauważ, że końcowy pojemnik jest ILookup, który zachowuje się trochę jak słownik, ale pozwala przechowywać wiele wartości w jednym kluczu.

+0

Jak napisano, 'GroupConsecutive' wywołuje komparator z" domyślnym "ostatnim razem. To wydaje się złe (szczególnie w moim przypadku, ponieważ uległo awarii z wyjątkiem NullReferenceException, ponieważ mój komparator tego nie oczekiwał.) –

+0

@ScottStafford: Chciałbym przejść z połączonym rozwiązaniem MS tutaj. Użyłem go z dużym sukcesem. – spender

1

Nie jest to możliwe ze Słownikiem. Słownik jest asocjacyjny (tzn .: każdy klucz musi wskazywać tylko jeden i jeden) i jest z natury nieuporządkowany. Do tej struktury danych musisz użyć czegoś innego. Nie byłoby to jednak strasznie trudne!

Edit

List<Tuple<string, int>> powinno załatwić sprawę:

List<KeyValuePair<string, int>> structure = new List<KeyValuePair<string, int>>(); 
structure.Add(new KeyValuePair<string, int>(myList[0], 1); 
for(int i = 0; i < myList.Count; i++) 
{ 
    if(myList[i] == structure[structure.Count-1].Key) 
    { 
     structure[structure.Count-1].Value += 1; 
    } 
    else 
    { 
     structure.Add(new KeyValuePair<string, int>(myList[i], 1); 
    } 
} 

Po tym powinieneś (przetestowane!) Mają co szukasz.

Edit (jedna myśl)

Chociaż może to być możliwe z LINQ (przy TakeWhile i liczy ...), nadal uważam, że więcej sensu używać pętlę tutaj, to proste. Ktoś bardziej błyskotliwy niż ja mógłbym spróbować pracować z Linq.

+0

Powiedz, że 'List >'? –

+0

Jak się tam dostać! Jestem teraz w trakcie edycji :) – Crisfole

+0

Myślę, że kontener "coś innego" już istnieje z interfejsem ILookup (zobacz moją odpowiedź) – spender