我有一个2D点类如下:
class Point
{
public int id_;
public float x_, y_;
public Point(int i, float x, float y)
{
id_ = i;
x_ = x;
y_ = y;
}
public float Distance(Point otherPoint)
{
return (float)Math.Sqrt(Math.Pow(x_ - otherPoint.x_, 2) + Math.Pow(y_ - otherPoint.y_, 2));
}
}
在我的主要代码中,我列出了这些要点。我被提出了一个新观点。我想在我的列表中找到与新点相距最短距离的点,如果它满足最小阈值标准。
我最初通过使用minValue(初始化为1e6)和minID直接编写它,遍历列表以找到最小值。在遍历之外,我检查了这个最小值是否小于阈值。这很有用。
但我想知道是否有更好/更清洁的方式来实现它,我最终得到了这个:
var list = new List<Point>();
list.Add(new Point(0, 10.0f, 1.0f));
list.Add(new Point(1, 1.0f, 0.0f));
list.Add(new Point(2, 0.0f, 0.0f));
var p = new Point(3, 0.6f, 0.0f);
var subList = list.Select((item, index) => new { item, index })
.Where(x => (x.item.distance(p) <= 1.0))
.Select(x => x.item).ToList();
Point minPoint = subList[Enumerable.Range(0, subList.Count).Aggregate((a, b) => (subList[a].Distance(p) < subList[b].Distance(p) ? a : b))];
Console.WriteLine(minPoint.id_);
有更好的方法吗?
答案 0 :(得分:3)
我宁愿使用以下内容进行O(N)距离计算和O(N)比较:
var closest = list.Select(item => new { item, distance = item.Distance(p) })
.Aggregate((a, b) => a.distance <= b.distance ? a : b);
var closestPt = closest.distance <= 1.0 ? closest.item : null;
答案 1 :(得分:1)
有几件事可以改进:
删除第一个Select
语句,因为它没有用?你没有做任何事情;这允许您删除第二个Select
语句。
请勿使用ToList
:您无论如何都不想构建此列表;
Math.Pow
是一种用于任意权力的方法,使用x*x
而不是Math.Pow(x,2)
时,使用Points
;
您针对上限提出了一些小错误,Points
而不是Distance
,Distance
而不是Point
;
获取class Point {
public int id_;
public float x_, y_;
public Point(int i, float x, float y) {
id_ = i;
x_ = x;
y_ = y;
}
public float Distance(Point otherPoint) {
float dx = this.x_-otherPoint.x_;
float dy = this.y_-otherPoint.y_;
return (float)Math.Sqrt(dx*dx+dy*dy);
}
}
,而不是绝对最有效的的方法是使用以下语句:
var minPoint = list.Where(x => x.Distance(p) <= 1.0).OrderBy(x => x.Distance(p)).FirstOrDefault();
潜在的查询:
null
如果不存在这样的项目(满足Where
子句),则返回public static class Utils {
public static T MinBy<T,R> (this IEnumerable<T> source, Func<T,R> f) where R : IComparable<R> {
IEnumerator<T> e = source.GetEnumerator();
if(!e.MoveNext()) {
throw new Exception("You need to provide at least one element.");
}
T min = e.Current;
R minf = f(min);
while(e.MoveNext()) {
T x = e.Current;
R xf = f(x);
if(minf.CompareTo(xf) > 0) {
min = x;
minf = xf;
}
}
return min;
}
}
。然而,在大多数情况下,这不是绝对最有效的实施方式。
另一种方法是首先实现扩展方法:
var minPoint = list.Where(x => x.Distance(p) <= 1.0).MinBy(x => x.Distance(p));
然后你可以使用:
csharp
此方法在 O(n)中运行,因此可能是最有效的方法之一。
<强>基准强>
我已经测试了@ipavlu和我自己的两种方法,虽然你给的是小测试集,所以结果不是真正科学有效的,并使用{{1}执行这些交互式shell:
csharp> DateTime dt=DateTime.Now; for(int i = 0; i < 10000000; i++) { var minPoint = list.Where(x => x.Distance(p) <= 1.0).OrderBy(x => x.Distance(p)).FirstOrDefault(); }; DateTime dt2 = DateTime.Now; Console.WriteLine(dt2-dt);
(1,68): warning CS0219: The variable `minPoint' is assigned but its value is never used
00:00:09.3165310
csharp> DateTime dt=DateTime.Now; for(int i = 0; i < 10000000; i++) { var minPoint = list.Where(x => x.Distance(p) <= 1.0).MinBy(x => x.Distance(p)); }; DateTime dt2 = DateTime.Now; Console.WriteLine(dt2-dt);
(1,68): warning CS0219: The variable `minPoint' is assigned but its value is never used
00:00:03.3658400
csharp> DateTime dt=DateTime.Now; for(int i = 0; i < 10000000; i++) { Point closest_to_p = null;float shortest_d = float.MaxValue;list.ForEach(point =>{var d = point.Distance(p);if (d > 1.0f) return;if (closest_to_p == null || shortest_d > d){closest_to_p = point;shortest_d = d;}}); }; DateTime dt2 = DateTime.Now;Console.WriteLine(dt2-dt);
00:00:10.4554550
csharp> DateTime dt=DateTime.Now; for(int i = 0; i < 10000000; i++) { var null_point = new KeyValuePair<Point,float>(null, float.PositiveInfinity);var rslt_point = list.Select(xp =>{var d = xp.Distance(p);return d <= 1.0f ? new KeyValuePair<Point, float>(xp, d) : null_point;}).Aggregate(null_point, (a, b) =>{if (a.Key == null) return b;if (b.Key == null) return a;return a.Value > b.Value ? b : a;}, x => x.Key); }; DateTime dt2 = DateTime.Now; Console.WriteLine(dt2-dt);
(1,146): warning CS0219: The variable `rslt_point' is assigned but its value is never used
00:00:18.5995530
这导致一些无关紧要的结果:
CommuSoft.A 00:00:09.3165310
CommuSoft.B 00:00:03.3658400
ipavlu.A 00:00:10.4554550
ipavlu.B 00:00:18.5995530
此外请注意,这些工作在调试模式下,编译器有时可以找到有用的优化。
答案 2 :(得分:1)
我会对这个问题的两个解决方案有一些想法,这里是原始类,删除了不必要的下划线。通常id是唯一的,所以 readonly 我从@CommuSoft的答案中借用了Distance方法,因为他对这个方法是正确的:
class Point
{
public readonly int id;
public float x;
public float y;
public Point(int id, float x, float y)
{
this.id = id;
this.x = x;
this.y = y;
}
public float Distance(Point p)
{
float dx = this.x - p.x;
float dy = this.y - p.y;
return (float)Math.Sqrt(dx * dx + dy * dy);
}
}
共享部分:
List<Point> list = new List<Point>();
list.Add(new Point(0, 10.0f, 1.0f));
list.Add(new Point(1, 1.0f, 0.0f));
list.Add(new Point(2, 0.0f, 0.0f));
Point p = new Point(3, 0.6f, 0.0f);
下一个解决方案 IpavluVersionA1 在内存/分配的使用效率最高,计算效率最高:
//VersionA1, efficient memory and cpu usage
Point closest_to_p = null;
float shortest_d = float.MaxValue;
//list.ForEach because it is iterating list through for cycle, most effective
list.ForEach(point =>
{
//Distance is computed only ONCE per Point!
var d = point.Distance(p);
if (d > 1.0f) return;
if (closest_to_p == null || shortest_d > d)
{
closest_to_p = point;
shortest_d = d;
}
});
//closest_to_p is cloases point in range with distance 1.0
//or below or is null, then does not exist
下一个是 IpavluVersionA2 ,性能最佳:
//VersionA2, most efficient memory and cpu usage
Point closest_to_p = null;
float shortest_d = float.MaxValue;
int max = list.Count;
for (int i = 0; i < max; ++i)
{
var point = list[i];
var d = point.Distance(p);
if (d > 1.0f) continue;
if (closest_to_p == null || shortest_d > d)
{
closest_to_p = point;
shortest_d = d;
}
}
//closest_to_p is closest point in range with distance 1.0
//or below or is null, then does not exist
使用LINQ方法的另一个解决方案 IpavluVersionB 必须创建新的struct对象以保持Point和距离,但它们很可能是在堆栈上创建的。计算距离仅在ONCE完成,然后重复使用值!
//versionB
var null_point = new KeyValuePair<Point,float>(null, float.PositiveInfinity);
var rslt_point =
list
.Select(xp =>
{
var d = xp.Distance(p);
return d <= 1.0f ? new KeyValuePair<Point, float>(xp, d) : null_point;
})
.Aggregate(null_point, (a, b) =>
{
if (a.Key == null) return b;
if (b.Key == null) return a;
return a.Value > b.Value ? b : a;
}, x => x.Key);
rslt_point
为null或最接近p
的实例。
<强>基准:强>
<强> BechmarkREsults:强>
B [10000000] I [3]:CommuSoft:3521 IpavluA1:371 IpavluA2:195 IpavluB:1587
B [10000000] I [3]:CommuSoft:3466 IpavluA1:371 IpavluA2:194 IpavluB:1583
B [10000000] I [3]:CommuSoft:3463 IpavluA1:370 IpavluA2:194 IpavluB:1583
B [10000000] I [3]:CommuSoft:3465 IpavluA1:370 IpavluA2:194 IpavluB:1582
B [10000000] I [3]:CommuSoft:3471 IpavluA1:372 IpavluA2:196 IpavluB:1583
B 1 I [3000000]:CommuSoft:919 IpavluA1:21 IpavluA2:17 IpavluB:75
B 1 I [3000000]:CommuSoft:947 IpavluA1:21 IpavluA2:17 IpavluB:75
B 1 I [3000000]:CommuSoft:962 IpavluA1:21 IpavluA2:17 IpavluB:75
B 1 I [3000000]:CommuSoft:969 IpavluA1:21 IpavluA2:17 IpavluB:75
B 1 I [3000000]:CommuSoft:961 IpavluA1:21 IpavluA2:17 IpavluB:75