从IEnumerable<IEnumerable<T>>中获取一个属性的平均值的IEnumerable,其中T具有两个属性

3

我有一个包含 IEnumerable<IEnumerable<CustomObject>> 的变量,其中的 CustomObject 包括 x(它被用作关键字,在这个例子中是 123)和一个 y 值。以下是一些虚假数据:

{
  { {1, 2}, {2, 4}, {3, 6}, {4, 8} }, 
  { {1, 2}, {2, 0}, {3, 0}, {4,-2} },
  { {1, 2}, {2, 2}, {3, 0}, {4, 0} }
}

以下是IEnumerable<CustomObject>的最佳检索方式:
{ {1, 2}, {2, 2}, {3, 2}, {4, 2} }
即每个元素的y值的平均值。
性能需要合理,因此不能使用.ToList()或类似的操作。我试过各种LINQ操作,但都无法实现。
更新
@Bort,@Rawling,我已经测试了你们的答案,@Rawling的答案稍微快一点。然而,@Bort的答案更易读,所以我想暂时采用那个。请继续提供答案!
4个回答

5
使用LINQ,您可以使用SelectMany将列表中的列表展平,然后通过GroupBy x并选择平均值:Select
var averages = customObjectLists
    .SelectMany(l => l)
    .GroupBy(co => co.x)
    .Select(g => new CustomObject { x => g.Key, y = g.Average(co => co.y) });

1
GroupBy 不算作调用 ToList() 吗?它确实会固定整个数据集。 - Rawling
如果你要计算平均数,你必须遍历整个数据集——否则你无法计算平均值。 - Maarten
是的,你需要对它进行迭代,但这并不意味着你需要一次性将其全部加载到内存中。 - Rawling
哪里说GroupBy会加载整个数据集? - Maarten
@Maarten: 是的。使用反射器,你会看到它将每个元素放入数组中。在无限序列上使用此代码,您将在获得任何结果之前遇到 OutOfMemoryException。仔细想一下,你会发现除非你知道输入中没有更多的元素进入该组,否则你不能跨组得到平均值 - 而你无法知道这一点,直到输入被耗尽,并且您需要在其他组中存储所有元素的地方。 - Rawling
@Maarten(我想撤回上一个“好好想想”点,显然从您的第一条评论中可以看出您已经了解了这一点。) - Rawling

1

像这样的代码应该可以得到您想要的结果。它将把列表中的列表平铺成一个单一的List<CustomObject>,然后按X值分组并平均Y值,最终得到一个匿名类型的IEnumerable,其中包含XY属性。您可以更改select new {} ...以调用CustomObject的构造函数,然后您将获得一个IEnumerable<CustomObject>

var myComplexObject = //your IEnumerable<IEnumerable<CustomObject>>
var result = from firstList in myComplexObject
        from secondList in firstList
        group secondList by secondList.X into grp
        select new {X = grp.Key, Y = (int)grp.Average(p=>p.Y)};

1

如果您不介意固定 外部 枚举器,以下的 LINQy 方法将推迟执行 内部 枚举器。

IEnumerable<V> AggregateAcross<T, U, V>(
            IEnumerable<IEnumerable<T>> input,
            Func<T, U> select,
            Func<IEnumerable<U>, V> aggregate)
    {
        var enumerators = input.Select(ie => ie.GetEnumerator()).ToArray();
        while (enumerators.All(e => e.MoveNext()))
        {
            yield return aggregate(enumerators.Select(e => select(e.Current)));
        }
    }

例如,称为e.g.

foreach (var avg in AggregateAcross(
                     input,
                     pair => pair.y,
                     e => e.Average(y => y)))
{
    Console.WriteLine(avg);
}

请注意,只要内部枚举器之一耗尽元素,它就会停止。此外,在完成后需要处理所有枚举器的释放。请参考this answer以获取更多想法。
(还要注意,这完全忽略了x值。由于您的所有输入都是有序的,并且您所需的输出也是有序的,因此x值不会添加任何内容。)

-1

我没有测试过,但我认为这应该可以工作。

public void Test() {
    IEnumerable<IEnumerable<CustomObject>> data = ...;
    var result = data
        .SelectMany(x => x)
        .GroupBy(
            item => item.x,
            (key, r) => new { x = key, data = r.Select(z => z.y) }
        )
        .Select(x => new CustomObject { x = x.x, y = (int)x.data.Average() })
        .ToList();
}

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