65

I want to create a data store to allow me to store some data.

The first idea was to create a dictionary where you have 1 key with many values, so a bit like a one to many relationship.

I think the dictionary only has 1 key value.

How else could I store this information?

Drew Gaynor
  • 7,733
  • 5
  • 36
  • 50
Marc G
  • 683
  • 1
  • 7
  • 7

14 Answers14

70

As of .net3.5+ instead of using a Dictionary<IKey, List<IValue>> you can use a Lookup from the Linq namespace:

// lookup Order by payment status (1:m) 
// would need something like Dictionary<Boolean, IEnumerable<Order>> orderIdByIsPayed
ILookup<Boolean, Order> byPayment = orderList.ToLookup(o => o.IsPayed);
IEnumerable<Order> payedOrders = byPayment[false];

From msdn:

A Lookup resembles a Dictionary. The difference is that a Dictionary maps keys to single values, whereas a Lookup maps keys to collections of values.

You can create an instance of a Lookup by calling ToLookup on an object that implements IEnumerable.

You may also want to read this answer to a related question. For more info, consult msdn.

Full example:

using System;
using System.Collections.Generic;
using System.Linq;

namespace LinqLookupSpike
{
    class Program
    {
        static void Main(String[] args)
        {
            // init 
            var orderList = new List<Order>();
            orderList.Add(new Order(1, 1, 2010, true));//(orderId, customerId, year, isPayed)
            orderList.Add(new Order(2, 2, 2010, true));
            orderList.Add(new Order(3, 1, 2010, true));
            orderList.Add(new Order(4, 2, 2011, true));
            orderList.Add(new Order(5, 2, 2011, false));
            orderList.Add(new Order(6, 1, 2011, true));
            orderList.Add(new Order(7, 3, 2012, false));

            // lookup Order by its id (1:1, so usual dictionary is ok)
            Dictionary<Int32, Order> orders = orderList.ToDictionary(o => o.OrderId, o => o);

            // lookup Order by customer (1:n) 
            // would need something like Dictionary<Int32, IEnumerable<Order>> orderIdByCustomer
            ILookup<Int32, Order> byCustomerId = orderList.ToLookup(o => o.CustomerId);
            foreach (var customerOrders in byCustomerId)
            {
                Console.WriteLine("Customer {0} ordered:", customerOrders.Key);
                foreach (var order in customerOrders)
                {
                    Console.WriteLine("    Order {0} is payed: {1}", order.OrderId, order.IsPayed);
                }
            }

            // the same using old fashioned Dictionary
            Dictionary<Int32, List<Order>> orderIdByCustomer;
            orderIdByCustomer = byCustomerId.ToDictionary(g => g.Key, g => g.ToList());
            foreach (var customerOrders in orderIdByCustomer)
            {
                Console.WriteLine("Customer {0} ordered:", customerOrders.Key);
                foreach (var order in customerOrders.Value)
                {
                    Console.WriteLine("    Order {0} is payed: {1}", order.OrderId, order.IsPayed);
                }
            }

            // lookup Order by payment status (1:m) 
            // would need something like Dictionary<Boolean, IEnumerable<Order>> orderIdByIsPayed
            ILookup<Boolean, Order> byPayment = orderList.ToLookup(o => o.IsPayed);
            IEnumerable<Order> payedOrders = byPayment[false];
            foreach (var payedOrder in payedOrders)
            {
                Console.WriteLine("Order {0} from Customer {1} is not payed.", payedOrder.OrderId, payedOrder.CustomerId);
            }
        }

        class Order
        {
            // key properties
            public Int32 OrderId { get; private set; }
            public Int32 CustomerId { get; private set; }
            public Int32 Year { get; private set; }
            public Boolean IsPayed { get; private set; }

            // additional properties
            // private List<OrderItem> _items;

            public Order(Int32 orderId, Int32 customerId, Int32 year, Boolean isPayed)
            {
                OrderId = orderId;
                CustomerId = customerId;
                Year = year;
                IsPayed = isPayed;
            }
        }
    }
}

Remark on Immutability

