Подтвердить что ты не робот

Список <string []> определить максимальную длину Linq

У меня есть следующая структура

List<string[]> sList = new List<string[]>() {
    new[] { "x", "xxx", "xxxx" },  //1,3,4
    new[] { "x", "xx", "xx" },     //1,2,2
    new[] { "xxxxxx", "xx", "xx" } //6,2,2
};

и мне нужно определить максимальный string.length элементов по столбцу

В этом примере ожидаемый результат должен быть:

List<int> Result = new List<int>() { 6, 3, 4 };

Есть ли простой подход Linq?

мое усилие (работает, но не использует Linq):

List<int> Result = new List<int>();
foreach (string[] line in Table)
{
    for (int i = 0; i < line.Length; i++)
    {
        if (Result.Count > i)
        {
            if (Result[i] < line[i].Length)
            {
                Result[i] = line[i].Length;
            }
        }
        else
        {
            Result.Insert(i, line[i].Length);
        }
    }
}

количество строк/столбцов является динамическим, но каждая строка имеет одинаковое количество столбцов.

4b9b3361

Ответ 1

Один подход:

int maxColumns = sList.Max(arr => arr.Length);
List<int> Result = Enumerable.Range(0, maxColumns)
    .Select(i => sList.Max(arr => (arr.ElementAtOrDefault(i) ?? "").Length))
    .ToList();

Вы хотите максимальную длину столбца. Столбцы - это индексы массива. Таким образом, вам нужен способ взглянуть на массивы на индекс. Поэтому я использовал Enumerable.Range(0, maxColumns). Затем я использую ElementAtOrDefault для обработки случая, когда массив не содержит столько "столбцов" (не нужно, как я объясняю ниже). Это возвращает null для ссылочных типов, таких как string. Я заменяю их нулевым коалесцирующим оператором с "", который дает 0 как длину.

Поскольку вы упомянули, что "каждая строка имеет одинаковое количество столбцов", вы можете сделать ее более читаемой:

List<int> Result = Enumerable.Range(0, sList.First().Length)
    .Select(i => sList.Max(arr => arr[i].Length))
    .ToList();

Ответ 2

Это лучшее, что я мог придумать:

List<int> Result = sList.First().Select((x, i) => sList.Max(y => y[i].Length)).ToList();

Бонусные точки для одной строки?

Объяснение: Поскольку вы сказали, что все они имеют одинаковое количество элементов, возьмите первую строку и пропустите ее. Используйте индекс, чтобы получить этот элемент из каждой из других строк, получая длину, а затем максимум этого.

Ответ 3

Здесь используется подход Aggregate:

var maxLengths = sList.Select(strings => strings.Select(s => s.Length))
                      .Aggregate((prev, current) => prev.Zip(current, Math.Max));

Это работает с предположением, что каждая коллекция имеет одинаковое количество элементов.

Как это работает:

  • Сначала мы берем только длины строки, поэтому концептуально мы работаем с IEnumerable<IEnumerable<int>>, поэтому наша коллекция выглядит так:

    {{1, 3, 4},
     {1, 2, 2},
     {6, 2, 2}}
    

    (это неточно - из-за отложенного исполнения у нас никогда не было этой коллекции - длины рассчитываются для каждой строки по мере необходимости.)

  • Затем мы используем Aggregate для объединения векторов:

    • У нас есть {1, 3, 4} и {1, 2, 2}.
    • Zip две коллекции: {{1, 2}, {3, 2}, {4, 2}}.
    • Примените Math.Max к каждой паре и получите {2, 3, 4}.
  • Повторите для {2, 3, 4} и {6, 2, 2} и получите {6, 3, 4}.

Плюсы:

  • Работает для одной строки.
  • Повторяйте только однократную сортировку коллекции.
  • Не делает никаких предположений о конкретной реализации (List of Arrays) - может работать с любым IEnumerable<IEnumerable<string>>, не полагаясь на [i] или ElementAtOrDefault.
  • Вы попросили решение Linq.

Минусы:

  • Несколько сложнее.
  • Если строки были разной длины, Zip не будет вести себя так, как ожидалось (верните кратчайшую длину, а не самую длинную).

Ответ 4

