简体   繁体   中英

Filtering a Key Value from a Dictionary in C#

I'm new to C# and am currently trying to figure out the best way to implement the following:

I have a list of species with an associated group:

Bird: "Budgie", "Parrot"
Dog: "Pitbull", "Labrador"
Cat: "Cheetah", "Lion"

Given a string of an animal, I need to return its group. eg. "Cheetah" would return "Cat".

I have implemented the following:

// create list one and store values
List<string> valSetOne = new List<string>();
valSetOne.Add("Budgie");
valSetOne.Add("Parrot");

// create list two and store values
List<String> valSetTwo = new List<String>();
valSetTwo.Add("Lion");
valSetTwo.Add("Cheetah");

// create list three and store values
List<String> valSetThree = new List<String>();
valSetThree.Add("Labrador");
valSetThree.Add("Pitbull");

// add values into map
map.Add("Bird", valSetOne);
map.Add("Cat", valSetTwo);
map.Add("Dog", valSetThree);

foreach(KeyValuePair<string, List<string>> kvp in map){
    foreach(string value in kvp.Value)
    {
       Console.WriteLine("Key = {0}, Value = {1}", kvp.Key, value);
    }
}

Instead of having a foreach loop, is there a faster way to find the key given an animal value?

//EDIT:

Currently trying to initialize with a Tuple

var tupleList = new List<Tuple<string, List<string>>>
{
   new Tuple<string, List<string>>("cat", { "cheetah", "lion"})

};

I'm getting the following error: Unexpected symbol `{'

If you don't want to use another way to store your data and you store them in a dictionary with keys the animal species, I would suggest you change the type of the value that is associated with each key, to a HashSet

Dictionary<string, HashSet<string>>

Doing so you can iterate through the keys ( O(n) , where n is the number of keys) of the dictionary and using HashSet Contains method in O(1) you can find out if the animal is associated with the current key/spiece or not.

A better modeling of the data will be to create a class of the group which will have a name and a collection of animals in it. Then you can hold a collection of groups and query it.

If you want to stick with the dictionary then: As your question is to retrieve the key for a given value out of the list of values for that key, I'd organize the data the other way around:

var map = new Dictionary<string,string>
{
    ["Cheetah"] = "Cat",
    ["Lion"] = "Cat",
    //....
};

Then you can search by your real key - which is the species and not the group:

if(map.TryGetValue("Lion", out var group)) { }

As it is easier to construct the data by grouping to list of species you can:

var rawData = new List<(string group, List<string> species)>
{
    ("Cat", new List<string> { "Cheetah", "Lion" }),
    //...
};

var result = rawData.SelectMany(item => item.species.Select(s => (s, item.group)))
                    .ToDictionary(k => k.s, v => v.group);

This works with C#7.0 named tuples. If you are using a previous version you can use anonymous types or "old fashion" tuples

For a pre C# 7.0 initialization of the collection:

var rawData = new List<Tuple<string, List<string>>>
{
    Tuple.Create<string,List<string>> ("Cat", new List<string> { "Cheetah", "Lion" })
};

Using your existing dictionary structure, you can do this:

string cheetahGroup = map.FirstOrDefault(item => 
    item.Value.Contains("cheetah", StringComparer.OrdinalIgnoreCase)).Key;

Note that if the animal doesn't exist, then cheetahGroup will be null . In the case above, it will be "Cat" .

As others have suggested I think you should use classes here. But other responses stop at 1 level of classes. I would recommend a more holistic object-oriented approach.

public abstract class Animal {}

public abstract class Dog : Animal {}
public abstract class Cat : Animal {}
public abstract class Bird : Animal {}

public sealed class Budgie : Bird {}
public sealed class Parrot : Bird {}
public sealed class Pitbull : Dog {}
public sealed class Labrador : Dog {}
public sealed class Cheetah : Cat {}
public sealed class Lion : Cat {}

[note that sealed is optional here]

then you just say

Labrador spot = new Labrador();

to check if it's a dog

if (spot is Dog) {
    print("it's a dog");
}

This solution has the option of being easily extensible, and super easy to read. There's not weird code that distracts you from the fact that a Labrador is a dog, and a budgie is a bird. It's very clear what you're trying to do and what classes Dog, Cat, Cheetah, etc represent.

If you further wanted to split up labradors into Chocolate, Yellow, and Black, you could just add another layer of classes inheriting from Labrador. If you needed to define the properties of the animals, that's easy too. For example if you only wanted Dogs and Cats to have names, you could add a name field in their classes, whereas if you want all animals to have a name you can put the name field in the Animal class.

If you needed to move the animals, you could at the animal level define a method called Move() that forces its children to override this method in their own way, so perhaps dogs would Move() by Walking(), whereas birds would Move() by Flying().

I would seriously recommending abandoning the dictionary and think about using something similar to what I described. If you start to think this way it will really help you plan out more complex projects and tasks. There's a reason Object Oriented Programming is so widespread!

I would make a class Animal , since you are working with animals, that would have two properties: Species and SubSpecies

public class Animal
{
    public string Species { get; set; }
    public string SubSpecies { get; set; }

    public Animal(string Species, string SubSpecies)
    {
        this.Species = Species;
        this.SubSpecies = SubSpecies;
    }
}

Then you can instanciate all your animals inside a list :

List<Animal> myAnimals = new List<Animal>();
myAnimals.Add(new Animal("Bird", "Budgie"));
myAnimals.Add(new Animal("Bird", "Parrot"));
myAnimals.Add(new Animal("Dog", "Pitbull"));
myAnimals.Add(new Animal("Dog", "Labrador"));
myAnimals.Add(new Animal("Cat", "Cheetah"));
myAnimals.Add(new Animal("Cat", "Lion"));    

And finally, you can use Linq to find whatever you're looking for:

//Will return null if the subSpecies doesn't exist
public string FindTheSpecies(string SubSpecies)
{
    return myAnimals.FirstOrDefault(x => x.SubSpecies == SubSpecies)?.Species;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM