views:

302

answers:

6

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;
+3  A: 

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.

Zyphrax
this is definitely a solution but I will be doing a lot of the managing of the collection myself, e.g. logic such as if-key-doesn't-exist-yet create-new-entry otherwise add-to-List<string>-of-existing-entry, I would think this would be a common enough need in programming that some core .NET collection type would exist to handle it inherently so I can just say .Add(key, value) and either the single or multiple key is stored internally.
Edward Tanguay
@Edward: no sorry, not that I know of. What you could do is use LINQ to process your string lines and use the ToDictionary method to create your dictionary.
Zyphrax
@Edward: I've put some more thought into it, have a look at the new example :)
Zyphrax
+1  A: 

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;
}
ChaosPandion
@ChaosPandion +1: HashSet is a good suggestion, although it isn't that much faster than a List for small lists. And you lose the ability to get a specific item by index from the set. I often use HashSets for complex objects or large lists of value types.
Zyphrax
@Zyphrax - Agreed, I am just remembering situations where each entry in the dictionary had to have a unique set of values. LINQ makes using the `HashSet` a little more convenient if not slower.
ChaosPandion
+1  A: 

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);}});
Tedil
+7  A: 

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.

Josh Einstein
But you have to manually split the values when you retrieve them. I would recommend this be wrapped into its own little class.
ChaosPandion
No that is not true. There is a GetValues method.
Josh Einstein
Man don't you love that feeling of embarrassment?
ChaosPandion
It's understandable. NameValueCollection is strange in a lot of ways. It doesn't implement IDictionary for example.
Josh Einstein
I bet the performance issues with this collection are negated by the workarounds you have to use in the other solutions.
ChaosPandion
Maybe, but not necessarily. Particularly if you read more than you write to the collection, the arrays created by the GetValues method could be a problem. Internally I think it's just splitting the string.
Josh Einstein
Edward Tanguay
+2  A: 

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

Thomas Levesque
Ahh that's the one I was looking for too but I couldn't remember. I saw it on a Jon Skeet tweet recently.
Josh Einstein
+2  A: 

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.

Sameh Serag
+1, I love PowerCollections. But it was designed circa .NET 2.0 and hasn't really been touched since. It could probably use a rewrite with .NET 3.5 or 4.0 in mind.
Josh Einstein