{"id":47,"date":"2024-02-13T07:33:53","date_gmt":"2024-02-13T15:33:53","guid":{"rendered":"https:\/\/denispavlov.net\/Blog\/?p=47"},"modified":"2024-05-28T12:09:18","modified_gmt":"2024-05-28T19:09:18","slug":"multi-value-dictionary","status":"publish","type":"post","link":"https:\/\/denispavlov.net\/Blog\/2024\/02\/13\/multi-value-dictionary\/","title":{"rendered":"Multi Value Dictionary"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">I recently was asked to implement a dictionary-like data structure that can store multiple values for a given key. The interface I was given is as follows.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>    public interface IMultiValueDictionary&lt;K, V&gt; : IEnumerable&lt;KeyValuePair&lt;K, V&gt;&gt;\n    {\n        bool Add(K key, V value);\n        IEnumerable&lt;V&gt; Get(K key);\n        IEnumerable&lt;V&gt; GetOrDefault(K key);\n        void Remove(K key, V value);\n        void Clear(K key);\n        IEnumerable&lt;KeyValuePair&lt;K, V&gt;&gt; Flatten();\n    }<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Since the precondition was that the values are unique for any given key, I decided to use a <code>Dictionary<\/code> where key is of type <code>K<\/code> and value is a <code>HashSet<\/code> of type <code>V<\/code>, as follows.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>private readonly Dictionary&lt;K, HashSet&lt;V&gt;&gt; _data = &#91;];<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s go down the list and implement all the methods.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public bool Add(K key, V value)\n{\n    if (_data.TryGetValue(key, out var values))\n    {\n        return values.Add(value);\n    }\n    else\n    {\n        _data.Add(key, &#91;value]);\n        return true;\n    }\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The add key\/value method is pretty straightforward. If the values for a given key exist, attempt to add new value to that hash set, which returns a boolean indicating success, otherwise add the key with a new hash set containing only the new value.<br><strong>Time Complexity: O(1) or O(n)<\/strong> if underlying array runs out of empty spots and larger array has to be reallocated (Dictionary is implemented using an array under the hood, and HashSet is implemented using a Dictionary where key and value are same).<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public IEnumerable&lt;V&gt; Get(K key)\n{\n    if (!_data.TryGetValue(key, out var values))\n        throw new KeyNotFoundException();\n\n    return values;\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The get by key method should throw an exception if the key doesn&#8217;t exist, otherwise return the values at key.<br><strong>Time Complexity: O(1)<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public IEnumerable&lt;V&gt; GetOrDefault(K key) =&gt; _data.TryGetValue(key, out var values) ? values : &#91;];<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The get or default is a safe version of the get method, which will never throw an exception and is useful for method-chaining.<br><strong>Time Complexity: O(1)<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public void Remove(K key, V value)\n{\n    if (!_data.TryGetValue(key, out var values))\n        throw new KeyNotFoundException();\n\n    values.Remove(value);\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The remove key\/value method will throw an exception if the key doesn&#8217;t exist, otherwise will try to remove the value.<br><strong>Time Complexity: O(1)<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public void Clear(K key) =&gt; _data.Remove(key);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The clear method just removes that key if it exists, and all the values associated with it.<br><strong>Time Complexity: O(1)<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public IEnumerable&lt;KeyValuePair&lt;K, V&gt;&gt; Flatten()\n{\n    var data = new List&lt;KeyValuePair&lt;K, V&gt;&gt;();\n\n    foreach (var item in _data)\n        foreach (var value in item.Value)\n            data.Add(new KeyValuePair&lt;K, V&gt;(item.Key, value));\n\n    return data;\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The flatten method just needs to return a collection of all key\/value pairs, where key is repeated for each of its values. The above implementation does the job, however, it creates a copy of all the data in another list. Luckily, C# offers a better solution.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public IEnumerable&lt;KeyValuePair&lt;K, V&gt;&gt; Flatten()\n{\n    foreach (var item in _data)\n        foreach (var value in item.Value)\n            yield return new KeyValuePair&lt;K, V&gt;(item.Key, value);\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The solution is to use yield return, which is an iterator to provide the next value. It avoids having to create another copy of data. Or use shorthand LINQ method syntax as shown below.<br><strong>Time Complexity: O(n)<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public IEnumerable&lt;KeyValuePair&lt;K, V&gt;&gt; Flatten()\n    =&gt; _data.SelectMany(x =&gt; x.Value.Select(value =&gt; new KeyValuePair&lt;K, V&gt;(x.Key, value)));<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Great! Are we done?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Not exactly. If you notice above, the <code>IMultiValueDictionary<\/code> interface inherits <code>IEnumerable<\/code> interface, so if we try to compile as is, we will get two errors.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>'MultiValueDictionary&lt;K, V&gt;' does not implement interface member 'IEnumerable&lt;KeyValuePair&lt;K, V&gt;&gt;.GetEnumerator()'\n'MultiValueDictionary&lt;K, V&gt;' does not implement interface member 'IEnumerable.GetEnumerator()'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">We have to implement <code>GetEnumerator<\/code> methods, as instructed by the compiler, to define how our data structure will iterate through its data. As it turns out, flatten already does exactly that (or defers that action) by returning an <code>IEnumerable<\/code>, which has <code>GetEnumerator<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public IEnumerator&lt;KeyValuePair&lt;K, V&gt;&gt; GetEnumerator() =&gt; Flatten().GetEnumerator();<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">And lastly <code>IEnumerable.GetEnumerator<\/code> is implemented with the following one-liner, which you don&#8217;t usually have to change.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>IEnumerator IEnumerable.GetEnumerator() =&gt; GetEnumerator();<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>And that does it! This compiles nicely, passes all unit tests and is efficient at storing\/retrieving data.<\/strong><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<script src=\"https:\/\/gist.github.com\/MechStar\/a98a7d7f4d369733688a05627dc1c42d.js\"><\/script>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity is-style-dots\"\/>\n\n\n\n<div class=\"wp-block-group is-content-justification-center is-nowrap is-layout-flex wp-container-core-group-is-layout-d05cb3ef wp-block-group-is-layout-flex\">\n\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>I recently was asked to implement a dictionary-like data structure that can store multiple values for a given key. The interface I was given is as follows. Since the precondition was that the values are unique for any given key, I decided to use a Dictionary where key is of type K and value is [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"footnotes":""},"categories":[1],"tags":[19,10,11,14,15,12],"class_list":["post-47","post","type-post","status-publish","format-standard","hentry","category-net","tag-deferred-execution","tag-dictionary","tag-hashset","tag-ienumerable","tag-ienumerator","tag-multi-value-dictionary"],"_links":{"self":[{"href":"https:\/\/denispavlov.net\/Blog\/wp-json\/wp\/v2\/posts\/47","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/denispavlov.net\/Blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/denispavlov.net\/Blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/denispavlov.net\/Blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/denispavlov.net\/Blog\/wp-json\/wp\/v2\/comments?post=47"}],"version-history":[{"count":20,"href":"https:\/\/denispavlov.net\/Blog\/wp-json\/wp\/v2\/posts\/47\/revisions"}],"predecessor-version":[{"id":172,"href":"https:\/\/denispavlov.net\/Blog\/wp-json\/wp\/v2\/posts\/47\/revisions\/172"}],"wp:attachment":[{"href":"https:\/\/denispavlov.net\/Blog\/wp-json\/wp\/v2\/media?parent=47"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/denispavlov.net\/Blog\/wp-json\/wp\/v2\/categories?post=47"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/denispavlov.net\/Blog\/wp-json\/wp\/v2\/tags?post=47"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}