Omri Btian
Omri Btian

Reputation: 6547

group items by range of values using linq (IGrouping)

Say I have a list of Person class

public class Person
{
    public int Age { get; set; }
    public string Name { get; set; }
}
  1. How can I group by dynamic ranges? (For example starting from the youngest person I would like to group by ranges of 5 so if the youngest person is 12 the groups would be 12-17, 18-23 ....)

  2. How can I determine the Key of IGrouping interface? (Set the Key of each group to be the ages average in that group for example)

Upvotes: 1

Views: 1467

Answers (2)

Martin Liversage
Martin Liversage

Reputation: 106956

To get the key to group by you can create a function:

String GetAgeInterval(Int32 age, Int32 minimumAge, Int32 intervalSize) {
  var group = (age - minimumAge)/intervalSize;
  var startAge = group*intervalSize + minimumAge;
  var endAge = startAge + intervalSize - 1;
  return String.Format("{0}-{1}", startAge, endAge);
}

Assuming that the minimum age is 12 and the interval size is 5 then for ages between 12 and 16 (inclusive) the function will return the string 12-16, for ages between 17 and 21 (inclusive) the function will return the string 17-21 etc. Or you can use an interval size of 6 to get the intervals 12-17, 18-23 etc.

You can then create the groups:

var minimumAge = persons.Min(person => person.Age);
var personsByAgeIntervals = persons
  .GroupBy(person => GetAgeInterval(person.Age, minimumAge, 5));

To get the average age in each group you can do something like this:

var groups = personsByAgeIntervals.Select(
  grouping => new {
    AgeInterval = grouping.Key,
    AverageAge = grouping.Average(person => person.Age),
    Persons = grouping.ToList()
  }
);

This will create a sequence of groups represented by an anonymous type with properties AgeInterval, AverageAge and Persons.

Upvotes: 3

AWinkle
AWinkle

Reputation: 673

Using Linq but not IGrouping (I've never used this interface, so I didn't think helping you would be the best time to start). I added a configuration class to set the min/max age as well as a basic descriptor.

    public class GroupConfiguration {
        public int MinimumAge { get; set; }
        public int MaximumAge { get; set; }
        public string Description { get; set; }
    }

I created a list of Person (people) and populated it with a few sample records.

        List<Person> people = new List<Person>() {
            new Person(12, "Joe"),
            new Person(17, "Bob"),
            new Person(21, "Sally"),
            new Person(15, "Jim")
        };

Then I created a list of GroupConfiguration (configurations) and populated it with 3 logical-for-me records.

        List<GroupConfiguration> configurations = new List<GroupConfiguration>() {
            new GroupConfiguration() {MinimumAge = 0, MaximumAge=17, Description="Minors"},
            new GroupConfiguration() {MinimumAge = 18, MaximumAge=20, Description="Adult-No Alcohol"},
            new GroupConfiguration() {MinimumAge = 21, MaximumAge=999, Description="Adult-Alcohol"},
        };

I then load them to a dictionary, to maintain the relationship between the configuration and the results that match that configration. This uses Linq to find the records from people that match MinimumAge <= age <= MaximumAge. This would allow someone to be placed in multiple results, if there were MinimumAge and Maximum age overlaps.

        Dictionary<GroupConfiguration, IEnumerable<Person>> groupingDictionary = configurations.ToDictionary(groupConfiguration => groupConfiguration, groupConfiguration
            => people.Where(x => x.Age >= groupConfiguration.MinimumAge && x.Age <= groupConfiguration.MaximumAge));

Throwing this in a console program, I validated that 3 people exist in the Minors group, 0 in the Adult-No Alcohol group, and 1 in the Adult-Alcohol group.

        foreach (var kvp in groupingDictionary) {
            Console.WriteLine(kvp.Key.Description + " " + kvp.Value.Count());
        }
        Console.ReadLine();

Upvotes: 1

Related Questions