David Strahm
David Strahm

Reputation: 273

Linq find first value of list in other list

A seemingly simple problem: I have a list of strings (read from a property file):

IList<string> defaultValues = new List<string> {"0458","0309"};

and another list of some Objects, each having a property of type string:

IList<Token> tokens = new List<Token>
{
    new Token {DisplayValue = "0123"},
    new Token {DisplayValue = "0309"},
    new Token {DisplayValue = "0203"},
    new Token {DisplayValue = "0458"},
    new Token {DisplayValue = "0911"}
};

public class Token
{
   public string DisplayValue { get; set; }
}

Now I would like to get the element of tokens where DisplayValue matches the first element of defaultValues (0485). If no element with DisplayValue 0485 is found, the second element in defaultvalues should match (0309) and so on.

The defaultValues List can be dynamic, so more values could be added, and always the first entry should have priority.

So the defaultValues list is sort of a priority list of strings, the lower the index the higher the priority. In the example above, the result should be "0458".

I could do something like this:

string result = string.Empty;

foreach (var searchValue in defaultValues)
{
    if (tokens.Any(token => token.DisplayValue == searchValue))
    {
        result = searchValue;
    }   
}

But I think something like this could be done more elegantly and with no foreach ...

Upvotes: 1

Views: 508

Answers (2)

SPFiredrake
SPFiredrake

Reputation: 3892

Using Any is a O(n^2) in this case, which is probably not the most performant. Probably the best way to do this would be to do a GroupJoin (making it O(n) instead) and then select the first token that was matched. Just keep in mind that GroupJoin only does it on exactly matching keys, so if you are looking for a comparison or substring search, you'll have to find other ways.

defaultValues
    .GroupJoin(
        tokens, // matching 0:n tokens per default value
        defVals => defVals, // key selector for our left source
        tks => tks.DisplayValue, // key selector for our right source
        (defVal, tks) => tks.FirstOrDefault()) // result selector for our matches
    .FirstOrDefault(match => match != null)

This has the advantage of only traversing each collection once, and matches all tokens with the corresponding DefaultValue (so you could have multiple matches per default value). You COULD do defaultValues.FirstOrDefault(x => tokens.Any(t => t == x)), but you'd run into the same problem (with possible O(n^2) complexity). From here, you can just check whether it's null (use null coalescing) and add a static constant called Empty in Token that initializes to new Token { DisplayValue = string.Empty }. After that you can do this:

(defaultValues
    .GroupJoin(
        ...
    .FirstOrDefault(match => match != null) ?? Token.Empty).DisplayValue

Upvotes: 2

Yusubov
Yusubov

Reputation: 5843

Basically you need to look for a projection from your Linq/Lambada result.

I would try something like this:

var macthes = tokens.Any(t => t.DisplayValue.In(defaultValues))
              .Select(y =>y.DisplayValue);

Upvotes: 0

Related Questions