3

So I have a kindda odd scenario that I cannot get my head arround:

I have a outerlist which contains multiple innerlists.
Each innerlist contains multiple DataPoint objects.
Every innerlist contains the same amount of DataPoint objects.
A datapoint has a field called Value which is a double - e.g.:

  • Outerlist
    • Innerlist
      • DataPoint(value=2)
      • DataPoint(value=2)
    • Innerlist
      • DataPoint(value=4)
      • DataPoint(value=5)
    • Innerlist
      • DataPoint(value=3)
      • DataPoint(value=5)

So what I wanna do is to "combine" the innerlists into one List where the value of each DataPoint should be the average of the old values, based on its index in the list - in this case:

  • List
    • DataPoint(3) - ((2+4+3)/3) = 3
    • DataPoint(4) - ((2+5+5)/3) = 4

Is there any neat linq expression that could perform this kind of stuff (Im betting there are :))?


I ended up using the following solution:

var averages = Enumerable.Range(0, outer.First().Count()).Select(i => new DataPoint(outer.Select(l => l[i]).Average(x=>x.Value)));

which outputs IEnumerable, now with average valus - yay!

  • If you have a recent edition of resharper, it is worth typing out the for loops, and see if it suggests a linq based refactoring. Ok, i am half joking, but it might actually work. –  Aug 11 '11 at 20:31
  • Haha, Resharper is good, but I doubt it's that good. :) I think you're going to have to do the for loops anyway, though. LINQ is typically best for position-agnostic operations. – drharris Aug 11 '11 at 20:33
  • You may want to look at this question: http://stackoverflow.com/questions/6669121/elegant-averaging-of-arrays-from-different-instances-of-an-object-in-c – dlev Aug 11 '11 at 20:34
  • If I understand your question correctly, you won't to do something along the line of a pivot in linq. May be interested in this question: [Pivot data using LINQ](http://stackoverflow.com/questions/963491/pivot-data-using-linq). – Roman Aug 11 '11 at 20:34

4 Answers4

3

Start with an extension method to take a slice of the inner lists:

public static IEnumerable<double> Slice(
    this IEnumerable<List<DataPoint>> innerLists,
    int index) {
        foreach(var innerList in innerLists) {
            yield return innerList.DataPoints[index].Value;
        }
    }
}

Then:

var averages = Enumerable.Range(0, count)
                         .Select(index => outerList.InnerLists.Slice(index))
                         .Select(slice => slice.Average());

I am assuming a hierarchy like this:

class DataPoint { public double Value { get; set; } }

class InnerList { public List<DataPoint> DataPoints { get; set; } }

class OuterList { public IEnumerable<InnerList> InnerLists { get; set; } }

but the above idea is obviously adaptable to however exactly you have structured your data.

jason
  • 236,483
  • 35
  • 423
  • 525
2

The key here would be to group the inner list values by their index. Something like this would work:

// Test data
var list = new List<List<double>>() { 
    new List<double> { 2, 2 },
    new List<double> { 4, 5 },
    new List<double> { 3, 5 }
};

// Created groups based on index
var lookup = from inner in list 
             from value in inner.Select((n,idx) => new { n,idx })
             group value by value.idx;

// Calculate averages    
var averagesBasedOnIndex = lookup.Select(g => g.Average(x => x.n)).ToList();
driis
  • 161,458
  • 45
  • 265
  • 341
1

I'm on my iPad, so I havent compiled, but the following should work:

Enumerable.Range(0,outer.First().Length).
   Select(i => outer.Select(l => l[i]).Average());
  • Almost... the problem is that you are discriminating which inner list you are tabulating, not which datapoint. Additionally, average is going to need a lambda function, and you will need a selectmany in there: Enumerable.Range(0, items.First().InnerLists.First().DataPoints.Count()). Select(i => items.SelectMany(x=>x.InnerLists).Average(x=>x.DataPoints[i].Value)); – Michael Hays Aug 11 '11 at 21:15
  • 1
    Actually Jan was almost spot on besides the missing lambda in the average. I ended up using the following: var averages = Enumerable.Range(0, outer.First().Count()).Select(i => new DataPoint(outer.Select(l => l[i]).Average(x=>x.Value))); – Lars Tabro Sørensen Aug 11 '11 at 21:22
0

This is all you need:

var outer = new List<List<List<double>>>();
var l1 = outer.Select(
  x => new List<double> { 
         x.Average(y => y[0]), 
         x.Average(y => y[1]), 
         x.Average(y => y[2])
       });

With a data structure like yours, it would be:

var list = items.Select(x => new List<DataPoint> (
            Enumerable.Range(0,3).Select(z => 
                new DataPoint {
                   Value = x.InnerLists.Average(y => y.DataPoints[z].Value)})
           ));

(Thank you to whomever suggested the Enumerable.Range)

Michael Hays
  • 6,878
  • 2
  • 21
  • 17
  • Problem is the fact that I dont know the number of datapoints in the inner list, since these may vary between different outerlists. I ended up using Enumerable.Range in order to handle the dynamic number of datapoints. – Lars Tabro Sørensen Aug 11 '11 at 21:01