C# Querying With LINQ Querying the BirdWatcher Data How Much Data Do We Have?

Daniel Breen
Daniel Breen
13,693 Points

Select vs SelectMany When Getting Average

With the following code in mind...

var sightingsCorrect = birds.SelectMany(b => b.Sightings).Count();  // 1817
var sightingsWrong = birds.Select(b => b.Sightings).Count();  // 201
var average = birds.Select(b => b.Sightings.Count()).Average();      // 9.03980099502488

Note that sightingsCorrect uses SelectMany(), while sightingsWrong and average use Select().

Why don't we use SelectMany to get the average? How does Linq bring back the correct average? I would assume that average would be 1 (201 / 201 = 1)

1 Answer

Steven Parker
Steven Parker
140,485 Points

These variable names are a bit confusing, "sightingsCorrect" is actually the total number of sightings, and "sightingsWrong " is just a count of birds. It could be written more simply without the "Select" as:

var sightingsWrong = birds.Count();  // 201

The reason the average works is that the "Count" is applied to the sightings for each bird. So the "Average" totals them up and then divides by the number of counts. You can't use "SelectMany" there because it works on lists, not individual values as the "Count" returns.

Daniel Breen
Daniel Breen
13,693 Points

var sightingsWrong = birds.Select(b => b.Sightings).Count(); is actually not a count of birds. It's a count of unflattened Sightings lists, which happens to be equal to the count of birds. That's what I've been trying to figure out. :) I don't understand how birds.Select(b => b.Sightings.Count()).Average(); is expanding (unflattening?) Sightings without using SelectMany to count each item in Sightings before averaging.

Steven Parker
Steven Parker
140,485 Points

When I said "count of birds", I meant that it "happens to be equal to the count of birds". So it makes sense to use the simpler method and just count birds since you get the same result.

The reason you don't need (and can't use) "SelectMany" with "Average" is because "b.Sightings.Count()" converts each list into a single number, eliminating the need for "flattening". Then those single numbers get averaged.

You _can_use flattening to compute an average, and that is also shown in the video, but you do the math yourself instead of using the "Average" method:

var averageSightings = birds.SelectMany(b => b.Sightings).Count() / birds.Count();  // 9