Edward Tanguay
Edward Tanguay

Reputation: 193462

What is the best collection type to easily lookup values with multiple, identical keys?

I have text documents like the following which contain single and multiple variables:

title:: Report #3
description:: This is the description.
note:: more information is available from marketing
note:: time limit for this project is 18 hours
todo:: expand the outline
todo:: work on the introduction
todo:: lookup footnotes

I need to iterate through the lines of this text document and fill a collection with these variables, currently I'm using a Dictionary:

public Dictionary<string, string> VariableNamesAndValues { get; set; }

But this doesn't work on multiple, identical keys such as "note" and "todo" in the above example since keys have to be unique in a Dictionary.

What is the best collection so that I can not only get single values like this:

string variableValue = "";
if (VariableNamesAndValues.TryGetValue("title", out variableValue))
    return variableValue;
else
    return "";

but that I can also get multiple values out like this:

//PSEUDO-CODE:
List<string> variableValues = new List<string>();
if (VariableNamesAndValues.TryGetValues("note", out variableValues))
    return variableValues;
else
    return null;

Upvotes: 8

Views: 4067

Answers (6)

Sameh Deabes
Sameh Deabes

Reputation: 2973

You may use PowerCollections which is an open source project that has a MultiDictionary data structure which solves your problem.

Here is a sample of how to use it.

Note: Jon Skeet suggested it before in his answer to this question.

Upvotes: 2

Yvo
Yvo

Reputation: 19273

You can make a Dictionary of key: string and value: List of String

Dictionary<string,List<string>>

EDIT 1 & 2:
I've thought of a better solution if you can use .NET 3.0 or higher.
Here's a LINQ example (I typed it without Visual Studio, so I hope it compiles ;)):

string[] lines = File.ReadAllLines("content.txt");
string[] separator = {":: "};
var splitOptions = StringSplitOptions.RemoveEmptyEntries;

var items = from line in lines
            let parts = line.Split(separator, splitOptions)
            group parts by parts[0] into partGroups
            select partGroups;

A short explanation of the example above:

  • Get all lines from the file in a String array
  • Define some Split options (to keep the example readable)
  • For each line in the lines array, split it on the ":: "
  • Group the results of the split on the first split part (e.g. title, description, note, ...)
  • Store the grouped items in the items variable

The result of the LINQ query is a IQueryable<IGrouping<string, IEnumberable<string>>>.
Each item in the result has a Key property containing the key of the line (title, description, note, ...).
Each item can be enumerated containing all of values.

Upvotes: 3

Thomas Levesque
Thomas Levesque

Reputation: 292765

You could use a Lookup<TKey, TElement> :

ILookup<string, string> lookup = lines.Select(line => line.Split(new string[] { ":: " })
                                      .ToLookup(arr => arr[0], arr => arr[1]);
IEnumerable<string> notes = lookup["note"];

Note that this collection is read-only

Upvotes: 2

Josh
Josh

Reputation: 69282

If your keys and values are strings then use a NameValueCollection. It supports multiple values for a given key.

It's not the most efficient collection in the world. Particularly because it's a non-generic class, uses a lot of virtual method calls, and the GetValues method will allocate arrays for its return values. But unless you require the best performing collection, this is certainly the most convenient collection that does what you ask.

Upvotes: 7

ChaosPandion
ChaosPandion

Reputation: 78292

I have used Dictionary<string, HashSet<string>> for getting multiple values in the past. I would love to know if there is something better though.

Here is how you can emulate getting only one value.

public static bool TryGetValue(this Dictionary<string, HashSet<string>> map, string key, out string result)
{
    var set = default(HashSet<string>);
    if (map.TryGetValue(key, out set))
    {
        result = set.FirstOrDefault();
        return result == default(string);
    }
    result = default(string);
    return false;
}

Upvotes: 1

Tedil
Tedil

Reputation: 1933

I'm not a c# expert, but I think Dictionary<string, List<string>>

or some kind of HashMap<string, List<string>> might work. For example (Java pseudocode): aKey aValue aKey anotherValue

if(map.get(aKey) == null)
{
   map.put(aKey, new ArrayList(){{add(aValue);}});
} 
else 
{
   map.put(aKey, map.get(aKey).add(anotherValue));
}

or something similar. (or, the shortest way:

map.put(aKey, map.get(aKey) != null ? map.get(aKey).add(value) : new ArrayList(){{add(value);}});

Upvotes: 1

Related Questions