2011-08-05 15 views
10

Czy istnieje wyrafinowane wyrażenie LINQ, które może pozwolić mi na wykonanie następujących czynności w znacznie prostszy sposób. Mam List<List<double>>, zakładając, że lista jest kolumnami w macierzy 2d, chcę zamienić listę kolumn na listę wierszy. Mam następujący oczywiste rozwiązanie:LINQ zamienia kolumny w wiersze

int columns = 5; 
var values; // assume initialised as List<List<double>>() 

var listOfRows = new List<List<double>>(); 
for (int i = 0; i < columns ; i++) 
{ 
    List<double> newRow = new List<double>(); 
    foreach (List<double> value in values) 
    { 
     newRow.Add(value[i]); 
    } 
    listOfRows.Add(newRow); 
} 

Odpowiedz

5

Można LINQify wewnętrzną pętlę dość łatwo:

vector.AddRange(values.Select(value => value[i]));

, czy nie, że poprawia czytelność pozostaje całkowicie zależy od Ciebie!

+0

@DBM: jaka jest definicja z AddRange? –

+0

@ Reb.Cabin: http://msdn.microsoft.com/en-us/library/z883w3dc.aspx –

3

Oto wyrażenie LINQ, że zrobi to, co chcesz - patrząc na to bym osobiście trzymać z zagnieżdżonego foreach pętli chociaż - o wiele łatwiejsze do odczytania:

var columnList= new List<List<double>>(); 
columnList.Add(new List<double>() { 1, 2, 3 }); 
columnList.Add(new List<double>() { 4, 5, 6 }); 
columnList.Add(new List<double>() { 7, 8, 9 }); 
columnList.Add(new List<double>() { 10, 11, 12 }); 

int columnCount = columnList[0].Count; 
var rowList = columnList.SelectMany(x => x) 
         .Select((x, i) => new { V = x, Index = i }) 
         .GroupBy(x => (x.Index + 1) % columnCount) 
         .Select(g => g.Select(x=> x.V).ToList()) 
         .ToList(); 

Ten przykład działałby tylko na macierzy ze stałą liczbą kolumn. Zasadniczo spłaszcza macierz na liście, a następnie tworzy listę wierszy, grupując według indeksu elementu na liście modulo, licząc kolumnę.

Edit:

Inne podejście, znacznie bliżej do zagnieżdżonej pętli i prawdopodobnie podobna wydajność oprócz napowietrznej.

int columnCount = columnList[0].Count; 
int rowCount = columnList.Count; 

var rowList = Enumerable.Range(0, columnCount) 
         .Select(x => Enumerable.Range(0, rowCount) 
               .Select(y => columnList[y][x]) 
               .ToList()) 
         .ToList(); 
+0

+1 - to dobrze, jeśli chodzi o notatkę o stałej liczbie kolumn – Seth

+0

Czytelność Hmm jest prawdopodobnie mniej przyjazna. Czy wydajność będzie taka sama pomiędzy moją wersją a wyrażeniem Linq? – Seth

+0

@Seth: Zakładam, że działa * gorzej niż twoja wersja, ponieważ musi spłaszczyć, a następnie ponownie zgrupować, podczas gdy pętla for używa bezpośrednio indeksu elementu listy do utworzenia list wierszy – BrokenGlass

2
var inverted = Enumerable.Range(0, columnCount) 
       .Select(index => columnList.Select(list => list[index])); 

Krótko mówiąc, mamy wyliczać indeks kolumny z szeregu i użyć go do zbierania n-ty element każdej listy.

Należy pamiętać, że należy sprawdzić, czy każda lista ma taką samą liczbę kolumn.

0

Oto jeden, który działa dla macierzy prostokątnych (nie-ragged). Kod C# tutaj działa wycinane i wklejane w LinqPad, darmowe, interaktywne narzędzie programistyczne C#.

Definiuję operator postfiksu (czyli metodę rozszerzenia) "Transponuj". Użyj operatora następująco:

var rand = new Random(); 

    var xss = new [] { 
     new [] {rand.NextDouble(), rand.NextDouble()}, 
     new [] {rand.NextDouble(), rand.NextDouble()}, 
     new [] {rand.NextDouble(), rand.NextDouble()}, 
    }; 

    xss.Dump("Original"); 
    xss.Transpose().Dump("Transpose"); 

powodując coś takiego:

Original 
0.843094345109116 
0.981432441613373 

0.649207864724662 
0.00594645645746331 

0.378864820291691 
0.336915332515219 