Предполагая, что все ваши "строки" имеют одинаковое количество элементов ( "столбцы" ):

var rowLength = sList[0].Length;
var result = Enumerable.Range(0, rowLength)
    .Select(index => sList.Select(row => row[index].Length).Max())
    .ToList();

Ответ 5

Удивительно, что нет хороших (эффективных) ответов

1) Не используйте LINQ. Попробуйте простое, понятное процедурное решение
2) Если по какой-то причине вы хотите использовать LINQ, по крайней мере, сделать его эффективным

Ниже вы видите результаты моего примера кода

введите описание изображения здесь

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace ConsoleApplication9
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            Random rand = new Random();
            int maxColumnLength = 10;
            int rowNumber = 100000;
            int colNumber = 300;
            List<string[]> input = new List<string[]>();
            for (int i = 0; i < rowNumber; i++)
            {
                string[] row = new string[colNumber];
                for (int j = 0; j < colNumber; j++)

                    row[j] = new string('x', rand.Next(maxColumnLength));

                input.Add(row);
            }

            var result = Time(SimpleIteration, input, "Simple Iteration");

            var result2 = Time(SimpleIterationWithLinq, input, "Simple Iteration LINQ");

            var result3 = Time(AcceptedAnswer, input, "Accepted Answer");

            var result4 = Time(TomDoesCode, input, "TomDoesCode");

            //var result5 = Time(Kobi, input, "Kobi"); //StackOverflow

            var result6 = Time(Konamiman, input, "Konamiman");

            var result7 = Time(RahulSingh, input, "RahulSingh");

            System.Console.ReadLine();
        }

        private static List<int> SimpleIteration(List<string[]> input)
        {
            int[] maxPerColumn = new int[input.First().Length];
            for (int i = 0; i < maxPerColumn.Length; i++)
                maxPerColumn[i] = -1;

            foreach (var row in input)
            {
                for (int i = 0; i < row.Length; i++)
                    if (maxPerColumn[i] < row[i].Length)
                        maxPerColumn[i] = row[i].Length;
            }

            return maxPerColumn.ToList();
        }

        private static List<int> SimpleIterationWithLinq(List<string[]> input)
        {
            return input.Aggregate(new int[input.First().Length], (maxPerColumn, row) =>
             {
                 for (int i = 0; i < row.Length; i++)
                     if (maxPerColumn[i] < row[i].Length)
                         maxPerColumn[i] = row[i].Length;

                 return maxPerColumn;
             }).ToList();
        }

        private static List<int> AcceptedAnswer(List<string[]> input)
        {
            return Enumerable.Range(0, input.First().Length)
                             .Select(i => input.Max(arr => arr[i].Length))
                             .ToList();
        }

        private static List<int> TomDoesCode(List<string[]> input)
        {
            return input.First().Select((x, i) => input.Max(y => y[i].Length)).ToList();
        }

        private static List<int> Kobi(List<string[]> input)
        {
            return input.Select(strings => strings.Select(s => s.Length))
                      .Aggregate((prev, current) => prev.Zip(current, Math.Max)).ToList();
        }

        private static List<int> Konamiman(List<string[]> input)
        {
            var rowLength = input[0].Length;
            return Enumerable.Range(0, rowLength)
                .Select(index => input.Select(row => row[index].Length).Max())
                .ToList();
        }

        private static List<int> RahulSingh(List<string[]> input)
        {
            int arrayLength = input.First().Length;
            return input.SelectMany(x => x)
                                     .Select((v, i) => new { Value = v, Index = i % arrayLength })
                                     .GroupBy(x => x.Index)
                                     .Select(x => x.Max(z => z.Value.Length))
                                     .ToList();
        }

        private static List<int> Time(Func<List<string[]>, List<int>> act, List<string[]> input, string methodName)
        {
            Stopwatch s = Stopwatch.StartNew();
            var result = act(input);
            s.Stop();
            Console.WriteLine(methodName.PadRight(25) + ":" + s.ElapsedMilliseconds + " ms");
            return result;
        }
    }
}

Ответ 6

