«Превращая» IEnumerable90 градусов - PullRequest
8 голосов
/ 18 февраля 2011

То, что я ищу, - это базовая операция (у которой, я уверен, есть имя, о котором я просто не знаю). У меня есть матрица, как:

{1,2,3}

{A, N, F}

{7,8,9}

который я хотел бы преобразовать в

{1, А, 7}

{2, N, 8}

{3, F, 9}

(Выше приведены только идентификаторы для объектов, а не реальных значений. Фактические объекты относятся к тому же типу и неупорядочены)

Я бы предпочел декларативное решение, но скорость - это фактор. Мне нужно будет перевернуть довольно много таблиц (100 тыс. Ячеек в минуту), и медленная версия будет на критическом пути.

Однако я все еще больше заинтересован в удобочитаемом решении. Я ищу альтернативные решения ниже. (Под альтернативой я имею в виду не вариации, а иной подход)

var  arrays = rows.Select(row => row.ToArray());
var cellCount = arrays.First().Length;
for(var i = 0;i<cellCount;i++){
  yield return GetRow(i,arrays);
}

IEnumerable<T> GetRow(int i,IEnumerable<T[]> rows){
  foreach(var row in rows}{
     yield return row[i]; 
  }
}

Из двух почти одинаково читаемых решений я бы выбрал более быстрое, но читаемость идет раньше скорости

EDIT Это всегда будет квадратная матрица

Ответы [ 6 ]

10 голосов
/ 18 февраля 2011

Я немного сомневаюсь в этой реализации. Он имеет побочные эффекты локально для итератора, но выглядит логически чистым для меня. Это предполагает, что каждая последовательность имеет одинаковую длину, но должна работать для любой. Вы можете думать об этом как о методе переменной длины Zip(). Он должен работать лучше, чем другие связанные решения LINQ, найденные в других ответах, поскольку он использует только минимальные операции, необходимые для работы. Вероятно, даже лучше без использования LINQ. Может даже считаться оптимальным.

public static IEnumerable<IEnumerable<T>> Transpose<T>(this IEnumerable<IEnumerable<T>> source)
{
    if (source == null) throw new ArgumentNullException("source");
    var enumerators = source.Select(x => x.GetEnumerator()).ToArray();
    try
    {
        while (enumerators.All(x => x.MoveNext()))
        {
            yield return enumerators.Select(x => x.Current).ToArray();
        }
    }
    finally
    {
        foreach (var enumerator in enumerators)
            enumerator.Dispose();
    }
}
5 голосов
/ 18 февраля 2011
4 голосов
/ 18 февраля 2011

Взгляните на этот метод расширения. Linq транспонировать . Я не уверен в производительности, но код выглядит элегантно.

1 голос
/ 18 февраля 2011

Ваш вопрос, по-видимому, подразумевает, что вы хотите изменить исходную матрицу.

Если это так, и если вы можете сохранить матрицу как IList<IList<T>> matrix, то это будет работать, однако, только в случае квадратной матрицы.

for(int i = 0; i < matrix.Count; ++i)
{
    for(int j = 0; j < i; ++j)
    {
        T temp = matrix[i][j];
        matrix[i][j] = matrix[j][i];
        matrix[j][i] = temp
    }
}
0 голосов
/ 08 июля 2012

Вот мой, если кому-то интересно. Он работает таким же образом, как и у Джеффа, но, кажется, немного быстрее (при условии, что эти ToArrays () необходимы). Там нет видимых петель или временных переменных, и он намного более компактен:

public static IEnumerable<IEnumerable<T>> Transpose<T>(
    this IEnumerable<IEnumerable<T>> source)
{
    return source
        .Select(a => a.Select(b => Enumerable.Repeat(b, 1)))
        .Aggregate((a, b) => a.Zip(b, Enumerable.Concat));
}

Если он вам нужен и для пустых списков, он становится таким:

public static IEnumerable<IEnumerable<T>> Transpose<T>(
    this IEnumerable<IEnumerable<T>> source)
{
    return source
        .Select(a => a.Select(b => Enumerable.Repeat(b, 1)))
        .DefaultIfEmpty(Enumerable.Empty<IEnumerable<T>>())
        .Aggregate((a, b) => a.Zip(b, Enumerable.Concat));
}

Я заметил, что спрашивающий написал, что матрица всегда будет квадратной. Эта реализация (и jeffs) будет оценивать всю строку за раз, но если мы знаем, что матрица квадратная, мы можем переписать функцию zip более подходящим способом:

public static IEnumerable<IEnumerable<T>> Transpose<T>(
    this IEnumerable<IEnumerable<T>> source)
{
    return source
        .Select(a => a.Select(b => Enumerable.Repeat(b, 1)))
        .DefaultIfEmpty(Enumerable.Empty<IEnumerable<T>>())
        .Aggregate(Zip);
}

public static IEnumerable<IEnumerable<T>> Zip<T>(
    IEnumerable<IEnumerable<T>> first, 
    IEnumerable<IEnumerable<T>> second)
{
    var firstEnum = first.GetEnumerator();
    var secondEnum = second.GetEnumerator();

    while (firstEnum.MoveNext())
        yield return ZipHelper(firstEnum.Current, secondEnum);
}

private static IEnumerable<T> ZipHelper<T>(
    IEnumerable<T> firstEnumValue, 
    IEnumerator<IEnumerable<T>> secondEnum)
{
    foreach (var item in firstEnumValue)
        yield return item;

    secondEnum.MoveNext();

    foreach (var item in secondEnum.Current)
        yield return item;
}

Таким образом, каждый элемент не будет оцениваться, пока не будет возвращен.

0 голосов
/ 18 февраля 2011

Ну, то, что вы ищете здесь - это преобразование T[][] -> T[][]. Существует множество IEnumerabe<IEnumerable<T>>.Transpose() решений, но все они сводятся к циклическому перечислению с использованием временных поисков / ключей, и они оставляют желать лучшего, когда речь идет о производительности на огромном объеме. Ваш пример на самом деле работает быстрее (хотя вы можете потерять и второй foreach).

Сначала спросите "нужен ли мне LINQ вообще". Вы не описали, какова цель транспонированной матрицы, и если скорость действительно является вашей заботой, вы могли бы преуспеть, если бы просто держались подальше от LINQ / foreach и делали это старомодным способом (для внутреннего использования)

...