2013-07-31 8 views
12

Jeśli mam listę zawierającą dowolną liczbę list, tak jak poniżej:Jak "zip" lub "obrócić" zmienną liczbę list?

var myList = new List<List<string>>() 
{ 
    new List<string>() { "a", "b", "c", "d" }, 
    new List<string>() { "1", "2", "3", "4" }, 
    new List<string>() { "w", "x", "y", "z" }, 
    // ...etc... 
}; 

... czy jest jakiś sposób aby jakoś „zip” lub „Rotate” Listy do czegoś takiego?

{ 
    { "a", "1", "w", ... }, 
    { "b", "2", "x", ... }, 
    { "c", "3", "y", ... }, 
    { "d", "4", "z", ... } 
} 

Oczywistym rozwiązaniem byłoby zrobić coś takiego:

public static IEnumerable<IEnumerable<T>> Rotate<T>(this IEnumerable<IEnumerable<T>> list) 
{ 
    for (int i = 0; i < list.Min(x => x.Count()); i++) 
    { 
     yield return list.Select(x => x.ElementAt(i)); 
    } 
} 

// snip 

var newList = myList.Rotate(); 

... ale zastanawiałem się, czy nie było czystsze sposób robić to za pomocą LINQ lub w inny sposób?

+0

Dlaczego chcesz rozwiązać ten problem, używając LINQ? –

+0

@ Moo-Juice - dobra rada, zredagowałem mój tytuł. Myślę, że po prostu automatycznie założyłem, że rozwiązanie użyje linq w jakiś sposób, ale przypuszczam, że niekoniecznie tak jest. – Michael0x2a

+2

Twój przykładowy kod jest bardzo nieefektywny, jeśli sekwencje nie są losowym dostępem. –

Odpowiedz

16

Można toczyć własną instancję ZipMany który ręcznie iteracje każdego z wyliczeń.To będzie prawdopodobnie lepiej na większych sekwencji niż przy użyciu GroupBy po projekcji każdą sekwencję:

public static IEnumerable<TResult> ZipMany<TSource, TResult>(
    IEnumerable<IEnumerable<TSource>> source, 
    Func<IEnumerable<TSource>, TResult> selector) 
{ 
    // ToList is necessary to avoid deferred execution 
    var enumerators = source.Select(seq => seq.GetEnumerator()).ToList(); 
    try 
    { 
    while (true) 
    { 
     foreach (var e in enumerators) 
     { 
      bool b = e.MoveNext(); 
      if (!b) yield break; 
     } 
     // Again, ToList (or ToArray) is necessary to avoid deferred execution 
     yield return selector(enumerators.Select(e => e.Current).ToList()); 
    } 
    } 
    finally 
    { 
     foreach (var e in enumerators) 
     e.Dispose(); 
    } 
} 
+0

Musiało minąć kilka lat, zanim zadzwonię na telefon ... :) –

+0

Poszedłem i posprzątałem dla ciebie. Wierzę, że uchwyci to twoje zamiary. –

+0

@JeffMercado Twoje uderzenie mnie dosłownie 5 sekund. Miałem prawie ten sam kod (który testowałem i działało). Myślę, że to dobra edycja. –

12

Można to zrobić za pomocą rozszerzenia Select ma swój Func<T, int, TOut>:

var rotatedList = myList.Select(inner => inner.Select((s, i) => new {s, i})) 
         .SelectMany(a => a) 
         .GroupBy(a => a.i, a => a.s) 
         .Select(a => a.ToList()).ToList(); 

To daje kolejny List<List<string>>.

Podział

.Select(inner => inner.Select((s, i) => new {s, i})) 

Dla każdej listy wewnętrznej, zakładamy zawartość listy do nowej anonimowy obiekt z dwóch właściwości: s, wartość ciągu, a i indeks tej wartości w oryginalnej listy .

.SelectMany(a => a) 

Mamy spłaszczyć wynik do jednej listy

.GroupBy(a => a.i, a => a.s) 

Group Firma przez właściwość i naszego anonimowego obiektu (przypomnijmy to indeks) i wybierz właściwość s jako nasze wartości (ciąg tylko).

.Select(a => a.ToList()).ToList(); 

Dla każdej grupy zmieniliśmy wyliczalną na listę i inną listę dla wszystkich grup.

+2

+1, ale mogę być w mniejszości tutaj, myśląc, że oryginalna metoda postępowania była * droga * bardziej czytelna. –

+0

Jestem też w tej łodzi, ale wciąż jestem pod wrażeniem – Jonesopolis

+0

@ Moo-Juice Zgadzam się. I daje lepszą wydajność. Postrzegam to jako skrajny przypadek, w którym działa LINQ, ale daleki jest od optymalnego. –

5

Co powiesz na używanie SelectMany i GroupBy z niektórymi indeksami?

// 1. Project inner lists to a single list (SelectMany) 
// 2. Use "GroupBy" to aggregate the item's based on order in the lists 
// 3. Strip away any ordering key in the final answer 
var query = myList.SelectMany(
    xl => xl.Select((vv,ii) => new { Idx = ii, Value = vv })) 
     .GroupBy(xx => xx.Idx) 
     .OrderBy(gg => gg.Key) 
     .Select(gg => gg.Select(xx => xx.Value)); 

Od LINQPad:

we groupa da items

+0

+1. Jest to w rzeczywistości bardziej zgodne z sygnaturą metody użytą w pytaniu ('IEnumerable >'). –

0
(from count in Range(myList[0].Count) 
select new List<string>(
    from count2 in Range(myList.Count) 
    select myList[count2][count]) 
    ).ToList(); 

To nie jest ładny, ale myślę, że to zadziała.

+0

Myślę, że to rozwiązanie działa tylko wtedy, gdy istnieją trzy listy wewnątrz 'mylist'. Miałem nadzieję na rozwiązanie, które działa bez względu na to, ile list istnieje. – Michael0x2a

+0

OK, ja * myślę, że * moja edycja sprawia, że ​​tak się stanie. Nie w 100% pewne, ale podstawowa idea istnieje. –

1

można skondensować for pętle używając Range:

var result = Enumerable.Range(0, myList.Min(l => l.Count)) 
    .Select(i => myList.Select(l => l[i]).ToList()).ToList(); 
3

Oto nieefektywny wariant na podstawie Matrix transpozycji:

public static class Ext 
{ 
    public static IEnumerable<IEnumerable<T>> Rotate<T>(
     this IEnumerable<IEnumerable<T>> src) 
    { 
     var matrix = src.Select(subset => subset.ToArray()).ToArray(); 
     var height = matrix.Length; 
     var width = matrix.Max(arr => arr.Length); 

     T[][] transpose = Enumerable 
      .Range(0, width) 
      .Select(_ => new T[height]).ToArray(); 
     for(int i=0; i<height; i++) 
     {   
      for(int j=0; j<width; j++) 
      {    
       transpose[j][i] = matrix[i][j];    
      } 
     } 

     return transpose; 
    } 
} 
+0

+1, czytelne * i * metoda rozszerzenia. –

+0

@ Moo-Juice Nie strasznie skuteczny, chociaż można go poprawić. Mnóstwo rzeczy w szyku. :) – JerKimball

+0

+1 za wzmiankę o macierzowej transpozycji –

Powiązane problemy