By default, Lookups are kind of immutable and accessing the internals would involve reflection. If you need mutability and don't want to write your own wrapper, you could use MultiValueDictionary (formerly known as MultiDictionary) from corefxlab (formerly part ofMicrosoft.Experimental.Collections which isn't updated anymore).

mbx
  • 5,728
  • 6
  • 57
  • 86
  • Suppose key and values are both of type string then why not `Dictionary>` instead of `Dictionary>`? List cannot guarantee uniqueness within the value collection for a given key but a set can. – user3613932 Jun 27 '18 at 23:19
  • @user3613932 Not the point of my answer. If you want to enforce uniqueness for values, just `.Distinct()` them before creating the lookup. If you need multisets or keep items ordered or indexed a list seems reasonable. – mbx Jun 29 '18 at 14:39
  • @mbx I agree with the correctness and feasibility of your approach. With the list approach, there is an implicit assumption in your answer i.e. the collection of values per key is small, and with this assumption in mind, your answer seems appropriate. However, from a readability perspective, if I use something off the shelf to maintain uniqueness in the collection (set) then I would argue it is more readable as the interfaces/API are clear, and even the class `HashSet` gives a very clear signal to the reader of my code (they don't have to go into my implementation to see what I am doing). – user3613932 Jul 03 '18 at 02:59
  • @user3613932 You mean the first list in the full example, right? This is just boiler plate to show the usage of `ToLookup` and `ILookup` vs the `Dictionary` based implementation. – mbx Jul 03 '18 at 10:01
53

You can use a list for the second generic type. For example a dictionary of strings keyed by a string:

Dictionary<string, List<string>> myDict;
Shimmy Weitzhandler
  • 92,920
  • 119
  • 388
  • 596
Oded
  • 463,167
  • 92
  • 837
  • 979
19

Microsoft just added an official prelease version of exactly what you're looking for (called a MultiDictionary) available through NuGet here: https://www.nuget.org/packages/Microsoft.Experimental.Collections/

Info on usage and more details can be found through the official MSDN blog post here: http://blogs.msdn.com/b/dotnet/archive/2014/06/20/would-you-like-a-multidictionary.aspx

I'm the developer for this package, so let me know either here or on MSDN if you have any questions about performance or anything.

Hope that helps.

Update

The MultiValueDictionary is now on the corefxlab repo, and you can get the NuGet package from this MyGet feed.

Shimmy Weitzhandler
  • 92,920
  • 119
  • 388
  • 596
Ian Hays
  • 591
  • 1
  • 5
  • 4
  • Looks like it's now called MultiValueDictionary. I want to use this, but am unsure of its future. The blog hasn't been updated in 3 years. Any ideas as to whether this is safe to use? – Al Lelopath May 05 '17 at 20:07
  • I haven't experimented with the `MultiValueDictionary`, but it implements `IReadOnlyDictionary` which is immutable. Anyway I've updated your answer, looks like that tool moved to the corefxlab repo. – Shimmy Weitzhandler Aug 22 '17 at 19:15
8

Use this:

Dictionary<TKey, Tuple<TValue1, TValue2, TValue3, ...>>
Shimmy Weitzhandler
  • 92,920
  • 119
  • 388
  • 596
Gisway
  • 5,314
  • 5
  • 33
  • 58
7

Your dictionary's value type could be a List, or other class that holds multiple objects. Something like

Dictionary<int, List<string>> 

for a Dictionary that is keyed by ints and holds a List of strings.

A main consideration in choosing the value type is what you'll be using the Dictionary for, if you'll have to do searching or other operations on the values, then maybe think about using a data structure that helps you do what you want -- like a HashSet.

Tim Ridgely
  • 2,290
  • 1
  • 18
  • 24
5

You could use a Dictionary<TKey, List<TValue>>.

That would allow each key to reference a list of values.

Shimmy Weitzhandler
  • 92,920
  • 119
  • 388
  • 596
Blorgbeard
  • 93,378
  • 43
  • 217
  • 263
4

Use a dictionary of lists (or another type of collection), for example:

var myDictionary = new Dictionary<string, IList<int>>();

myDictionary["My key"] = new List<int> {1, 2, 3, 4, 5};
GraemeF
  • 10,997
  • 5
  • 50
  • 73
1

You can have a dictionary with a collection (or any other type/class) as a value. That way you have a single key and you store the values in your collection.

Maurits Rijk
  • 9,175
  • 2
  • 31
  • 50
1

A .NET dictionary does only have a 1-to-1 relationship for keys and values. But that doesn't mean that a value can't be another array/list/dictionary.

I can't think of a reason to have a 1 to many relationship in a dictionary, but obviously there is one.

If you have different types of data that you want to store to a key, then that sounds like the ideal time to create your own class. Then you have a 1 to 1, but you have the value class storing more that 1 piece of data.

Alastair Pitts
  • 18,689
  • 9
  • 63
  • 95
1

You can create a very simplistic multi-dictionary, which automates to process of inserting values like this:

public class MultiDictionary<TKey, TValue> : Dictionary<TKey, List<TValue>>
{
    public void Add(TKey key, TValue value)
    {
        if (TryGetValue(key, out List<TValue> valueList)) {
            valueList.Add(value);
        } else {
            Add(key, new List<TValue> { value });
        }
    }
}

This creates an overloaded version of the Add method. The original one allows you to insert a list of items for a key, if no entry for this entry exists yet. This version allows you to insert a single item in any case.

You can also base it on a Dictionary<TKey, HashSet<TValue>> instead, if you don't want to have duplicate values.

Olivier Jacot-Descombes
  • 86,431
  • 10
  • 121
  • 160
1

Take a look at MultiValueDictionary from Microsoft.

Example Code:

MultiValueDictionary<string, string> Parameters = new MultiValueDictionary<string, string>();

Parameters.Add("Malik", "Ali");
Parameters.Add("Malik", "Hamza");
Parameters.Add("Malik", "Danish");

//Parameters["Malik"] now contains the values Ali, Hamza, and Danish
Ali Asad
  • 1,045
  • 1
  • 12
  • 25
0

Here's my approach to achieve this behavior.

For a more comprehensive solution involving ILookup<TKey, TElement>, check out my other answer.

public abstract class Lookup<TKey, TElement> : KeyedCollection<TKey, ICollection<TElement>>
{
  protected override TKey GetKeyForItem(ICollection<TElement> item) =>
    item
    .Select(b => GetKeyForItem(b))
    .Distinct()
    .SingleOrDefault();

  protected abstract TKey GetKeyForItem(TElement item);

  public void Add(TElement item)
  {
    var key = GetKeyForItem(item);
    if (Dictionary != null && Dictionary.TryGetValue(key, out var collection))
      collection.Add(item);
    else
      Add(new List<TElement> { item });
  }

  public void Remove(TElement item)
  {
    var key = GetKeyForItem(item);
    if (Dictionary != null && Dictionary.TryGetValue(key, out var collection))
    {
      collection.Remove(item);
      if (collection.Count == 0)
        Remove(key);
    }
  }
}

Usage:

public class Item
{
  public string Key { get; }
  public string Value { get; set; }
  public Item(string key, string value = null) { Key = key; Value = value; }
}

public class Lookup : Lookup<string, Item>
{
  protected override string GetKeyForItem(Item item) => item.Key;
}

static void Main(string[] args)
{
  var toRem = new Item("1", "different");
  var single = new Item("2", "single");
  var lookup = new Lookup()
  {
    new Item("1", "hello"),
    new Item("1", "hello2"),
    new Item(""),
    new Item("", "helloo"),
    toRem,
    single
  };

  lookup.Remove(toRem);
  lookup.Remove(single);
}

Note: the key must be immutable (or remove and re-add upon key-change).

Shimmy Weitzhandler
  • 92,920
  • 119
  • 388
  • 596
0

You can also use;

 List<KeyValuePair<string, string>> Mappings;
Teoman shipahi
  • 43,086
  • 13
  • 113
  • 137
0
 Dictionary<int, string[]> dictionaty  = new Dictionary<int, string[]>() {
            {1, new string[]{"a","b","c"} },
            {2, new string[]{"222","str"} }
        };
Chag
  • 31
  • 3
  • 4
    While this code may answer the question, providing additional context regarding how and/or why it solves the problem would improve the answer's long-term value. – Piotr Labunski Apr 07 '20 at 09:57