0

So I have set up my Dictionary as follow:

Dictionary<string[], string> d = new Dictionary<string[], string>();
d.Add(new[] { "email@yahoo.com", "test@yahoo.com" }, "Group 1");
d.Add(new[] { "myemail@gmail.com", "checkit@gmail.com" }, "Group 2");

string keycheck = "email@yahoo.com";
string val = "";

d.TryGetValue(keycheck, out string k);

I get the following error:

Argument 1: cannot convert from 'string' to 'string[]' (CS1503) (Project1)

which make sense since the key in the Dictionary is an String array.

How can I update my code so I can find the value in the Dictionary based on the email string.

So if the keycheck is provided above, the return value should be Group 1

Si8
  • 9,141
  • 22
  • 109
  • 221
  • 1
    Parsing every single key to check if it contains the string you are looking for. Honestly this is a terrible way to use a dictionary – Steve Mar 14 '19 at 16:59
  • What would you recommend instead of a dictionary? – Si8 Mar 14 '19 at 16:59
  • 3
    Don't use string arrays as keys then. Use the email address (string) as the key and the group as the value. – Mike Zboray Mar 14 '19 at 17:01
  • Well, first we need to understand why you have built the dictionary in that way. A simpler class with email and group and then a List of these classes? – Steve Mar 14 '19 at 17:01
  • 1
    @Si8 An easy way to do that, if you don't worry about memory too much, is a `Dictionary`, repeating the Groups for every email. – Guilherme Mar 14 '19 at 17:02
  • Possible duplicate with https://stackoverflow.com/questions/2444033/get-dictionary-key-by-value – Pavel Anikhouski Mar 14 '19 at 17:04
  • @Steve There will be sets of emails which would on on each bucket of group and figured dictionary would be the way to go. I would go through each email provided by the user and return which group they fall under. I guess I was mistaken? – Si8 Mar 14 '19 at 17:04
  • @MikeZboray What do you mean? The email addresses are the key and the group as the value. – Si8 Mar 14 '19 at 17:05
  • If you need to find the Group from the Email and you aren't limited by the dictionary size then probably the best way is to have a dictionary where you store the a single email as key and add another entry for another email with the same group – Steve Mar 14 '19 at 17:07
  • @Steve So you are saying add a new line for each email? – Si8 Mar 14 '19 at 17:09
  • 1
    @Si8 You have a list of email address as keys. I am proposing a single email address as a key. The value is the group (string). You want to structure the dictionary so that you can use it for the O(1) lookups on keys. – Mike Zboray Mar 14 '19 at 17:10
  • If the speed is of uttermost importance yes. – Steve Mar 14 '19 at 17:10
  • If I have 200 email address, there will be 200 lines... idk that may get out of control as I add more emails. But if that's the faster method of adding one line at a time, then that's way i will do it. – Si8 Mar 14 '19 at 17:13
  • It is the faster way to retrieve an item from your dictionary, not to add them. Anyway 200 items are not really a burden on your memory – Steve Mar 14 '19 at 17:15
  • Sorry that's what I meant... Thanks – Si8 Mar 14 '19 at 17:16
  • 1
    What happens when the key email address is in two different groups? – Eric Lippert Mar 14 '19 at 17:26
  • 1
    It's 1 to 1 entry @EricLippert – Si8 Mar 14 '19 at 17:31
  • 2
    Then you should have two dictionaries: one from string to string, that is email to group, and one from string to string[], that is group to emails. You can then do lookups both ways: I have the group, I need the emails, or I have the email, I need the group. – Eric Lippert Mar 14 '19 at 17:34
  • You might want to re-think this structure. What are the scenarios you need to support? What defines a "group"? Can an email address be moved from one group to another? Can it belong to more than one group? Can it be removed from a group? Does a group have other properties (i.e. should it be represented by a class rather than a string)? What types of lookups do you need to do? Is "email address" a property of another object (do you need a `User` class)? I think some more time spent on design may change the way you're storing these related objects. – Rufus L Mar 14 '19 at 17:37
  • @RufusL I agree and the only scenario is this will be hardcoded values, email to group. An email can only be part of one group so it will never happen where one email falls in multiple group. I just need to find the group an email address belong to and the email can get really long, maybe starting with 200 and maybe 500+. – Si8 Mar 14 '19 at 17:39
  • Sounds like a database would come in handy here – Rufus L Mar 14 '19 at 17:41
  • Yes i would think so but unf that's not an option for me :D – Si8 Mar 14 '19 at 17:43
  • a SQL Db would be perfect – Si8 Mar 14 '19 at 17:43
  • 1
    While a traditional database is probably not appropriate, a SQLite database is rarely a non-option. SQLite is more of a "file format that supports SQL syntax" than a raw database. In contrast to traditional databases, SQLite does not require deployment/installation; it runs within the app itself. Dev installation just means adding a Nuget package (or downloading the precompiled binaries). That being said, as written your problem description seems too simple to justify SQLite. – Brian Mar 15 '19 at 13:17

3 Answers3

2
var result = d.FirstOrDefault(x=>x.Key.Contains(keycheck));

It returns a KeyValuePair, to get the value you can do a .Value. If it can't find, it will return a KeyValuePair where both the key and value are null.

Note, however, that using an array as the key and search for strings inside the array, you lose one of the greatest benefits of the Dictionary, which is a faster way to retrieve a value, given it's key.

Magnetron
  • 7,495
  • 1
  • 25
  • 41
  • Thanks, speed maybe on concern though. – Si8 Mar 14 '19 at 17:03
  • @Si8 Why, what did you get? – Magnetron Mar 14 '19 at 17:18
  • I got `null` for both key and value – Si8 Mar 14 '19 at 17:19
  • I do agree with the `Note` you added so maybe i just add one email per group. – Si8 Mar 14 '19 at 17:19
  • That means that the string searched is not any array of the dictionary. Are you sure everything is typed correted? Because in my machine it worked – Magnetron Mar 14 '19 at 17:21
  • WOW you are right... I had an extra `.` :/ My mistake you are correct. Is there any way to stop the search once I found the value or I think that may be happening now – Si8 Mar 14 '19 at 17:25
  • The `FirstOrDefault` method already stops when it finds the first item. – Magnetron Mar 14 '19 at 17:27
  • It puzzles me, because I copied and pasted your code and mine and it worked. – Magnetron Mar 14 '19 at 17:29
  • One of the email is like this `name.last@email.com` – Si8 Mar 14 '19 at 17:30
  • It shouldn't make difference the format of the email because everything is a string – Magnetron Mar 14 '19 at 17:33
  • That's what i thought and I am confused as well – Si8 Mar 14 '19 at 17:33
  • So I first took a CSV and converted it into a DT and the email value in the column is showing as this `email.first.last@gmail.com\r`. Do you think that could be an issue with the `\r`? Maybe I strip that first? – Si8 Mar 14 '19 at 17:47
  • Yes, that makes the two strings different. I was about to suggest that the problem could be a non-printable char. You should store without that char – Magnetron Mar 14 '19 at 17:49
  • This approach doesn't use the dictionary's internal index. Basically you could have just had an array and searched it and the performance would be the same. Except the dictionary also maintains an index of the arrays for you which you don't use. – Tamir Daniely Mar 14 '19 at 18:05
1

The way you're arranging your data you will not be able to query the index at O(1). all searches will be slow. To use Dictionary<TKey, TValue> effectively you need to do something like this:

    class Group : List<string>
    {
        public string Name { get; set; }
        public Group(string name) => Name = name;
    }

    static void Main(string[] args)
    {
        var groups = new List<Group> {
            new Group("Group 1") { "email@yahoo.com", "test@yahoo.com" },
            new Group("Group 2") { "myemail@gmail.com", "checkit@gmail.com" }
        };


        var index = groups
            .SelectMany(group => group.Select(item => (item, group)))
            .ToDictionary(x => x.item, x => x.group);

        // This will be a very fast operation
        var whichGroup = index["test@yahoo.com"].Name; // = "Group 1"
    }

If your data set gets updated then you'll also need to update the index. For that you can extend Collection<T> which allows you to handle changes with overrides.

Note that this approach means that every value can only be in 1 group. If you need to be able to have the same value in several groups then the index value becomes a list.

Tamir Daniely
  • 1,659
  • 1
  • 20
  • 24
  • Ahhhh I see what you did there... I totally forgot about `List<>` – Si8 Mar 14 '19 at 17:27
  • Getting this error: `Error CS1503: Argument 1: cannot convert from 'System.Collections.Generic.IEnumerable>' to 'System.Collections.Generic.IDictionary' (CS1503) (Project1)` – Si8 Mar 14 '19 at 17:35
  • Thank you @Tamir Daniely – Si8 Mar 14 '19 at 17:35
  • That error is because I was writing in dotnet core which has some more options in the Dictionary constructor. See my edit for a version that works in .net framework. – Tamir Daniely Mar 14 '19 at 18:01
  • Ohhhhh I guess I have to get on the .NET Core bandwagon... Haven't done .NET in a while so I need to catch up. Thank you – Si8 Mar 14 '19 at 18:09
0

Use an IDictionary<string, string>. The key is an email address. The value is a group. Add a group for each email address. It’s ok for group to be added more than once. This makes your lookup simple.

Kit
  • 20,354
  • 4
  • 60
  • 103