Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Linq alternative to retrieving Lookup values by key (or partial key)

Tags:

c#

linq

I am working on the following code to find a "description" based on a search phrase that the user will specify. Think of the Keys as important phrases and the Values as a description on where to go to find that phrase.
Think of it as a store locator for example (best analogy I can think up). If you search "Target" (key), you'll get tons of cities (value) and some may have the same name. So there can be multiple Targets in the same city.
A Dictionary will obviously not work, because I'll either have duplicate store names, or possibly duplicate city names. Which brings me to my actual situation:

Basically, I'm starting from a List<KeyValuePair<string, string>> to allow for duplicates in both directions, then converting that to a Lookup<string, string> which I assumed would be less confusing than it is turning out to be.

List<KeyValuePair<string, string>> kvpList = new List<KeyValuePair<string, string>>();
Lookup<string, string> collection;

kvpList.Add(new KeyValuePair<string, string>("K1", "R1"));
kvpList.Add(new KeyValuePair<string, string>("K1", "R1"));
kvpList.Add(new KeyValuePair<string, string>("K1", "R2"));
kvpList.Add(new KeyValuePair<string, string>("K2", "R1"));
kvpList.Add(new KeyValuePair<string, string>("K2", "R2"));
kvpList.Add(new KeyValuePair<string, string>("K2", "R3"));
kvpList.Add(new KeyValuePair<string, string>("K2", "R1"));

collection = (Lookup<string,string>)kvpList.ToLookup(k => k.Key, k => k.Value);

The above is simply fake test information, but I feel like there must be a cleaner way to get the results from the Lookup especially since it appears to be very Linq-friendly. Unfortunately, I am quite unfamiliar with Linq, and the syntax of Linq itself doesn't seem to lend itself to be very beginner-friendly. I get the results in this code (hard coded search term just for testing purposes):

string searchTerm = "K2";
List<string> uniqueResults = new List<string>();

foreach (var item in collection)
{
    if (item.Key.Contains(searchTerm))
    {
        foreach (var value in item)
        {
            if (!uniqueResults.Contains(value))
            {
                uniqueResults.Add(value);
                Console.WriteLine("Added: " + value);
            }
            else
            {
                Console.WriteLine("Skipped duplicate: " + value);
            }
        }
    }
}  

I don't have issues with the above code, but my question is: Is there a way to use Linq to achieve what I am trying to accomplish? I feel like what I have is not the best way this could be done...
It may be worth noting that a partial searchTerm must also be able to find results where it occurs in the key (hence contains). Existing answers weren't quite able to answer my specific questions. I was not able to find one to help me get values by a partial key search.

like image 952
Broots Waymb Avatar asked Oct 26 '15 16:10

Broots Waymb


2 Answers

Your code translated to LINQ would look like this:

var uniqueResults = collection
        .Where(item => item.Key.Contains(searchTerm)) // filter the collection
        .SelectMany(x => x)                           // flatten results
        .Distinct()                                   // remove duplicates
        .ToList();

You don't even need the Lookup. You can get the same results with kvpList:

var uniqueResults = kvpList
        .Where(item => item.Key.Contains(searchTerm)) // filter the collection
        .Select(item => item.Value)                   // get the Values from KeyValuePairs
        .Distinct()                                   // remove duplicates
        .ToList();

The LINQ solution is actually much easier to understand than the imperative one. Try to describe your algorithm in English: From kvpList, select distinct values where key contains the search term. That's almost exactly the 2nd LINQ code.

like image 127
Jakub Lortz Avatar answered Oct 09 '22 16:10

Jakub Lortz


You could use LINQ and a HashSet<string>, which would eliminate duplicates for you:

var uniqueResults = collection.Where(item => item.Contains(searchTerm))
                              .SelectMany(x => x)
                              .ToHashSet();

Where ToHashSet is a custom extension method you can create easily:

public static class EnumerableExtensions
{
    public static HashSet<T> ToHashSet(this IEnumerable<T> enumerable)
    {
        return new HashSet<T>(enumerable);
    }
}
like image 31
Yuval Itzchakov Avatar answered Oct 09 '22 18:10

Yuval Itzchakov