我有以下结构
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);
}
}
}
行数/列数是动态的,但每行的列数相同。
答案 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
。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,至少要使它高效
下面你会看到我的示例代码的结果
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);
}