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

DictionaryExtensions static class

RSS
Modified on 2010/01/02 08:16 by olmo Paths: Documentation Categorized as SignumUtilities

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; 
}

NameSunDistanceRevolutionDaysRotationDaysDiameterSatellitesColor
MercuryClose881416Small0Gray
VenusClose2255832Small0Yellow
EarthClose36524Small1Blue
MarsClose68725Small2Orange
JupiterMedium438010Big63Orange
SaturnMedium1058510Big48Yellow
UranusFar3066018Medium27Blue
NeptuneFar6022518Medium13Blue

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 TryGetS 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");
//Return: null
int? blueSatellites = colorToSatelitesSum.TryGetS("Blue"); 
//Return: 41

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"); //Returns null even if AnimalDN is not in the initial dictionary

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:

//If key is not found, it's inserted with new V() as the Value
public static V GetOrCreate<K, V>(this IDictionary<K, V> dictionary, K key) where V : new()

//If key is not found, it's inserted with value as the Value public static V GetOrCreate<K, V>(this IDictionary<K, V> dictionary, K key, V value)

//If key is not found, it's inserted with generator() as the 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>();

//Adds the pair and return "Bart" dic.GetOrCreate("Simpson", "Bart"); //Returns "Bart" dic.GetOrCreate("Simpson", "Lisa");

//Adds the pair and returns "Like" dic.GetOrCreate("Skywalker", () => "Luke"); dic.GetOrCreate("Skywalker", () => "Anakin");

//DOES NOT COMPILE because there's no parameterless constructor for string 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:

//Message could contain a '{0}' to insert the key value
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"); 

//throws new KeyNotFoundException("No planet with Red color found") //because Mars is orange in the table :)

SelectDictionary

Just a Select method for Dictionaries, taking two mapping funcitons, one for keys and another for values, saving you to deal with KeyValuePairs.

//Overload that takes mapKey: k=>k'  mapValue: v=>v'
public static Dictionary<K2, V2> SelectDictionary<K1, V1, K2, V2>(this IDictionary<K1, V1> dictionary, Func<K1, K2> mapKey, Func<V1, V2> mapValue)

//Overload that takes mapKey: k=>k' mapValue: (k,v)=>v' 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();

//Writes: //[G, 1] //[Y, 2] //[B, 3] //[O, 2]

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(", ")));

//Writes: //#FF808080 -> Mercury (Gray) //#FFFFFF00 -> Venus (Yellow), Saturn (Yellow) //#FF0000FF -> Earth (Blue), Uranus (Blue), Neptune (Blue) //#FFFFA500 -> Mars (Orange), Jupiter (Orange)

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();

//Writes: //[Gray, { Color = #FF808080, Planets = Mercury }] //[Yellow, { Color = #FFFFFF00, Planets = Venus, Saturn }] //[Blue, { Color = #FF0000FF, Planets = Earth, Uranus, Neptune }] //[Orange, { Color = #FFFFA500, Planets = Mars, Jupiter }]

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();

//Writes: //[Gray, { Color = #FF808080, Planets = Mercury }] //[Blue, { Color = #FF0000FF, Planets = Earth, Uranus, Neptune }] //[Orange, { Color = #FFFFA500, Planets = Mars, Jupiter }] //[Magenta, { Color = #FFFFA500, Planets = }] //[Yellow, { Color = , Planets = Venus, Saturn }]

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)
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.