简体   繁体   English

LINQ最小/最大和最小化迭代

[英]LINQ Min/Max and minimizing iterations

I'm writing a function that takes a series of System.Windows.Point and returns a ValueTuple with the boundary X and Y values for all the points. 我正在编写一个函数,该函数需要一系列System.Windows.Point并返回带有所有点的边界X和Y值的ValueTuple。 This is intended to determine the labels of graph axes. 这旨在确定图形轴的标签。

I'm attempting to minimize the number of iterations of the list I perform to just one. 我试图将执行的列表的迭代次数减至最少。 After a good deal of googling, I've adapted (read: "copied") an approach like below which I am told will do just that. 经过大量的谷歌搜索,我已经采用了一种类似下面的方法(被告知:“已复制”),我被告知可以做到这一点。 But I'm not sure how to verify that fact. 但是我不确定如何验证这一事实。 I'm wondering if someone more familiar with LINQ can 我想知道是否更熟悉LINQ的人可以

  1. Confirm that the below function will indeed only iterate the list once, even though it's calculating 4 different values 确认以下函数确实只对列表进行一次迭代,即使它正在计算4个不同的值
  2. If so, explain to me how this is. 如果是这样,请向我解释这是怎么回事。 Because it looks to me like the anonymous type being constructed calls "Min" and "Max"on the given list two times for each. 因为在我看来,正在构造的匿名类型在给定列表中分别两次调用“ Min”和“ Max”。 Why doesn't that result in 4 iterations? 为什么没有四次迭代呢?
  3. Perhaps even explain how I might have gone about verifying for myself the number of iterations that occurred so that in the future I won't need to ask such questions. 也许甚至可以解释一下我如何亲自验证发生的迭代次数,以便将来不再需要提出这样的问题。 I don't see how to do to that. 我不知道该怎么做。

My LINQ-Fu is not yet strong. 我的LINQ-Fu还不牢固。

Thanks 谢谢

    /// <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个不同的值

No - the original list will effectively be iterated 4 times. 否-原始清单将有效地重复4次。 You are creating a "null" grouping that will wrap the original collection so you can "project" the collection to a single object. 您正在创建将包装原始集合的“空”分组,以便可以将集合“投影”到单个对象。 Since you call 4 linq functions on the "grouping" - the original list will be iterated 4 times. 由于您在“分组”上调用了4个linq函数-原始列表将被迭代4次。 It's functionally equivalent to: 它在功能上等同于:

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

If that is a problem for you , the idiomatic way to find the bounds would be to use a foreach loop and keep track of the min and max x and y coordinates manually. 如果这对您来说是个问题 ,查找边界的惯用方式是使用foreach循环并手动跟踪最小和最大x和y坐标。 It would be a relatively short function, and would reduce the number of iterations by 75%: 这将是一个相对简短的函数,并将迭代次数减少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
    };

You could use Aggregate to loop find the mins and maxes with a lambda: 可以使用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);
    });

But the aggregator will create an object for each object in the source collection, plus one for the "initial" object. 但是聚合器将为源集合中的每个对象创建一个对象,再为“初始”对象创建一个对象。 So the list will only be iterated once but multiple temporary objects will be created, increasing the amount of memory that needs to be GC'ed. 因此,列表只会被迭代一次,但是会创建多个临时对象,从而增加了需要进行GC处理的内存量。

The approach you're using there iterates at least five times over the input values (one to "group" them, and once for each min/max) and is an extremely strange way to go about what you're doing. 您在此处使用的方法至少对输入值进行五次迭代(一次将它们“分组”,然后对每个最小值/最大值进行一次迭代),这是进行操作的一种极其奇怪的方法。

When you want to take a collection of values and condense them down into one value, the go-to choice is .Aggregate (also known as reduce or fold in other languages). 当您要收集一组值并将其压缩为一个值时,首选方法是.Aggregate (在其他语言中也称为reducefold )。

In your case, you can do that like this. 在您的情况下,您可以这样做。 It should iterate over your collection only once: 它应该只对您的集合进行一次迭代:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM