2

I'm trying to make a program where it reads strings that has a word and its meaning, for example

Book: Cover with Papers in between
Book: Reserve

And whenever I try my code I get an error because each key has to be unique. Is there a way to work around this?

Hashtable ht = new Hashtable();
        var fileStream = new FileStream(@"e:\test.txt", FileMode.Open, FileAccess.Read);
        using (var streamReader = new StreamReader(fileStream, Encoding.UTF8))
        {

            string line;
            while ((line = streamReader.ReadLine()) != null)
            {
                ht.Add(line.Split(':')[0], line.Split(':')[1]);
            }
        }

        if (ht.ContainsKey("Book"))
        {
            listBox1.Items.Add(ht["Book"].ToString());
        }
Cor
  • 37
  • 1
  • 5

5 Answers5

3

In the general case, you could use a List<string> for the value, and just Add to it. However, you can probably simplify with LINQ via ToLookup:

var groups = File.ReadLines(path)
            .Select(line => line.Split(':'))
            .ToLookup(x => x[0], x => x[1].Trim());

Now you can access groups[key] which gives you all the values with that prefix, or you can foreach over groups to get each combination of .Key and values.

In terms of your code, this is:

var groups = File.ReadLines(@"e:\test.txt")
            .Select(line => line.Split(':'))
            .ToLookup(x => x[0], x => x[1].Trim());

foreach(var val in groups["Book"])
    listBox1.Items.Add(val);

(no need to check for existence first, it just works correctly if no match)

However! You only need to do this if you still want all the values after this code, i.e. you use groups somewhere else. If you don't, you can be more frugal and just abandon the unwanted data:

var values = File.ReadLines(@"e:\test.txt")
             .Where(line => line.StartsWith("Book:"))
             .Select(line => line.Substring(5).Trim());

foreach(var val in values)
    listBox1.Items.Add(val);

Edit: minor thing - a vexing method signature means that line.Split(':') actually creates an array every time, because params; so I usually use:

static readonly char[] Colon = {':'};

and

line.Split(Colon)

Which is measurably more efficient if it is a hot path.

Marc Gravell
  • 927,783
  • 236
  • 2,422
  • 2,784
  • Thank you, could you apply it to my code so I can see how it works please, as I'm still very new to this. – Cor May 04 '17 at 08:26
  • @Cor sure; but: if there are 2 lines with `Book` as the prefix, what value did you want to use in the listbox? all of them? or the first? or the last? – Marc Gravell May 04 '17 at 08:29
  • ♦ All of them, in our example 'book' has two meanings, i want to add both to the listbox. Thanks! – Cor May 04 '17 at 08:33
  • @Cor I've added two examples of doing that, depending on your exact needs – Marc Gravell May 04 '17 at 08:34
2

Use a Dictionary where the values is a list of strings:

var myDicitonary = new Dictionary<string, List<string>>

And now, you'd do the following:

if (!myDictionary.ContainsKey(key))
{
    myDicitonary.Add(key, new List<string>());
}

myDicitonary[key].Add(value);
InBetween
  • 30,991
  • 3
  • 46
  • 80
0

Use a Dictionary<string, List<string>> instead of the Hashtable.

Patrick
  • 628
  • 4
  • 11
0

Depending on what you want to achieve, you may use a SortedList or a SortedDictionary, that you initialze with your own IComparer, which allows duplicate keys.
Have a look at these Stackoverflow posts for details:

C# Sortable collection which allows duplicate keys
Is there an alternative to Dictionary/SortedList that allows duplicates?

The drawback of this solution is, that you cannot access the elements by key, but still by index.

Community
  • 1
  • 1
Tobias Knauss
  • 2,931
  • 1
  • 14
  • 39
0

use Dictionary instead.

Dictionary<string, list<string>> dic = new Dictionary<string, list<string>();
Amir Zia
  • 1
  • 2