Это то, что у меня есть: -

  int arrayLength = sList.First().Length;
  List<int> result = sList.SelectMany(x => x)
                           .Select((v, i) => new { Value = v, Index = i % arrayLength  })
                           .GroupBy(x => x.Index)
                           .Select(x => x.Max(z => z.Value.Length))
                           .ToList();

Объяснение: - Сгладить список, спроецировать его значение и индекс (индекс рассчитывается на основе длины столбцов), группировать по индексу и выбирать значение с максимальной длиной.

Ответ 7

Общий метод агрегирования результатов - это метод расширения Aggregate.

tl; rd для простейшего случая:

    slist.Select(row => row.Select(str => str.Length))
         .Aggregate(l, r) => l.Zip(r, Math.Max);

Сначала давайте получим данные в форме, которую хотим: Список длин

var llist = slist.Select(row => row.Select(str => str.Length));

Теперь у нас есть список списков длины. Намного легче работать.

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

List<Int> seed = ???
Func<IEnumerable<Int>, IEnumerable<Int>, IEnumerable<Int>> accumulator  = ???
llist.Aggregate(seed, accumulator);

Отсюда я предполагаю, что число "столбцов" является переменной.

Значение семени new List<int>(). Если есть 0 строк, то ваш результат.

List<Int> seed = new List<Int>();
Func<List<Int>, List<Int>, List<Int>> accumulator  = ???
llist.Aggregate(seed, accumulator);

Для каждой строки новый агрегат является максимальным значением для каждого элемента агрегата и следующей строки.

Это происходит именно так, как это делает метод .Zip, если элементы гарантированно имеют одинаковую длину: https://msdn.microsoft.com/en-us/library/vstudio/dd267698(v=vs.100).aspx

дает

private IEnumerable<Int> accumulate(aggregate, row){
  aggregate.Zip(row, (i1, i2) => Math.max(i1, i2));
}

К сожалению, у нас нет этой гарантии, поэтому нам придется предоставить наш собственный ZipAll. Правила заключаются в следующем: если элемент выходит из обоих, возьмите макс. Если он существует только в одном, возьмите его. Это немного картофеля; ZipAll действительно должен быть библиотечной функцией.

  IEnumerable<T> ZipAll(IEnumerable<T> left, IENumerable<T> right, Func<T, T, T> selector) {
   var le = left.GetEnumerator;
   var re = right.GetEnumerator;

   while(le.MoveNext()){
     if (re.MoveNext()) {
       yield return selector(le.Current, re.Current);
     } else {
       yield return le.Current;
     }
   }

   while(re.MoveNext()) yield return re.Current;
}

(1)

Это дает нам

List<Int> seed = new List<Int>();
Func<List<Int>, List<Int>, List<Int>> accumulator = (l, r) => ZipAll(l, r, Math.Max);
llist.Aggregate(seed, accumulator);

Если вы встраиваете все (принимаете нашу пользовательскую функцию ZipAll), которая становится

llist.Aggregate(new List<Int>(), (l, r) => ZipAll(l, r, Math.Max));

Если у вас есть хотя бы одна строка, вы можете оставить семя (первая строка станет семенем), чтобы получить

llist.Aggregate((l, r) => ZipAll(l, r, Math.Max));

Если число столбцов постоянное, мы можем использовать сборку в Zip

llist.Aggregate((l, r) => l.Zip(r, Math.Max));


(1) За пределами этого вопроса более общая перегрузка
IEnumerable<TResult, TLeft, TRight> ZipAll(IEnumerable<TLeft> left, IENumerable<TRight> right, Func<TResult, TLeft, TRight> selector, Func<TResult, TLeft> leftSelector, Func<Tresult, TRight> rightselector) {
   var le = left.GetEnumerator;
   var re = right.GetEnumerator;

   while(le.MoveNext()){
     if (re.MoveNext()) {
       yield return selector(le.Current, re.Current);
     } else {
       yield return leftSelector(le.Current);
     }
   }

   while(re.MoveNext()) yield return rightSelector(re.Current);
}

С этим мы могли бы написать наш предыдущий ZipAll в терминах этого с левым и правым проекциями в качестве функции тождества как

IEnumerable<T> ZipAll(this IEnumerable<T> left, IENumerable<T> right, Func<T, T, T> selector) {
    return ZipAll(left, right, selector, id => id, id => id);
 }