满足列表中条件的所有元素的最小值

时间:2015-10-12 20:13:27

标签: c# list traversal

我有一个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_);

有更好的方法吗?

3 个答案:

答案 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)

有几件事可以改进:

  1. 删除第一个Select语句,因为它没有用?你没有做任何事情;这允许您删除第二个Select语句。

  2. 请勿使用ToList:您无论如何都不想构建此列表;

  3. Math.Pow是一种用于任意权力的方法,使用x*x而不是Math.Pow(x,2)时,使用Points;

  4. 您针对上限提出了一些小错误,Points而不是DistanceDistance而不是Point;

  5. 获取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的实例。

<强>基准:

  • 必须以发布模式构建,
  • 必须在VisualStudio之外无需调试运行,
  • 测试在两种情况下运行了5次,
  • 场景X:3项,1000万次,所有方法,以毫秒为单位的时间,
  • 场景Y:3mil itmes,1次,所有方法,时间以毫秒为单位,

the code is here

<强> BechmarkREsults:

  • B - 列表上的迭代次数,
  • I - 列表中的项目数,
  • 所有数字,以毫秒为单位,
  • CommuSoft是CommuSoft回答的解决方案,
  • Ivan Stoev提出了匿名类型的解决方案,其行为类似于带有struct的VersionA2,
  • 显然,IpavluVersionA2性能最佳。

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