9

I have a list of points (List)

  • 7,43
  • 7,42
  • 6,42
  • 5,42
  • 6,43
  • 5,43

I want to use linq expression to get the point closest to 0,0. For example - for this list I expect 5,42 value.

How to find point closest to 0,0 point with LINQ?

4 Answers4

17

The following finds the point with the lowest L^2 norm (most common definition of "distance" in two dimensions) without performing an expensive sort of the whole list:

var closestToOrigin = points
    .Select(p => new { Point = p, Distance2 = p.X * p.X + p.Y * p.Y })
    .Aggregate((p1, p2) => p1.Distance2 < p2.Distance2 ? p1 : p2)
    .Point;
Rawling
  • 45,907
  • 6
  • 80
  • 115
  • If we care about performance then your solution is good, but you can see the better one in my updated post. – Hamlet Hakobyan Feb 26 '13 at 15:58
  • @Hamlet What makes you say yours is better? I see some tradeoffs (anonymous object creation vs a second calculation of distance) but the meat of it is basically a clone of this answer. – Rawling Feb 27 '13 at 07:48
4

Try this:

List<Point> points = new List<Point>();
// populate list
var p = points.OrderBy(p => p.X * p.X + p.Y * p.Y).First();

or more fast solution:

var p = points.Aggregate(
            (minPoint, next) =>
                 (minPoint.X * minPoint.X + minPoint.Y * minPoint.Y)
                 < (next.X * next.X + next.Y * next.Y) ? minPoint : next);
Hamlet Hakobyan
  • 31,621
  • 6
  • 49
  • 65
  • 1
    Doing a whole sort is expensive when you only want the minimum. (If only Microsoft had provided an overload for `First` that took an `IOrderedEnumerable`, then it wouldn't matter!) – Rawling Feb 26 '13 at 15:08
  • 1
    Actually - ordering by squared distance is just fine :). There is no need for the sqrt. Also, as @Rawling points out - the Enumerable.Min extension method (http://msdn.microsoft.com/en-us/library/bb359972.aspx) will be a little more efficient than a sort. – Ani Feb 26 '13 at 15:08
3

Rawling's solution is definitely shorter, but here's an alternative

// project every element to get a map between it and the square of the distance
var map = pointsList                                            
    .Select(p => new { Point = p, Distance = p.x * p.x + p.y * p.y });

var closestPoint = map // get the list of points with the min distance
    .Where(m => m.Distance == map.Min(t => t.Distance)) 
    .First() // get the first item in that list (guaranteed to exist)
    .Point; // take the point

In case you need to find all the elements that have the shortest distance to 0,0, simply remove First and do a Select(p => p.Point) to get the points (as opposed to the mapping).

vlad
  • 4,610
  • 1
  • 28
  • 35
3

As an alternative approach, you might consider adding to your standard libraries an implementation of IEnumerable.MinBy() and IEnumerable.MaxBy().

If you have that available, the code becomes simply:

var result = points.MinBy( p => p.X*p.X + p.Y*p.Y );

Jon Skeet has provided a good implementation of MinBy and MaxBy.

He talks about it here: How to use LINQ to select object with minimum or maximum property value

The links from there are out of date though; the latest version is here:

http://code.google.com/p/morelinq/source/browse/MoreLinq/MinBy.cs

http://code.google.com/p/morelinq/source/browse/MoreLinq/MaxBy.cs

Here's a full sample. Clearly, this is a sledgehammer to crack a nut, BUT I think these methods are useful enough to include in your standard libraries:

using System;
using System.Collections.Generic;
using System.Drawing;

namespace Demo
{
    public static class EnumerableExt
    {
        public static TSource MinBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector, IComparer<TKey> comparer)
        {
            using (IEnumerator<TSource> sourceIterator = source.GetEnumerator())
            {
                if (!sourceIterator.MoveNext())
                {
                    throw new InvalidOperationException("Sequence was empty");
                }

                TSource min = sourceIterator.Current;
                TKey minKey = selector(min);

                while (sourceIterator.MoveNext())
                {
                    TSource candidate = sourceIterator.Current;
                    TKey candidateProjected = selector(candidate);

                    if (comparer.Compare(candidateProjected, minKey) < 0)
                    {
                        min    = candidate;
                        minKey = candidateProjected;
                    }
                }

                return min;
            }
        }

        public static TSource MinBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector)
        {
            return source.MinBy(selector, Comparer<TKey>.Default);
        }
    }

    public static class Program
    {
        static void Main(string[] args)
        {
            List<Point> points = new List<Point>
            {
                new Point(7, 43),
                new Point(7, 42),
                new Point(6, 42),
                new Point(5, 42),
                new Point(6, 43),
                new Point(5, 43)
            };

            var result = points.MinBy( p => p.X*p.X + p.Y*p.Y );

            Console.WriteLine(result);
        }
    }
}
Community
  • 1
  • 1
Matthew Watson
  • 90,570
  • 7
  • 128
  • 228