Ciel
Ciel

Reputation: 4440

Using LINQ with a WHERE to ask for multiple possible results without redundant "||" commands

This is a cosmetics issue, it is syntactical sugar only, just because I want to do it. But basically I have a lot of code that reads like this ... I am using a dynamic for the sheer purpose of time saving and example.

var terms = new List<string> {"alpha", "beta", "gamma", "delta", "epsilon", "rho"};

var list = new List<dynamic> {
    new {
        Foo = new List<string> {
            "alpha",
            "gamma"
        }
    }
};

var result = list
     .Where(
          n => n.Foo.Contains(terms[0]) ||
               n.Foo.Contains(terms[1]) ||
               n.Foo.Contains(terms[2]) ||
               n.Foo.Contains(terms[3]) ||
               n.Foo.Contains(terms[4]) ||
               n.Foo.Contains(terms[5]))
     .ToList();

Obviously this is a ludicrous hyperbole for the sheer sake of example, more accurate code is ...

    Baseline =
        Math.Round(Mutations.Where(n => n.Format.Is("Numeric"))
            .Where(n => n.Sources.Contains("baseline") || n.Sources.Contains("items"))
            .Sum(n => n.Measurement), 3);

But the basic point is that I have plenty of places where I want to check to see if a List<T> (usually List<string>, but there may at times be other objects. string is my present focus though) contains any of the items from another List<T>.

I thought that using .Any() would work, but I actually haven't been able to get that to function as expected. So far, only excessive "||" is all that yields the correct results.

This is functionally fine, but it is annoying to write. I wanted to know if there is a simpler way to write this out - an extension method, or a LINQ method that perhaps I've not understood.

Update

I am really aware that this may be a duplicate, but I am having a hard time figuring out the wording of the question to a level of accuracy that I can find duplicates. Any help is much appreciated.

Solution

Thank you very much for all of the help. This is my completed solution. I am using a dynamic here just to save time on example classes, but several of your proposed solutions worked.

var terms = new List<string> {"alpha", "beta", "gamma", "delta", "epsilon", "rho"};

var list = new List<dynamic> {
    new  { // should match
        Foo = new List<string> {
            "alpha",
            "gamma"
        },
        Index = 0
    },
    new { // should match
        Foo = new List<string> {
            "zeta",
            "beta"
        },
        Index = 1
    },
    new { // should not match
        Foo = new List<string> {
            "omega",
            "psi"
        },
        Index = 2
    },
    new  { // should match
        Foo = new List<string> {
            "kappa",
            "epsilon"
        },
        Index = 3
    },
    new  { // should not match
        Foo = new List<string> {
            "sigma"
        },
        Index = 4
    }
};

var results = list.Where(n => terms.Any(t => n.Foo.Contains(t))).ToList();

// expected output - [0][1][3]
results.ForEach(result => {
    Console.WriteLine(result.Index);
});

Upvotes: 2

Views: 187

Answers (5)

Martin Liversage
Martin Liversage

Reputation: 106906

If performance is an issue when using Any() you can use a regular expression instead. Obviously, you should probably measure to make sure that regular expressions performs faster:

var terms = new List<string> { "alpha", "beta", "gamma", "delta", "epsilon", "rho" };
var regex = new Regex(string.Join("|", terms));
var result = list
             .Where(n => regex.Match(n.Foo).Success);

This assumes that joining the terms to a list creates a valid regular expression but with simple words that should not be a problem.

One advantage of using a regular expression is that you can require that the terms are surrounded by word boundaries. Also, the predicate in the Where clause may be easier to understand when compared to a solution using Contains inside Any.

Upvotes: 1

Grant Winney
Grant Winney

Reputation: 66479

You want to find out if Any element in one collection exists within another collection, so Intersect should work nicely here.

You can modify your second code snippet accordingly:

var sources = new List<string> { "baseline", "items" };

Baseline =
    Math.Round(Mutations.Where(n => n.Format.Is("Numeric"))
        .Where(n => sources.Intersect(n.Sources).Any())
        .Sum(n => n.Measurement), 3);

Regarding the part of the docs you quoted for "Intersect":

Produces the set intersection of two sequences by using the default equality comparer to compare values.

Every object can be compared to an object of the same type, to determine whether they're equal. If it's a custom class you created, you can implement IEqualityComparer and then you get to decide what makes two instances of your class "equal".

In this case, however, we're just comparing strings, nothing special. "Foo" = "Foo", but "Foo" ≠ "Bar"

So in the above code snippet, we intersect the two collections by comparing all the strings in the first collection to all the strings in the second collection. Whichever strings are "equal" in both collections end up in a resulting third collection.

Then we call "Any()" to determine if there are any elements in that third collection, which tells us there was at least one match between the two original collections.

Upvotes: 1

stovroz
stovroz

Reputation: 7075

I assume n.Foo is a string rather than a collection, in which case:

var terms = new List<string> { "alpha", "beta", "gamma", "delta", "epsilon", "rho" };

var list = (new List<string> { "alphabet", "rhododendron" })
    .Select(x => new { Foo = x });

var result = list.Where(x => terms.Any(y => x.Foo.Contains(y)));

Upvotes: 1

Sergey Kalinichenko
Sergey Kalinichenko

Reputation: 726849

I thought that using .Any() would work, but I actually haven't been able to get that to function as expected.

You should be able to make it work by applying Any() to terms, like this:

var result = list
         .Where(n => terms.Any(t => n.Foo.Contains(t)))
         .ToList();

Upvotes: 4

Christos
Christos

Reputation: 53958

You could try this one:

var result = list.Where(terms.Contains(n.Foo))
                 .ToList();

Upvotes: 1

Related Questions