33

I have a method that returns groups of technicians who have worked on certain projects, for example:

project 1 | John
project 1 | Tim
project 2 | John
project 2 | Dave

I originally tried to create a Dictionary which is usually my go-to collection of key-value pairs, but in this case I am unable to use it because I can't have a duplicate key (the project). What is an alternative that I can use?

My only thought is to create a Dictionary<Project, List<Technicians>> but is there something much easier?

AdamMc331
  • 15,332
  • 9
  • 63
  • 121
  • 1
    Dictionary ensures that you won't have duplicate keys in it, what's the problem? – Vsevolod Goloviznin Dec 15 '14 at 14:01
  • @VsevolodGoloviznin the problem is that I want to have a duplicate key, but still have unique Key Value Pairs (if I can restrict that part). – AdamMc331 Dec 15 '14 at 14:02
  • 15
    `Dictionary>` looks good enough. But if you don't really need to have `Project` as key (i.e. you don't need to get all technicians of some project) than you can try `List>` – Andy Korneyev Dec 15 '14 at 14:02
  • Do you need to be able to efficiently look up a group by its key (i.e. project)? If so, `Dictionary` is your guy. If you don't care, use something else. – Tim Rogers Dec 15 '14 at 14:05
  • @AndyKorneyev I will need all technicians for each project, but was trying to avoid the two-dimensional type access if I could, but I think your first option is going to be the way to go. – AdamMc331 Dec 15 '14 at 14:06
  • If you only ever need to get a list of the technicians for a project then you have a solution. If you need to go from technician to project then you should look at this http://stackoverflow.com/questions/255341/getting-key-of-value-of-a-generic-dictionary – juharr Dec 15 '14 at 14:06
  • Keep in mind, with your desired solution, you will end up doing multiple updates/delete in your collection, if you have to change `Project`. Your current option of `Dictionary>` doesn't have this problem and IMO, it is good enough. – Habib Dec 15 '14 at 15:04
  • possible duplicate of [multiple values for One Key values dictionary in C#?](http://stackoverflow.com/questions/19315413/multiple-values-for-one-key-values-dictionary-in-c) –  Dec 15 '14 at 16:44
  • 5
    This datastructure is called a *multimap*, unfortunately, there isn't one in the .NET class library. – Jörg W Mittag Dec 15 '14 at 17:13
  • 3
    It's probably clear to you by now, but what you are describing is more commonly thought of as having multiple values per key. Technically same thing I suppose, but at least to my ear, talking about "duplicate keys" sounds backwards, more like an error condition or invalid state. – hyde Dec 15 '14 at 20:59
  • @hyde I agree, and what you describe is exactly how I felt prior to asking this question. I understand a lot better now. – AdamMc331 Dec 16 '14 at 15:49

5 Answers5

46

In your case, the same key is related to multiple values, so standard dictionary is not suitable, as is. You can declare it like Dictionary<Key, List<Values>>.

But, also, you can use:

Lookup class, which is

Represents a collection of keys each mapped to one or more values.

You need framework 3.5 and more, for this.

Tigran
  • 59,345
  • 8
  • 77
  • 117
  • Does `Lookup` have a public constructor? – Groo Dec 15 '14 at 14:06
  • @Groo: no, but it's easy to convert a sequence or dictionary with `ToLookup` extension. – Dennis Dec 15 '14 at 14:06
  • I think a Lookup class is glossing over the real issue here. The OP has a relationship between to classes that should be modeled by a property on the `Project` class. – Greg Burghardt Dec 15 '14 at 14:07
  • 13
    @Dennis: I doubt OP will find this useful. `Lookup` is a readonly collection, which can only be created using LINQ from existing data. You cannot instantiate it yourself, or mutate it. – Groo Dec 15 '14 at 14:08
  • @GregBurghardt changing the `Project` class is not a preferred route. I'm dealing with software that has already been written and this is to include some added functionality. We only recently began recording Technician work hours for a specific project. @Tigran, is this *similar* to a `List>` that others have suggested? – AdamMc331 Dec 15 '14 at 14:09
  • 6
    Changing the `Project` class _is_ preferred because you clearly need a Project linked to a list of Technicians. I can see using a Lookup class or Dictionary if you can't change the source code for the Project class, but if you can it is useful to create a __clear connection between two classes__. – Greg Burghardt Dec 15 '14 at 14:12
  • @GregBurghardt I definitely understand where you're coming from. I will discuss it with the lead developer of the project. Otherwise I'm likely going to use a `Lookup`. – AdamMc331 Dec 15 '14 at 14:13
  • @McAdam331: no. in case of `List` you don't have key like access to it. – Tigran Dec 15 '14 at 14:15
  • @Tigran good to know. This definitely does what I was looking for, but I'm going to stick with the first method as you mentioned. I realized in other comments that I am always going to want every technician for a project (in this particular case) and the first method organizes them exactly like that. However, `Lookup` will definitely have uses for me in the future and I'm glad you brought it to my attention. Thanks! – AdamMc331 Dec 15 '14 at 14:19
  • @McAdam331 It's more appropriate to extend the `Project` class, but you get it from a third party? In cases like this, I find [`ConditionalWeakTable`](http://msdn.microsoft.com/en-us/library/dd287757%28v=vs.110%29.aspx) useful. You can use the `Project` objects as keys, but you don't have to worry about causing memory leaks. – Keen Dec 15 '14 at 16:28
13

What you need is a relationship between a Project and one or more Technicians:

public class Project
{
    public ICollection<Technician> Technicians { get; set; }
}

var project = new Project();
project.Technicians = new List<Technician>()
{
    new Technician(),
    new Technician()
};

Your objects should mirror the relationships in real life.

As a side note, you might be interested in reading about Domain-driven design.

public void LoadTechnicians(Project project)
{
    List<Technician> techs = new List<Technician>();

    // query the database and map Technician objects

    // Set the "Technicians" property
    project.Technicians = techs;
}
Greg Burghardt
  • 14,951
  • 7
  • 38
  • 71
  • What about **fast** search for the technicians by project? – Dennis Dec 15 '14 at 14:09
  • It's already handled by the fact a `Project` has a list of technicians. – Greg Burghardt Dec 15 '14 at 14:10
  • 1
    @Dennis If you have a `Project` and you need all of the `Technicians` for it then in this example all you need to do is call a property of the `Project`, rather than feeding it into a dictionary and handling the output. It'll be both easier *and* faster. – Servy Dec 15 '14 at 18:36
12

There is an experimental NuGet package from MS that contains MultiValueDictionary.

Basically, it's like Dictionary<Project, List<Technicians>>, except that you don't have to repeat all the logic to manage the Lists every time you access it.

svick
  • 214,528
  • 47
  • 357
  • 477
  • 2
    Just a note, but it's no longer the MultiDictionary. It's been renamed to the MultiValueDictionary for better clarification of what it does: http://blogs.msdn.com/b/dotnet/archive/2014/08/05/multidictionary-becomes-multivaluedictionary.aspx – Greg Dec 15 '14 at 16:36
5

I don't think there is anything wrong with your solution. After all by, you can access easily all the team members by project. But alternatively you can try List<KeyValuePair<Project, Technician>>. You maintain the key-value relationship, but without restriction of not repeated keys. Is it much simpler to what you have now? Depends on use cases.

Alternatively, you can hide this structure behind your custom collection implementation.

PiotrWolkowski
  • 7,550
  • 5
  • 38
  • 58
0

I've copy pasted my own answer from this post.

It's easy enough to "roll your own" version of a dictionary that allows "duplicate key" entries. Here is a rough simple implementation. You might want to consider adding support for basically most (if not all) on IDictionary<T>.

public class MultiMap<TKey,TValue>
{
    private readonly Dictionary<TKey,IList<TValue>> storage;

    public MultiMap()
    {
        storage = new Dictionary<TKey,IList<TValue>>();
    }

    public void Add(TKey key, TValue value)
    {
        if (!storage.ContainsKey(key)) storage.Add(key, new List<TValue>());
        storage[key].Add(value);
    }

    public IEnumerable<TKey> Keys
    {
        get { return storage.Keys; }
    }

    public bool ContainsKey(TKey key)
    {
        return storage.ContainsKey(key);
    }

    public IList<TValue> this[TKey key]
    {
        get
        {
            if (!storage.ContainsKey(key))
                throw new KeyNotFoundException(
                    string.Format(
                        "The given key {0} was not found in the collection.", key));
            return storage[key];
        }
    }
}

A quick example on how to use it:

const string key = "supported_encodings";
var map = new MultiMap<string,Encoding>();
map.Add(key, Encoding.ASCII);
map.Add(key, Encoding.UTF8);
map.Add(key, Encoding.Unicode);

foreach (var existingKey in map.Keys)
{
    var values = map[existingKey];
    Console.WriteLine(string.Join(",", values));
}
Community
  • 1
  • 1
ChristopheD
  • 100,699
  • 26
  • 154
  • 173