LINQ Min/Max 和最小化迭代
LINQ Min/Max and minimizing iterations
我正在编写一个函数,它采用一系列 System.Windows.Point 和 returns 一个 ValueTuple,所有点的边界 X 和 Y 值。这是为了确定图形轴的标签。
我正在尝试将执行的列表迭代次数减少到仅一次。经过大量谷歌搜索后,我采用了(阅读:"copied")一种如下所示的方法,我被告知可以做到这一点。但我不确定如何验证这一事实。我想知道是否有人更熟悉 LINQ
- 确认下面的函数确实只会迭代列表一次,即使它正在计算 4 个不同的值
- 如果是这样,请向我解释这是怎么回事。因为在我看来,正在构造的匿名类型在给定列表上分别调用 "Min" 和 "Max" 两次。为什么这不会导致 4 次迭代?
- 也许甚至可以解释我是如何为自己验证发生的迭代次数的,这样以后我就不需要问这样的问题了。我不知道该怎么做。
我的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
(在其他语言中也称为 reduce
或 fold
语言)。
对于你的情况,你可以这样做。它应该只迭代一次你的集合:
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)
));
}
我正在编写一个函数,它采用一系列 System.Windows.Point 和 returns 一个 ValueTuple,所有点的边界 X 和 Y 值。这是为了确定图形轴的标签。
我正在尝试将执行的列表迭代次数减少到仅一次。经过大量谷歌搜索后,我采用了(阅读:"copied")一种如下所示的方法,我被告知可以做到这一点。但我不确定如何验证这一事实。我想知道是否有人更熟悉 LINQ
- 确认下面的函数确实只会迭代列表一次,即使它正在计算 4 个不同的值
- 如果是这样,请向我解释这是怎么回事。因为在我看来,正在构造的匿名类型在给定列表上分别调用 "Min" 和 "Max" 两次。为什么这不会导致 4 次迭代?
- 也许甚至可以解释我是如何为自己验证发生的迭代次数的,这样以后我就不需要问这样的问题了。我不知道该怎么做。
我的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
(在其他语言中也称为 reduce
或 fold
语言)。
对于你的情况,你可以这样做。它应该只迭代一次你的集合:
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)
));
}