Signum.Utilities.DictionaryExtensions
Some useful extension methods to deal with Dictionaries.
We will use the same data as in
GroupExtensions for our 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 |
TryGet
In the days of .Net 1.1, before generics came onto the scene, the old
HashTable had the good property of
returning null when a key is not found. Dictionary<K,V> change this so it
throws a KeyNotFoundException when the key is not found, because V could be a value type so null is not an option.
Alternatively, Dictionary exposes
TryGetValue but it uses an out parameter, so it's not very convenient to use when writing code in a functional style.
For this reasons we provide TryGet methods. There are many overloads, TryGetC when V is a class (so null is allowed) and TryGetS when V is a struct (so the output is V? to allow nulls).
public static V TryGetC<K, V>(this IDictionary<K, V> dictionary, K key) where V : class
public static V? TryGetS<K, V>(this IDictionary<K, V> dictionary, K key) where V : struct
public static V? TryGetS<K, V>(this IDictionary<K, V?> dictionary, K key) where V : struct
In the following example we use TryGet
S beacause the V (not K) is a value type:
Dictionary<string, int> colorToSatelitesSum = planets.AgGroupToDictionary(a => a.Color, gr => gr.Sum(a => a.Satellites));
colorToSatelitesSum.TryGetS("Pink");
int? blueSatellites = colorToSatelitesSum.TryGetS("Blue");
Now TryGet returns null even if used over a null Dictionary (instead or throwing NullReferenceException) in order to simplify chaining.
Dictionary<Type, Dictionary<string, PropertyInfo>> propertyCache = types.ToDictionary(t=>t, t=>t.GetProperties().ToDictionary(p=>p.PropertyName));
PropertyInfo pi = propertyCache.TryGetC(typeof(AnimalDN)).TryGetC("Color");
GetOrCreate
When a Dictionary is used for implementing a cache, often the following pattern appears:
- If the value is in the dictionary: Retrieve the value from the dictionary.
- Else: Compute the value and store it in the dictionary.
This common pattern can be expressed easily using GetOrCreate extension method:
public static V GetOrCreate<K, V>(this IDictionary<K, V> dictionary, K key) where V : new()
public static V GetOrCreate<K, V>(this IDictionary<K, V> dictionary, K key, V value)
public static V GetOrCreate<K, V>(this IDictionary<K, V> dictionary, K key, Func<V> generator)
Pedagogic example:
Dictionary<string, string> dic = new Dictionary<string, string>();
dic.GetOrCreate("Simpson", "Bart");
dic.GetOrCreate("Simpson", "Lisa");
dic.GetOrCreate("Skywalker", () => "Luke");
dic.GetOrCreate("Skywalker", () => "Anakin");
dic.GetOrCreate("Dalton");
Practical example:
static Dictionary<Type, string[]> fieldNames = new Dictionary<Type, string[]>();
(...)
static string[] GetFields(Type type)
{
lock(fieldNames)
fieldNames.GetOrCreate(type, ()=>type.GetFields().Select(fi=>fi.Name).ToArray());
}
Note that GetOrCreate does not save you from locking the data structure to avoid concurrency problems. For a lock-free concurrency take a look at
Immutable Data Structures.
GetOrThrow
Throws your own custom error message in a KeyNotFoundException when the key is not found in the dictionary:
public static V GetOrThrow<K, V>(this IDictionary<K, V> dictionary, K key, string menssage)
Example:
Dictionary<string, List<Planet>> dictionary = planets.GroupToDictionary(a => a.Color);
List<Planet> redPlanets = dictionary.GetOrThrow("Red", "No planet with {0} color found");
SelectDictionary
Just a Select method for Dictionaries, taking two mapping funcitons, one for keys and another for values, saving you to deal with KeyValuePairs.
public static Dictionary<K2, V2> SelectDictionary<K1, V1, K2, V2>(this IDictionary<K1, V1> dictionary, Func<K1, K2> mapKey, Func<V1, V2> mapValue)
public static Dictionary<K2, V2> SelectDictionary<K1, V1, K2, V2>(this IDictionary<K1, V1> dictionary, Func<K1, K2> mapKey, Func<K1, V1, V2> mapValue)
Example:
Dictionary<string, List<Planet>> dictionary = planets.GroupToDictionary(a => a.Color);
dictionary.SelectDictionary(k => k[0], v => v.Count).ToConsole();
It's your responsibility to create a mapKey that preserves uniqueness in the data set. i.e. If there were any Green planets this operation will fail because G will conflict with Gray.
JumpDictionary
Joins the Values of a dicionary with the Keys of another (if types match).
public static Dictionary<K, V> JumpDictionary<K, Z, V>(this IDictionary<K, Z> dictionary, Dictionary<Z,V> other)
Example:
Dictionary<string, List<Planet>> dictionary = planets.GroupToDictionary(a => a.Color);
Dictionary<Color, string> dicColors = new Dictionary<Color, string>()
{
{Colors.Gray, "Gray"},
{Colors.Yellow, "Yellow"},
{Colors.Blue, "Blue"},
{Colors.Orange, "Orange"}
};
dicColors.JumpDictionary(dictionary).ToConsole(kvp => "{0} -> {1}".Formato(kvp.Key, kvp.Value.ToString(", ")));
JoinDictionary
Joins two dictionaries using the keys as the join key (should have the same type), returns a new Dictionary and using mixer function to generater the values.
If there are keys in one dictionary and not in the other, the keys will not appear in the final dicionary: Inner Join
public static Dictionary<K, V3> JoinDictionary<K, V1, V2, V3>(this IDictionary<K, V1> dic1, IDictionary<K, V2> dic2, Func<K, V1, V2, V3> mixer)
Example:
Dictionary<string, List<Planet>> dictionary = planets.GroupToDictionary(a => a.Color);
Dictionary<string, Color> dicColors = new Dictionary<string, Color>()
{
{"Gray", Colors.Gray},
{"Yellow", Colors.Yellow},
{"Blue", Colors.Blue},
{"Orange", Colors.Orange}
};
dicColors.JoinDictionary(dictionary, (n, c, list) => new
{
Color = c,
Planets = list.ToString(a => a.Name, ", ")
}).ToConsole();
OutterJoinDictionary
There's also support for OuterJoin with dictionaries. Because of the recurrent problem with value types, there are many (nasty) overloads to deal with nullability:
public static Dictionary<K, V3> OuterJoinDictionaryCC<K, V1, V2, V3>(this IDictionary<K, V1> dic1, IDictionary<K, V2> dic2, Func<K, V1, V2, V3> mixer) where V1 : class where V2 : class
public static Dictionary<K, V3> OuterJoinDictionarySC<K, V1, V2, V3>(this IDictionary<K, V1> dic1, IDictionary<K, V2> dic2, Func<K, V1?, V2, V3> mixer) where V1 : struct where V2 : class
public static Dictionary<K, V3> OuterJoinDictionarySC<K, V1, V2, V3>(this IDictionary<K, V1?> dic1, IDictionary<K, V2> dic2, Func<K, V1?, V2, V3> mixer) where V1 : struct where V2 : class
public static Dictionary<K, V3> OuterJoinDictionaryCS<K, V1, V2, V3>(this IDictionary<K, V1> dic1, IDictionary<K, V2> dic2, Func<K, V1, V2?, V3> mixer) where V1 : class where V2 : struct
public static Dictionary<K, V3> OuterJoinDictionaryCS<K, V1, V2, V3>(this IDictionary<K, V1> dic1, IDictionary<K, V2?> dic2, Func<K, V1, V2?, V3> mixer) where V1 : class where V2 : struct
public static Dictionary<K, V3> OuterJoinDictionarySS<K, V1, V2, V3>(this IDictionary<K, V1> dic1, IDictionary<K, V2> dic2, Func<K, V1?, V2?, V3> mixer) where V1 : struct where V2 : struct
public static Dictionary<K, V3> OuterJoinDictionarySS<K, V1, V2, V3>(this IDictionary<K, V1?> dic1, IDictionary<K, V2?> dic2, Func<K, V1?, V2?, V3> mixer) where V1 : struct where V2 : struct
Example:
Dictionary<string, List<Planet>> dictionary = planets.GroupToDictionary(a => a.Color);
Dictionary<string, Color> dicColors = new Dictionary<string, Color>()
{
{"Gray", Colors.Gray},
{"Yellow", Colors.Yellow},
{"Blue", Colors.Blue},
{"Orange", Colors.Orange}
};
dicColors.OuterJoinDictionarySC(dictionary, (n, c, list) => new
{
Color = c,
Planets = list == null ? null : list.ToString(a => a.Name, ", ")
}).ToConsole();
Notice how Magenta has no planets and Yellow has no color.
AddRange
Adds many values in a dictionary at once. If a key already exists throws an ArgumentException.
There are many overloads.
public static void AddRange<K, V>(this IDictionary<K, V> dictionary, IEnumerable<K> keys, IEnumerable<V> values)
public static void AddRange<K, V>(this IDictionary<K, V> dictionary, IDictionary<K, V> other)
public static void AddRange<K, V, A>(this IDictionary<K, V> dictionary, IEnumerable<A> collection, Func<A, K> getKey, Func<A, V> getValue)
SetRange
Sets many values in a dictionary at once. If a key already exists the value is overridden.
There are many overloads.
public static void SetRange<K, V>(this IDictionary<K, V> dictionary, IEnumerable<K> keys, IEnumerable<V> values)
public static void SetRange<K, V>(this IDictionary<K, V> dictionary, IDictionary<K, V> other)
public static void SetRange<K, V, A>(this IDictionary<K, V> dictionary, IEnumerable<A> collection, Func<A, K> getKey, Func<A, V> getValue)
RemoveRange
Removes many key values entries at once given the collection of keys to remove
public static void RemoveRange<K, V>(this IDictionary<K, V> dictionary, IEnumerable<K> keys)
Union
Unites two dictionaries creating a new one:
public static Dictionary<K, V> Union<K, V>(this IDictionary<K, V> dictionary, IDictionary<K, V> other)
Extract
Moves all the entries in a dictionary that satisfies a condition to a new one, removing them from the original one.
public static Dictionary<K, V> Extract<K, V>(this IDictionary<K, V> dictionary, Func<K, bool> condition)
public static Dictionary<K, V> Extract<K, V>(this IDictionary<K, V> dictionary, Func<K, V, bool> condition)
There's also another overload that extracts one single element, returning the value:
public static V Extract<K, V>(this IDictionary<K, V> dictionary, K key)
Inverse
Creates a new Dictionary using values as keys and keys as values. It's your responsibility to avoid repetitions in the values.
public static Dictionary<V, K> Inverse<K, V>(this IDictionary<K, V> dic)