38

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?

11
  • 1
    Dictionary ensures that you won't have duplicate keys in it, what's the problem? Commented Dec 15, 2014 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). Commented Dec 15, 2014 at 14:02
  • 15
    Dictionary<Project, List<Technicians>> 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<Tuple<Project, Technician>> Commented Dec 15, 2014 at 14:02
  • 5
    This datastructure is called a multimap, unfortunately, there isn't one in the .NET class library. Commented Dec 15, 2014 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. Commented Dec 15, 2014 at 20:59

5 Answers 5

52

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.

Sign up to request clarification or add additional context in comments.

10 Comments

Does Lookup have a public constructor?
@Groo: no, but it's easy to convert a sequence or dictionary with ToLookup extension.
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.
@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.
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.
|
14

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

3 Comments

What about fast search for the technicians by project?
It's already handled by the fact a Project has a list of technicians.
@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.
14

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.

1 Comment

Just a note, but it's no longer the MultiDictionary. It's been renamed to the MultiValueDictionary for better clarification of what it does: blogs.msdn.com/b/dotnet/archive/2014/08/05/…
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.

3 Comments

It seems they don't want “project 1 | John” twice. Maybe a HashSet<KeyValuePair<Project, Technician>>?
@ArturoTorresSánchez No, I don't want repeated Key-Value pairs. Project 1 can't have repeated technicians.
@McAdam331, ... I was going to say that KeyValuePair overrides Equals, but it seems it actually doesn't. I guess then Tuple may be a better option (still with HashSet).
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));
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.