Список <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);
}
}
}
количество строк/столбцов является динамическим, но каждая строка имеет одинаковое количество столбцов.
Ответы
Ответ 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);
}