3
\$\begingroup\$

I have an array of points:

 Point[] line; // { Point(1,1), Point(2,3), Point(1,1) }

Assuming Point has the relevant operators, what's the most linq-y way of computing the length of this line?

My best effort so far is:

float length = Enumerable.Range(0, line.Length-2)
    .Select(pointIndex => (line[pointIndex+1] - line[pointIndex]).Magnitude)
    .Sum();

Seems like there's a lot of subscripting in there though.

I would think there's a way to do something like this pseudocode:

float length = line.PairwiseMap(p1, p2 => (p2 - p1).Magnitude).Sum();

?

\$\endgroup\$

3 Answers 3

4
\$\begingroup\$

Another possible answer (first time I've ever suggested two answers) is to keep your existing approach, but modified just a little bit by using .Select()'s under-appreciated little brother that gives you access to an index variable:

float length = line.Skip(1)
    .Select((point, index) => (point - line[index]).Magnitude)
    .Sum();

Heck, speaking of overloads. You can also keep your existing approach but use .Sum()'s overload:

Enumerable.Range(0, line.Length - 1)
    .Sum(pointIndex => (line[pointIndex + 1] - line[pointIndex]).Magnitude)
\$\endgroup\$
2
\$\begingroup\$

You could use linq Zip instead of PairwiseMap. There is a drawback, you have to create two new enumerations with points:

//  collection of point/tuples
var points = new[] { 
    Tuple.Create(1f, 1f), 
    Tuple.Create(2f, 3f), 
    Tuple.Create(5f, 5f), 
    Tuple.Create(0f, 0f) 
};

// your implementation
Enumerable
    .Range(0, points.Length - 1)
    .Select(index => Math.Sqrt(Math.Pow(points[index].Item1 - points[index + 1].Item1, 2) + Math.Pow(points[index].Item2 - points[index + 1].Item2, 2)))
    .Sum();

var left = points.Take(points.Length - 1); // starting points of verctors
var right = points.Skip(1); // ending points of verctors

// zip and calculate magnitude
left
     .Zip(right, (x, y) => 
         Math.Sqrt(Math.Pow(x.Item1 - y.Item1, 2f) + Math.Pow(x.Item2 - y.Item2, 2f)))
.Sum()
\$\endgroup\$
2
\$\begingroup\$

Use .Aggregate() which is a way to fold an operation over a list with an accumulator

if (line.Any())
    length = line.Skip(1)
            .Aggregate(
                new { Point = line.First(), Length = 0d }, //seed
                (accum, point) => new { Point = point, Length = accum.Length + (point - accum.Point).Magnitude })
            .Length
else
    length = 0;

In this case, our accumulator is a Tuple<Point, double>, where the double is the length so far.

One advantage to this approach is that your points collection doesn't need to be an array or even have random access, just be an IEnumerable<Point>. OTOH, .Aggregate() can be clunky, and you may prefer the readability of @akim's answer.

\$\endgroup\$
1
  • \$\begingroup\$ You're right, it's almost too slick. Very nice though! \$\endgroup\$
    – tenpn
    Commented Jul 24, 2012 at 8:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.