Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

Assume the following definition:

public class DataPoint {
    public DateTime Date { get; private set; }
    public double Value { get; private set; }
    public DataPoint(DateTime date, double value) { Date = date; Value = value; }
}

If var points = ... //a date ordered list of DataPoints

I want to convert points into a list or array that represents the steps between each subsequent value. E.g. [3, 5, 6, 10.5] would go to [3, 2, 1, 4.5] with dates.

Here's what I have:

var steps = (new[] {points.First()})
              .Concat(points
                        .Skip(1)
                        .Zip(points, 
                            (curr, prev) => new DataPoint(curr.Date, curr.Value - prev.Value)))

I think my ruby background might have gotten the best of me as that doesn't seem very readable. Any thoughts on how to make things more approachable?

share|improve this question
    
I would use a foreach loop + a local var –  miniBill Jun 13 at 22:30
add comment

4 Answers 4

up vote 10 down vote accepted

In my opinion, the Concat part is what's throwing it off. There is a lesser utilized Select overload that provides access to the index, which could be used like so (ignoring the DataPoint part to make a minimal example):

double[] points = new double[] { 3, 5, 6, 10.5 };

points.Select ((p, i) => p - (i == 0 ? 0 : points[i - 1]));
share|improve this answer
2  
Phenomenal, the Select overload is nice but the gravy was really in the ternary usage. Thanks! –  TCopple Jun 13 at 17:20
add comment

You might want to consider doing your pairing first, which could then be extracted into an extension method. Not sure about terminology here.

public static class EnumerableExtension
{
    public static IEnumerable<Tuple<T, T>> Pairwise<T>(
        this IEnumerable<T> enumerable,
        T leadingItem)
    {
        new T[] { leadingItem }.Concat(enumerable).Zip(enumerable, Tuple.Create);
    }
}

And then your code would look something like this

var points = GetAllTheDataPoints();
var steps = points.Pairwise(leadingItem: DataPoint.Zero)
    .Select(t => new DataPoint(t.Item2.Date, t.Item2.Value - t.Item1.Value));

Where Datapoint.Zero is just a DataPoint with value 0. If you want to make things even more explicit you could introduce your own Pair<T, T> type rather than using Tuple<T, T> since the property names Item1 and Item2 are kinda meh.

share|improve this answer
1  
Something like this would be preferred over my answer if it would get a lot of reuse in a codebase. Used in one place, I think more people would directly understand something using framework linq methods if it was concise enough. Jon Skeet actually has a PairWise extension method in his MoreLinq library that makes use of Func types instead of Tuple for easier lambda expressions: code.google.com/p/morelinq/source/browse/MoreLinq/… –  Ocelot20 Jun 13 at 19:11
    
+1 for MoreLinq reference –  pmacnaughton Jun 13 at 21:21
add comment

This is my proposed solution:

private static IEnumerable<double> Deltas(IEnumerable<double> sequence)
{
    var prev = 0.0;
    foreach (var item in sequence)
    {
        yield return item - prev;
        prev = item;
    }
}

The advantage of this version over @Ocelot20's is that it doesn't force your collection to implement IList, meaning

  • it's more reusable, and
  • it doesn't force you to have the sequence in memory all at once.
share|improve this answer
add comment

You want to find substraction beetwen i[0] - 0, i[1] - i[0], ... which can be solved by creating new enumeration

    int[] points1 = { 3, 5, 6, 10 }; // points[0], points[1], points[2], points[3]
    int[] points0 = { 0, 3, 5, 6 };  //         0, points[0], points[1], points[2]

To create points0 I need to add int = 0 at start.

    int[] points = { 3, 5, 6, 10 };

    var steps = new int[] { 0 }.Concat(points)
                               .Zip(points, (x1, x2) => x2 - x1);

Overall when I need to use indexes I usually stick to for loop.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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