LINQ - 基于列表中的索引返回平均值列表

3

我有一个有点奇怪的场景,无法理解:

我有一个外部列表,其中包含多个内部列表。
每个内部列表包含多个DataPoint对象。
每个内部列表包含相同数量的DataPoint对象。
DataPoint具有名为Value的字段,它是一个double类型 - 例如:

  • Outerlist
    • Innerlist
      • DataPoint(value=2)
      • DataPoint(value=2)
    • Innerlist
      • DataPoint(value=4)
      • DataPoint(value=5)
    • Innerlist
      • DataPoint(value=3)
      • DataPoint(value=5)

所以我的想法是将内部列表“合并”成一个列表,其中每个DataPoint的值应该是旧值的平均值,基于其在列表中的索引 - 在这种情况下:

  • List
    • DataPoint(3) - ((2+4+3)/3) = 3
    • DataPoint(4) - ((2+5+5)/3) = 4

是否有任何漂亮的Linq表达式可以执行此类操作(我打赌有:))?


我最终使用了以下解决方案:

var averages = Enumerable.Range(0, outer.First().Count()).Select(i => new DataPoint(outer.Select(l => l[i]).Average(x=>x.Value)));

它输出IEnumerable,现在具有平均值 - 耶!


如果你有最近版本的Resharper,值得手动输入for循环代码并查看它是否建议使用基于LINQ的重构。好吧,我开玩笑的,但这可能真的有效。 - user180326
哈哈,Resharper很好,但我怀疑它是否那么好。 :) 我认为你仍然需要使用for循环。不过,LINQ通常最适合进行位置无关的操作。 - drharris
你可能想看一下这个问题:https://dev59.com/Z1nUa4cB1Zd3GeqPXyqu - dlev
如果我理解你的问题正确,你想做一些类似于 LINQ 中的数据透视操作。可能会对这个问题感兴趣:使用LINQ透视数据 - Roman
4个回答

3

首先需要创建一个扩展方法,以便对内部列表进行切片:

public static IEnumerable<double> Slice(
    this IEnumerable<List<DataPoint>> innerLists,
    int index) {
        foreach(var innerList in innerLists) {
            yield return innerList.DataPoints[index].Value;
        }
    }
}

然后:

var averages = Enumerable.Range(0, count)
                         .Select(index => outerList.InnerLists.Slice(index))
                         .Select(slice => slice.Average());

我假设有这样一个层次结构:
class DataPoint { public double Value { get; set; } }

class InnerList { public List<DataPoint> DataPoints { get; set; } }

class OuterList { public IEnumerable<InnerList> InnerLists { get; set; } }

但是上述想法显然可以适应你的数据结构,无论你的结构是怎样的。

1
哇,这真是相当优雅。我从未考虑过这一点。 - drharris
1
相当不错的东西 - 显然比我最终使用的更易读。 - Lars Tabro Sørensen

2

这里的关键是按照它们的索引将内部列表值分组。类似这样的代码可以实现:

// Test data
var list = new List<List<double>>() { 
    new List<double> { 2, 2 },
    new List<double> { 4, 5 },
    new List<double> { 3, 5 }
};

// Created groups based on index
var lookup = from inner in list 
             from value in inner.Select((n,idx) => new { n,idx })
             group value by value.idx;

// Calculate averages    
var averagesBasedOnIndex = lookup.Select(g => g.Average(x => x.n)).ToList();

哦,太酷了,原来还有一个带索引解析的Select版本 - 我从来没有注意到过 :) - Lars Tabro Sørensen

1

我在使用iPad,所以还没有编译,但是以下代码应该可以工作:

Enumerable.Range(0,outer.First().Length).
   Select(i => outer.Select(l => l[i]).Average());

几乎...问题在于你正在区分哪个内部列表需要制表,而不是哪个数据点。此外,平均值将需要一个lambda函数,并且你需要在其中使用selectmany:Enumerable.Range(0, items.First().InnerLists.First().DataPoints.Count()). Select(i => items.SelectMany(x=>x.InnerLists).Average(x=>x.DataPoints[i].Value)); - Michael Hays
1
其实Jan几乎是完全正确的,除了平均数中缺少lambda表达式。最终我使用了以下代码:var averages = Enumerable.Range(0, outer.First().Count()).Select(i => new DataPoint(outer.Select(l => l[i]).Average(x=>x.Value))); - Lars Tabro Sørensen

0

这就是你所需要的全部:

var outer = new List<List<List<double>>>();
var l1 = outer.Select(
  x => new List<double> { 
         x.Average(y => y[0]), 
         x.Average(y => y[1]), 
         x.Average(y => y[2])
       });

有了像你这样的数据结构,它将会是:

var list = items.Select(x => new List<DataPoint> (
            Enumerable.Range(0,3).Select(z => 
                new DataPoint {
                   Value = x.InnerLists.Average(y => y.DataPoints[z].Value)})
           ));

感谢建议使用 Enumerable.Range 的人。


问题在于我不知道内部列表中数据点的数量,因为这些可能会因不同的外部列表而异。最终我使用了Enumerable.Range来处理动态数据点的数量。 - Lars Tabro Sørensen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接