grenade
grenade

Reputation: 32169

Check if any item in a list matches any item in another list

A coleague asked me to write a one-liner to replace the following method:

public static bool IsResourceAvailableToUser(IEnumerable<string> resourceRoles, IEnumerable<string> userRoles)
{
    foreach (var userRole in userRoles)
        foreach (var resourceRole in resourceRoles)
            if (resourceRole == userRole)
                return true;
    return false;
}

Resharper and I came up with this:

public static bool IsResourceAvailableToUser(IEnumerable<string> resourceRoles, IEnumerable<string> userRoles)
{
    return userRoles.Where(resourceRoles.Contains).Count() > 0;
}

Is there a better way?

Upvotes: 13

Views: 4634

Answers (2)

Zachary Dow
Zachary Dow

Reputation: 1947

You could write a generic extension method that would handle many cases. The meat of the function itself is one line.

/// <summary>
/// Compares both lists to see if any item in the enumerable 
/// equals any item in the other enumerable. 
/// </summary>
public static bool AnyItem<T>(this IEnumerable<T> source, IEnumerable<T> other, IEqualityComparer<T> comparer = null)
{
    return (comparer == null ? source.Intersect(other) : source.Intersect(other, comparer)).Any();
}

Older, Less efficient answer

public static bool AnyItem<T>(this IEnumerable<T> source, IEnumerable<T> other)
{
    return source.Any(s => other.Any(o => EqualityComparer<T>.Default.Equals(s, o)));
}

I think this is also more efficient than the current answer (It's not). I would have to check if getting the EqualityComparer is expensive, but I'm willing to doubt it.


You could also extend this function to accept an Expression that would evaluate what properties to compare for enumerables that contain objects.

public static bool AnyItem<T, TResult>(
        this IEnumerable<T> source, 
        IEnumerable<T> other, 
        Expression<Func<T, TResult>> compareProperty = null)
{
    if (compareProperty == null)
    {
        return source.Any(s => other.Any(o => EqualityComparer<T>.Default.Equals(s, o)));
    }

    return source.Any(s => other.Any(o => 
                    EqualityComparer<TResult>.Default.Equals(
                    s.GetPropertyValue(compareProperty),
                    o.GetPropertyValue(compareProperty))));
}

public static TValue GetPropertyValue<TTarget, TValue>(
    this TTarget target, Expression<Func<TTarget, TValue>> memberLamda)
{
    var memberSelectorExpression = memberLamda.Body as MemberExpression;
    var property = memberSelectorExpression?.Member as PropertyInfo;
    return (TValue)property?.GetValue(target);
}

Upvotes: 3

Jon Skeet
Jon Skeet

Reputation: 1504122

Given LINQ, yes:

return userRoles.Intersect(resourceRoles).Any();

Note that aside from the use of Intersect to turn it into O(m) + O(n) instead O(m * n), using Any is more efficient than using Count() > 0 - you know the answer as soon as you've found the first match.

Upvotes: 23

Related Questions