List <string []>由Linq确定最大长度

时间:2015-09-22 12:22:49

标签: c# linq list

我有以下结构

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);
        }
    }
}

行数/列数是动态的,但每行的列数相同。

7 个答案:

答案 0 :(得分:21)

这是我能想到的最好的:

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

一行的奖励积分?

说明: 既然你说他们都有相同数量的元素,那就拿第一行并循环遍历。使用索引从每个其他行获取该元素获取长度,然后获取其最大值。

答案 1 :(得分:19)

一种方法:

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。我用""的null-coalescing运算符替换它们,得到0作为长度。

由于您已经提到“每行具有相同的列数”,因此您可以使其更具可读性:

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

答案 2 :(得分:6)

以下是使用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}}
    

    (这不准确 - 由于deferred execution我们从未实际拥有此集合 - 根据需要计算每行的长度。)

  • 接下来,我们使用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}

优点:

  • 适用于单行。
  • 仅对集合进行一次迭代。
  • 不对具体实现(阵列列表)做出任何假设 - 可以使用任何IEnumerable<IEnumerable<string>>,而不依赖于[i]ElementAtOrDefault
  • 您已要求提供Linq解决方案。

缺点:

  • 有点复杂。
  • 如果行的长度不同Zip将无法按预期运行(返回最短的长度而不是最长的长度)。

答案 3 :(得分:4)

假设您的所有“行”具有相同数量的项目(“列”):

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

答案 4 :(得分:3)

令人惊讶的是,没有任何好的(有效的)答案

1)不要使用LINQ。尝试简单易读的程序解决方案
2)如果由于某种原因你想使用LINQ,至少要使它高效

下面你会看到我的示例代码的结果

enter image description here

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;
        }
    }
}

答案 5 :(得分:2)

这就是我所拥有的: -

  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();

说明: - 展平您的列表,投影它的值和索引(索引是根据列长度计算的),逐个索引并选择最大长度的值。

答案 6 :(得分:1)

汇总结果的一般方法是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));

现在我们有一个长度列表列表。更容易使用。

聚合通过保持一些运行总计,并运行参数是聚合和下一行的函数来工作,从而产生新的聚合。您可以选择提供种子值。它看起来像这样:

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);
 }