LINQ Min/Max 和最小化迭代

LINQ Min/Max and minimizing iterations

我正在编写一个函数,它采用一系列 System.Windows.Point 和 returns 一个 ValueTuple,所有点的边界 X 和 Y 值。这是为了确定图形轴的标签。

我正在尝试将执行的列表迭代次数减少到仅一次。经过大量谷歌搜索后,我采用了(阅读:"copied")一种如下所示的方法,我被告知可以做到这一点。但我不确定如何验证这一事实。我想知道是否有人更熟悉 LINQ

  1. 确认下面的函数确实只会迭代列表一次,即使它正在计算 4 个不同的值
  2. 如果是这样,请向我解释这是怎么回事。因为在我看来,正在构造的匿名类型在给定列表上分别调用 "Min" 和 "Max" 两次。为什么这不会导致 4 次迭代?
  3. 也许甚至可以解释我是如何为自己验证发生的迭代次数的,这样以后我就不需要问这样的问题了。我不知道该怎么做。

我的LINQ-Fu还不够强。

谢谢

    /// <summary>
    /// X and Y axis boundaries in the form of a System.ValueTuple.
    /// </summary>
    public (double MinX, double MaxX, double MinY, double MaxY) 
    GetBounds(List<System.Windows.Point> pts)
    {


        // Calculate the bounds with a LINQ statement.  Is this one iteration or many?

        var a = pts.GroupBy(i => 1).Select(
            pp => new
            {
                MinY = pp.Min(p => p.Y),
                MaxY = pp.Max(p => p.Y),
                MinX = pp.Min(p => p.X),
                MaxX = pp.Max(p => p.X)
            }).FirstOrDefault();


        return a != null ? (a.MinX, a.MaxX, a.MinY, a.MaxY) : (0, 0, 0, 0);
    }

Confirm that the below function will indeed only iterate the list once, even though it's calculating 4 different values

否 - 原始列表将有效地迭代 4 次。您正在创建一个 "null" 分组来包装原始集合,以便您可以 "project" 将集合合并为一个对象。由于您在 "grouping" 上调用了 4 个 linq 函数 - 原始列表将被迭代 4 次。它在功能上等同于:

var a = new
        {
            MinY = pts.Min(p => p.Y),
            MaxY = pts.Max(p => p.Y),
            MinX = pts.Min(p => p.X),
            MaxX = pts.Max(p => p.X)
        };

如果这对您来说是个问题,找到边界的惯用方法是使用 foreach 循环并跟踪最小和最大 x和 y 手动坐标。这将是一个相对较短的函数,并将迭代次数减少 75%:

int MinX, MaxX, MinY, MaxY;
MaxX = MaxY = Int.MinValue;
MinX = MinY = Int.MaxValue;
foreach(Point p in pts)
{
    MinX = Math.Min(p.X, MinX);
    MaxX = Math.Max(p.X, MaxX);
    MinY = Math.Min(p.Y, MinY);
    MaxY = Math.Max(p.Y, MaxY);
}
var a = new
    {
        MinY,
        MaxY,
        MinX,
        MaxX
    };

可以使用Aggregate通过lambda循环查找最小值和最大值:

var a = pts.Aggregate(
     new {
        MinX = int.MaxValue,
        MaxX = int.MinValue,
        MinY = int.MaxValue,
        MaxY = int.MinValue
    },
    (acc, p) => new {
        MinX = Math.Min(p.X, acc.MinX);
        MaxX = Math.Max(p.X, acc.MaxX);
        MinY = Math.Min(p.Y, acc.MinY);
        MaxY = Math.Max(p.Y, acc.MaxY);
    });

但是聚合器将为源集合中的每个对象创建一个对象,并为 "initial" 对象创建一个对象。因此列表只会迭代一次,但会创建多个临时对象,增加需要 GC 的内存量。

您在那里使用的方法对输入值进行至少五次迭代(一次到 "group" 它们,每个 min/max 一次)并且是一种非常奇怪的方式你在做什么。

当您想获取一组值并将它们压缩成一个值时,首选是 .Aggregate(在其他语言中也称为 reducefold语言)。

对于你的情况,你可以这样做。它应该只迭代一次你的集合:

public static (double minX, double maxX, double minY, double maxY) 
GetBounds(List<Point> pts)
{
    return pts.Aggregate(
        (Int32.MaxValue, Int32.MinValue, Integer.MaxValue, Int32.MinValue),
        (acc, point) => 
        (
            Math.Min(point.X, acc.Item1),
            Math.Max(point.X, acc.Item2),
            Math.Min(point.Y, acc.Item3),
            Math.Max(point.Y, acc.Item4)
        ));
}