Check that all items of IEnumerable<T?> has the same value using LINQ
Asked Answered
D

4

14

I have a nullable type, e.g. SomeEnum? and a set of values, e.g. IEnumerable<SomeEnum?>.

How to check that all items has the same value using LINQ (and get this value)?

Dementia answered 4/12, 2010 at 17:48 Comment(0)
L
11
public static bool AllEqual<T>(this IEnumerable<T?> values) where T : struct
{
        if (!values.Any()) return true;
        T? first = values.First();
        return values.Skip(1).All(v => first.Equals(v));
}

EDIT: To get the value you could return a tuple (success, value) like this:

public Tuple<bool, T?> AllEqual<T>(IEnumerable<T?> values) where T : struct
{
    if(! values.Any()) return Tuple.Create(true, (T?)null);
    T? first = values.First();
    bool equal = values.Skip(1).All(v => v.Equals(first));
    return Tuple.Create(equal, equal ? first : (T?)null);
}

or you could use an out parameter instead:

public static bool AllEqual<T>(this IEnumerable<T?> values, out T? value) where T : struct
{
    value = null;
    if (!values.Any()) return true;

    T? first = values.First();
    value = first;

    return values.Skip(1).All(v => first.Equals(v));
}
Low answered 4/12, 2010 at 18:15 Comment(8)
This is preferable for LINQ to Objects because it will bail as soon as it finds one value that isn't equal. In LINQ to Entities and such, it will cause 3 round-trips, though.Royalroyalist
It doesn't return the value, note.Cookhouse
@stripling - all the answers on this question short-circuit. And EF will treat this as L2O anyway, as it doesn't use Queryable unless the interface is IQueryableCookhouse
@Marc Gravell: Good point. I didn't know whether Distinct was lazy-evaluated for sure. Should've assumed it was.Royalroyalist
@stripling - and as a point of note, since not all sequences are repeatable, even in L2O you should avoid multiple readsCookhouse
I would be happy to use 4.0 but unfortunately locked with 3.5 (added the appropriate tag)Dementia
What does !values.All() do?Dementia
@Dementia - Assuming you meant !values.Any() that checks for an empty sequence - this implementation assumes that all the items in an empty sequence are empty and the 'value' is null. You may want to throw an exception instead. values.All(...) returns whether each item in the sequence meets some condition - in this case whether each other item is equal to the first (and hence are all equal).Low
D
24
data.Distinct().Count() == 1;

works well too.

Desex answered 18/3, 2011 at 4:48 Comment(1)
Great idea. And can be wrapped in the same extension method as Lee's wayDementia
L
11
public static bool AllEqual<T>(this IEnumerable<T?> values) where T : struct
{
        if (!values.Any()) return true;
        T? first = values.First();
        return values.Skip(1).All(v => first.Equals(v));
}

EDIT: To get the value you could return a tuple (success, value) like this:

public Tuple<bool, T?> AllEqual<T>(IEnumerable<T?> values) where T : struct
{
    if(! values.Any()) return Tuple.Create(true, (T?)null);
    T? first = values.First();
    bool equal = values.Skip(1).All(v => v.Equals(first));
    return Tuple.Create(equal, equal ? first : (T?)null);
}

or you could use an out parameter instead:

public static bool AllEqual<T>(this IEnumerable<T?> values, out T? value) where T : struct
{
    value = null;
    if (!values.Any()) return true;

    T? first = values.First();
    value = first;

    return values.Skip(1).All(v => first.Equals(v));
}
Low answered 4/12, 2010 at 18:15 Comment(8)
This is preferable for LINQ to Objects because it will bail as soon as it finds one value that isn't equal. In LINQ to Entities and such, it will cause 3 round-trips, though.Royalroyalist
It doesn't return the value, note.Cookhouse
@stripling - all the answers on this question short-circuit. And EF will treat this as L2O anyway, as it doesn't use Queryable unless the interface is IQueryableCookhouse
@Marc Gravell: Good point. I didn't know whether Distinct was lazy-evaluated for sure. Should've assumed it was.Royalroyalist
@stripling - and as a point of note, since not all sequences are repeatable, even in L2O you should avoid multiple readsCookhouse
I would be happy to use 4.0 but unfortunately locked with 3.5 (added the appropriate tag)Dementia
What does !values.All() do?Dementia
@Dementia - Assuming you meant !values.Any() that checks for an empty sequence - this implementation assumes that all the items in an empty sequence are empty and the 'value' is null. You may want to throw an exception instead. values.All(...) returns whether each item in the sequence meets some condition - in this case whether each other item is equal to the first (and hence are all equal).Low
G
10

Lazy and returns true for empty sequences:

public static bool AllEqual<T>(this IEnumerable<T> source, out T value)
{
    using (var enumerator = source.GetEnumerator())
    {
        if (!enumerator.MoveNext())
        {
            value = default(T);
            return true;
        }

        value = enumerator.Current;
        var comparer = EqualityComparer<T>.Default;

        while (enumerator.MoveNext())
        {
            if (!comparer.Equals(value, enumerator.Current))
            {
                return false;
            }
        }

        return true;
    }
}
Gomphosis answered 2/10, 2015 at 14:36 Comment(3)
+1 for avoiding an intermediate list like Distinct and multiple iterations like the first answer, which some sequences don't support. Definitely the best implementation in this list.Teston
I agree with Mike - really good solution. But doesn't use LINQ, as question's author asked.Approachable
@Approachable It does "use LINQ" in the sense that you can call it as something.Select(...).AllEqual(). The only problem here is that while it correctly returns true for empty sequences, it also returns default(T) for the value in that case, so an empty sequence is indistinguishable from a sequence full on nulls. It probably should have two out parameters, the second one to say that there were elements in the first place.Ruggiero
C
6
var value = data.Distinct().Single();

(which will throw an exception if there isn't exactly one unique value, otherwise it will return that value)

If you don't want exceptions:

var few=data.Distinct().Take(2).ToList();
if(few.Count==1) {
    var value = few[0];
    ...
}
Cookhouse answered 4/12, 2010 at 17:52 Comment(4)
Take(2) will throw an exception if Distinct() returns 1-length collection, will it not?Dementia
@abatishchev: No, it won't. It works on a "best-effort" basis.Lorentz
@Dementia Take(n) is take at most, not take exactlyCookhouse
@Dementia - also note these are sequences, not collections (until ToList). A subtle distinction, perhaps - until you get high volumes...Cookhouse

© 2022 - 2024 — McMap. All rights reserved.