Signum Framework Logo
"Open framework that encourages convention over configuration, using C# code,
not XML files, to model at the right level of abstraction and achieve deadlines.
...but also has a full Linq provider, and syncs the schema for you!"
Login
RSS

Search

»



Main
Index
Map
Videos
Download
Source code
Tutorials
Forum
FAQ



Image



PoweredBy

Signum.Utilities.GroupExtensions

When doing a Loading process from legacy database, very often poorly normalized, grouping turns out being one of the most useful operations.

There are so many extensions for Grouping over IEnumerable that it's worth taking them out to a different class: GroupExtensions. (since they are extension methods they are uses the same way).

We will use the following data to make the examples:

public class Planet
{
    public string Name;
    public string SunDistance;
    public int RevolutionDays;
    public int RotationDays;
    public string Diameter;
    public int Satellites;
    public string Color; 
}

NameSunDistanceRevolutionDaysRotationDaysDiameterSatellitesColor
MercuryClose881416Small0Gray
VenusClose2255832Small0Yellow
EarthClose36524Small1Blue
MarsClose68725Small2Orange
JupiterMedium438010Big63Orange
SaturnMedium1058510Big48Yellow
UranusFar3066018Medium27Blue
NeptuneFar6022518Medium13Blue

Note: All the IQueryable overloads have been removed in SF 2.0 for simplicity and consistency.

GroupToDictionary

Mixes GroupBy and ToDictionary<K, List<V>> because Dictionaries are richer collections, with more features than IEnumerable<IGrouping<K,V>>.

public static Dictionary<K, List<T>> GroupToDictionary<T, K>(this IEnumerable<T> collection, Func<T, K> keySelector)
{
   return collection
     .GroupBy(keySelector)
     .ToDictionary(g => g.Key, g => g.ToList());
}

public static Dictionary<K, List<V>> GroupToDictionary<T, K, V>(this IEnumerable<T> collection, Func<T, K> keySelector, Func<T, V> valueSelector)

Example:

Dictionary<string,List<Planet>> planetsByColor = planets.GroupToDictionary(a=>a.Color);

//Print results: Console.WriteLine(planetsByColor.ToString(kvp => "{0}: {1}".Formato(kvp.Key, kvp.Value.ToString(", ")), "\r\n"));

//Writes... //Gray: Mercury (Gray) //Yellow: Venus (Yellow), Saturn (Yellow) //Blue: Earth (Blue), Uranus (Blue), Neptune (Blue) //Orange: Mars (Orange), Jupiter (Orange)

Also, on Loading applications, when trying to normalize a given field often it is useful to group by the field and to order the groups descending by number of elements, so you can focus on the most important values of the field.

public static Dictionary<K, List<T>> GroupToDictionaryDescending<T, K>(this IEnumerable<T> collection, Func<T, K> keySelector)
public static Dictionary<K, List<V>> GroupToDictionaryDescending<T, K, V>(this IEnumerable<T> collection, Func<T, K> keySelector, Func<T, V> valueSelector)

When you enumerate a Dictionary the KeyValuePairs are returned in the order they were introduced.

Example:

Dictionary<string,List<Planet>> planetsByColor = planets.GroupToDictionaryDescending(a=>a.Color);

//Print results: Console.WriteLine(planetsByColor.ToString(kvp => "{0}: {1}".Formato(kvp.Key, kvp.Value.ToString(", ")), "\r\n"));

//Writes... //Blue: Earth (Blue), Uranus (Blue), Neptune (Blue) //Yellow: Venus (Yellow), Saturn (Yellow) //Orange: Mars (Orange), Jupiter (Orange) //Gray: Mercury (Gray)

GroupDistinctToDictionary

Sometimes, when normalizing a legacy database, you want to assert that a given column is Unique for all the elements in a collection. GroupDistinctToDictionary throws a nice exception when more than one element appears in a given group.

public static Dictionary<K, T> GroupDistinctToDictionary<T, K>(this IEnumerable<T> collection, Func<T, K> keySelector)
public static Dictionary<K, V> GroupDistinctToDictionary<T, K, V>(this IEnumerable<T> collection, Func<T, K> keySelector, Func<T, V> valueSelector)

Example:

Dictionary<int, Planet> planetByRevolutionDays = planets.GroupDistinctToDictionary(a => a.RevolutionDays);
planetByRevolutionDays.ToConsole();

//Writes: //[88, Mercury (Gray)] //[225, Venus (Yellow)] //[365, Earth (Blue)] //[687, Mars (Orange)] //[4380, Jupiter (Orange)] //[10585, Saturn (Yellow)] //[30660, Uranus (Blue)] //[60225, Neptune (Blue)]

//But Dictionary<int, Planet> planetByRotationDays = planets.GroupDistinctToDictionary(a=>a.RotationDays);

//throws InvalidOperationException("There is more than one element with key: 10");

GroupCount

Shortcut to create a dictionary from Key -> NumberOfElements that contain the key.

public static Dictionary<K, int> GroupCount<T, K>(this IEnumerable<T> collection, Func<T, K> keySelector)

There's also another overload without keySelector that can be used to look for repetitions of a value.

public static Dictionary<T, int> GroupCount<T>(this IEnumerable<T> collection)

Example:

Dictionary<string, int> diameterToCount = planets.GroupCount(a => a.Diameter);
diameterToCount.ToConsole(); 

//Writes: //[Small, 4] //[Big, 2] //[Medium, 2]

AgGroupToDictionary

Finally, AgGroupToDictionary takes a lambda to collapse all the values in a group, using this collapsed value as the value of the dictionary.

public static Dictionary<K, V> AgGroupToDictionary<T, K, V>(this IEnumerable<T> collection, Func<T, K> keySelector, Func<IGrouping<K, T>, V> agregateSelector)

Example:
Dictionary<string, double> colorToAvgRevDays = planets.AgGroupToDictionary(p => p.Color, gr=>gr.Average(p=>p.RevolutionDays));
colorToAvgRevDays.ToConsole(); 

//Writes //[Gray, 88] //[Yellow, 5405] //[Blue, 30416,6666666667] //[Orange, 2533,5]

There's also another overload that orders the groups descending by the number of elements of each one, so you can focus on the groups with more elements.

public static Dictionary<K, V> AgGroupToDictionaryDescending<T, K, V>(this IEnumerable<T> collection, Func<T, K> keySelector, Func<IGrouping<K, T>, V> agregateSelector)

Example:

Dictionary<string, double> colorToAvgRevDays = planets.AgGroupToDictionaryDescending(p => p.Color, gr=>gr.Average(p=>p.RevolutionDays));
colorToAvgRevDays.ToConsole(); 

//Writes //[Blue, 30416,6666666667] (there are 3) //[Yellow, 5405] (there are 2) //[Orange, 2533,5] (there are 2) //[Gray, 88] (just 1)

Creative Commons License Signum Framework Site by Signum Software is licensed under a Creative Commons Attribution 3.0 License.
Powered by ScrewTurn Wiki version 3.0.5.600.