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();

?

asked Jul 12, 2012 at 15:06
\$\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)
answered Jul 12, 2012 at 19:21
\$\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()
answered Jul 12, 2012 at 16:43
\$\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.

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

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.