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;
}
| Name | SunDistance | RevolutionDays | RotationDays | Diameter | Satellites | Color |
|---|
| Mercury | Close | 88 | 1416 | Small | 0 | Gray |
| Venus | Close | 225 | 5832 | Small | 0 | Yellow |
| Earth | Close | 365 | 24 | Small | 1 | Blue |
| Mars | Close | 687 | 25 | Small | 2 | Orange |
| Jupiter | Medium | 4380 | 10 | Big | 63 | Orange |
| Saturn | Medium | 10585 | 10 | Big | 48 | Yellow |
| Uranus | Far | 30660 | 18 | Medium | 27 | Blue |
| Neptune | Far | 60225 | 18 | Medium | 13 | Blue |
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);
Console.WriteLine(planetsByColor.ToString(kvp => "{0}: {1}".Formato(kvp.Key, kvp.Value.ToString(", ")), "\r\n"));
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);
Console.WriteLine(planetsByColor.ToString(kvp => "{0}: {1}".Formato(kvp.Key, kvp.Value.ToString(", ")), "\r\n"));
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();
Dictionary<int, Planet> planetByRotationDays = planets.GroupDistinctToDictionary(a=>a.RotationDays);
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();
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();
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();