Transpose 
0.843094345109116 
0.649207864724662 
0.378864820291691 

0.981432441613373 
0.00594645645746331 
0.336915332515219 

Istotą realizacji tego operatora jest następujący

public static IEnumerable<IEnumerable<T>> Transpose<T>(this IEnumerable<IEnumerable<T>> xss) 
    { 
     var heads = xss.Heads(); 
     var tails = xss.Tails(); 

     var empt = new List<IEnumerable<T>>(); 
     if (heads.IsEmpty()) 
      return empt; 
     empt.Add(heads); 
     return empt.Concat(tails.Transpose()); 
    } 

Oto pełna realizacja, w niektórych wierszach skomentowano, że można odkomentować, aby monitorować działanie tej funkcji.

void Main() 
{ 
    var rand = new Random(); 

    var xss = new [] { 
     new [] {rand.NextDouble(), rand.NextDouble()}, 
     new [] {rand.NextDouble(), rand.NextDouble()}, 
     new [] {rand.NextDouble(), rand.NextDouble()}, 
    }; 
    xss.Dump("Original"); 
    xss.Transpose().Dump("Transpose"); 
} 

public static class Extensions 
{ 
    public static IEnumerable<T> Heads<T>(this IEnumerable<IEnumerable<T>> xss) 
    { 
     Debug.Assert(xss != null); 
     if (xss.Any(xs => xs.IsEmpty())) 
      return new List<T>(); 
     return xss.Select(xs => xs.First()); 
    } 

    public static bool IsEmpty<T>(this IEnumerable<T> xs) 
    { 
     return xs.Count() == 0; 
    } 

    public static IEnumerable<IEnumerable<T>> Tails<T>(this IEnumerable<IEnumerable<T>> xss) 
    { 
     return xss.Select(xs => xs.Skip(1)); 
    } 

    public static IEnumerable<IEnumerable<T>> Transpose<T>(this IEnumerable<IEnumerable<T>> xss) 
    { 
//  xss.Dump("xss in Transpose"); 
     var heads = xss.Heads() 
//   .Dump("heads in Transpose") 
      ; 
     var tails = xss.Tails() 
//   .Dump("tails in Transpose") 
      ; 

     var empt = new List<IEnumerable<T>>(); 
     if (heads.IsEmpty()) 
      return empt; 
     empt.Add(heads); 
     return empt.Concat(tails.Transpose()) 
//   .Dump("empt") 
      ; 
    } 
} 
1

ja połączenie niektórych z rozwiązań powyżej, który czasami do kolumn i wierszy postać odwróconego pierwotną odpowiedź lub konwencji ja Zastosowanie: rzędzie odnosi się do pierwszego indeksu i kolumny do wewnętrznego (drugiego) indeks. na przykład Wartości [wiersz] [kolumny]

public static List<List<T>> Transpose<T>(this List<List<T>> values) 
    { 
     if (values.Count == 0 || values[0].Count == 0) 
     { 
      return new List<List<T>>(); 
     } 

     int ColumnCount = values[0].Count; 

     var listByColumns = new List<List<T>>(); 
     foreach (int columnIndex in Enumerable.Range(0, ColumnCount)) 
     { 
      List<T> valuesByColumn = values.Select(value => value[columnIndex]).ToList(); 
      listByColumns.Add(valuesByColumn); 
     } 
     return listByColumns; 
    }    

Właściwie wiersz i kolumna słowo jest po prostu nasza konwencja myślenia o danych w wierszach i kolumnach, a czasami dodaje więcej zamieszania niż ich rozwiązywania.

Po prostu zamieniamy wewnętrzny indeks zewnętrznego indeksu. (lub odwracanie indeksów). Tak więc można również zdefiniować następującą metodę rozszerzenia. . Ponownie pożyczyłem od powyższych rozwiązań, po prostu umieściłem to w czymś, co uważam za czytelne i dość kompaktowe.

Należy sprawdzić, czy wewnętrzne listy są jednakowych rozmiarów.

public static List<List<T>> InsideOutFlip<T>(this List<List<T>> values) 
    { 
     if (values.Count == 0 || values[0].Count == 0) 
     { 
      return new List<List<T>>(); 
     } 

     int innerCount = values[0].Count; 

     var flippedList = new List<List<T>>(); 
     foreach (int innerIndex in Enumerable.Range(0, innerCount)) 
     { 
      List<T> valuesByOneInner = values.Select(value => value[innerIndex]).ToList(); 
      flippedList.Add(valuesByOneInner); 
     } 
     return flippedList; 
    }    
Powiązane problemy