asdf101
asdf101

Reputation: 669

Finding values in a string:string dictionary where a given filter string is a subset of the key string

I have a Dictionary<string, string> object where values are stored that look like this:

examplePlanet                   : defaultText0
examplePlanet*                  : defaultText1
examplePlanet**                 : defaultText2
examplePlanetSpecificlocationA  : specificAText0
examplePlanetSpecificlocationA* : specificAText1
examplePlanetSpecificlocationB  : specificBText

And I have a string filter that matches one of these keys or is a subset of a key. This filter has the form planetLocation, which can be split into planet and location.

My goal is to create a list of values where the filter is matched in this way: if planetLocation exists in the dictionary, add its value, and all values where the key matches but has extra *'s, to a list. If planetLocation does not exist, only add values where the key matches the planet part of the filter (with possible extra *'s).

Basically, I want all values where the filter matches the key as much as possible.


Examples:

examplePlanetSpecificlocationA gives [specificAText0, specificAText1]
examplePlanetSpecificlocationB gives [specificBText]
examplePlanetSpecificlocationC gives [defaultText0, defaultText1, defaultText2]


I have already tried (among other things that didn't work) this:

private List<string> filteredResults;

///<summary>Filters dictionaries and returns a list of values</summary>
private List<string> GetFilteredResults(Dictionary<string, string> inputdictionary, string filter)
{
    List<string> _filteredResults = new List<string>();
    foreach (KeyValuePair<string, string> entry in inputdictionary)
    {
        if (entry.Key.Contains(filter))
        {
            _filteredResults.Add(entry.Value);
        }
    }
    return _filteredResults;
}

public void main()
{
    //stuff happens here that assigns a value to filterPlanet and filterLocation

    filteredResults = new List<string>();
    filteredResults = GetFilteredResults(exampledictionary, filterPlanet + filterLocation);
    if (filteredResults.Count == 0)
    {
        filteredResults = GetFilteredResults(exampledictionary, filterPlanet);
    }

    //do stuff with the filtered results
}

This almost worked, but returns all values where the key contains filterPlanet and not just filterPlanet itself plus possible *'s. I'm not sure how to make this function do what I want, and even if it somehow works I'm sure there is a more efficient way of filtering than this. Could you please help me here?

Upvotes: 1

Views: 447

Answers (1)

canton7
canton7

Reputation: 42245

I wouldn't use the *'s as a way of differentiating multiple values for the same key, and I'd keep filterPlanet and filterLocation separate. That way you can use a simple O(1) dictionary lookup, rather than iterating across all keys, doing substring searching, etc.

public class PlanetFilterer
{
    private readonly Dictionary<string, List<string>> lookup = new Dictionary<string, List<string>>();

    public PlanetFilterer(IEnumerable<(string filter, string value)> filters)
    {
        foreach (var (filter, value) in filters)
        {
            var filterWithoutStars = filter.TrimEnd('*');
            if (!lookup.TryGetValue(filterWithoutStars, out var values))
            {
                values = new List<string>();
                lookup[filterWithoutStars] = values;
            }
            values.Add(value);
        }
    }

    public IReadOnlyList<string> Lookup(string planet, string location)
    {
        List<string> results;
        if (lookup.TryGetValue(planet + location, out results))
        {
            return results; 
        }
        if (lookup.TryGetValue(planet, out results))
        {
            return results;
        }
        return Array.Empty<string>();
    }
}

Usage:

var filters = new[]
{
    ("examplePlanet", "defaultText0"),
    ("examplePlanet*", "defaultText1"),
    ("examplePlanet**", "defaultText2"),
    ("examplePlanetSpecificlocationA", "specificAText0"),
    ("examplePlanetSpecificlocationA*", "specificAText1"),
    ("examplePlanetSpecificlocationB", "specificBText"),
};
var filterer = new PlanetFilterer(filters);

Console.WriteLine(string.Join(", ", filterer.Lookup("examplePlanet", "SpecificlocationA")));
Console.WriteLine(string.Join(", ", filterer.Lookup("examplePlanet", "SpecificlocationB")));
Console.WriteLine(string.Join(", ", filterer.Lookup("examplePlanet", "SpecificlocationC")));

Try it online

Upvotes: 3

Related Questions