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();
?
3 Answers 3
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)
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()
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.
-
\$\begingroup\$ You're right, it's almost too slick. Very nice though! \$\endgroup\$tenpn– tenpn2012年07月24日 08:36:53 +00:00Commented Jul 24, 2012 at 8:36