如何检查所有列表项是否具有相同的值并返回该值,如果它们不具有相同的值,则返回“otherValue”?

157

在你那有点俏皮的注意力引导方面,我会选择Ani的答案。https://dev59.com/CW855IYBdhLWcg3wXC9-#4390285 - Binary Worrier
9个回答

200
var val = yyy.First().Value;
return yyy.All(x=>x.Value == val) ? val : otherValue; 

我能想到的最简洁的方式。您可以通过将val内联来使其成为单行,但是First()将被评估n次,这将使执行时间翻倍。

要纳入评论中指定的“空集”行为,只需在上述两行之前添加一行:

if(yyy == null || !yyy.Any()) return otherValue;

1
+1,使用.Any是否可以在存在不同值的情况下使枚举提前退出? - Jeff Ogata
14
当序列中的元素 x 的值不等于给定值 val 时,All 方法会立即终止。同理,当序列中存在一个元素 x,其值不等于给定值 val 时,Any(x => x.Value != val) 方法也会立即终止。换句话说,AllAny 方法都表现出类似于逻辑运算符中的“短路”行为,类似于 &&|| 运算符(这实际上就是 AllAny 方法所代表的)。 - jason
@Jason:没错。All(condition) 实际上等同于 !Any(!condition),并且只要有一个条件满足,两者的计算都会立即终止。 - KeithS
7
微优化:return yyy.Skip(1).All(x=>x.Value == val) ? val : otherValue; - Caltor
@Caltor 鉴于 Skip 很可能会分配内存,因此不进行微观优化。 - Stefan Glienke

131

一个适用于判断多个值是否相等的简单而快速的测试:

collection.Distinct().Count() == 1

3
在我看来,这个方案比KeithS的方案更干净。如果想允许空集合,可以考虑使用collection.Distinct().Count() <= 1 - 3dGrabber
6
小心,.Distinct() 不总是按预期工作 —— 特别是在处理对象时,请参见 问题。在这种情况下,您需要实现 IEquatable 接口。 - Matt
21
更加清晰,但是平均情况下性能较差;Distinct()保证遍历集合中的每个元素一次,在最坏情况下,即每个元素都不同的情况下,Count()会遍历整个列表两次。Distinct()还创建了一个HashSet,因此其行为可以是线性的,而不是NlogN或更糟,并且这将增加内存使用量。All()在最坏情况下需要遍历整个集合,即所有元素相等,但不会创建任何新的集合。 - KeithS
2
@KeithS 正如您现在所了解的那样,Distinct不会遍历整个集合,而Count将通过Distinct的迭代器进行一次遍历。 - NetMage
3
可以在Distinct中使用相等比较器来处理特定类。或者,按照我选择使用此答案的方式,可以使用Select首先映射到目标属性,例如:collection.Select(x => x.Property).Distinct().Count() == 1 - PJRobot
显示剩余2条评论

24

虽然你肯定可以使用现有的序列操作符构建这样的设备,但在这种情况下,我倾向于将其编写为自定义序列操作符。类似于:

// Returns "other" if the list is empty.
// Returns "other" if the list is non-empty and there are two different elements.
// Returns the element of the list if it is non-empty and all elements are the same.
public static int Unanimous(this IEnumerable<int> sequence, int other)
{
    int? first = null;
    foreach(var item in sequence)
    {
        if (first == null)
            first = item;
        else if (first.Value != item)
            return other;
    }
    return first ?? other;
}

这非常清晰、简洁,涵盖了所有情况,并且不会不必要地创建序列的额外迭代。

将其转换为适用于 IEnumerable<T> 的通用方法留作练习。:-)


举个例子,假设您有一系列可空值,并且提取的值也是可空的。在这种情况下,序列可能为空,或者序列中的每个项目都可能在提取的值中具有null。在这种情况下,合并将在实际上null是(可能)正确响应时返回“other”。假设函数是T Unanimous<U, T>(this IEnumerable<U> sequence, T other)或类似签名,这会使它变得有点复杂。 - Anthony Pegram
@Anthony:确实,这里存在许多复杂的情况,但它们很容易被解决。我使用可空int是为了方便,这样我就不必声明一个“我已经看过第一个项目”的标志。你也可以轻松地声明标志。另外,我使用“int”而不是T,因为我知道你总是可以比较两个int是否相等,而对于两个T来说则不是这种情况。这更像是一个解决方案的草图,而不是完全功能的通用解决方案。 - Eric Lippert

16
return collection.All(i => i == collection.First())) 
    ? collection.First() : otherValue;.

如果你担心为每个元素执行First()(这可能是一个有效的性能问题):

var first = collection.First();
return collection.All(i => i == first) ? first : otherValue;

@KeithS - 这就是为什么我添加了我的答案的第二部分。在小集合上,调用First()是微不足道的。但是在大集合上,这可能会成为一个问题。 - Justin Niessner
1
“对于小集合,调用First()是微不足道的。” - 这取决于集合的来源。对于简单对象的列表或数组,你说得很对。但是,有些可枚举对象不是基元有限内存缓存集合。对于委托的集合,或经过算法级数计算产生一系列值的枚举器(例如斐波那契数列),每次评估First()都会变得非常昂贵。 - KeithS
6
更糟糕的是,如果查询是一个数据库查询,并且每次调用“First”都会再次访问数据库。 - Eric Lippert
1
当你有像从文件中读取这样的一次性迭代时,情况会变得更糟。因此,Ani在其他线程中的答案看起来是最好的。 - Alexei Levenkov
@Eric - 别介意啦,每个元素访问数据库三次没有任何问题...:-P - Justin Niessner

4
这可能有点晚了,但是根据Eric的回答,这个扩展可以适用于值类型和引用类型:
public static partial class Extensions
{
    public static Nullable<T> Unanimous<T>(this IEnumerable<Nullable<T>> sequence, Nullable<T> other, IEqualityComparer comparer = null)  where T : struct, IComparable
    {
        object first = null;
        foreach(var item in sequence)
        {
            if (first == null)
                first = item;
            else if (comparer != null && !comparer.Equals(first, item))
                return other;
            else if (!first.Equals(item))
                return other;
        }
        return (Nullable<T>)first ?? other;
    }

    public static T Unanimous<T>(this IEnumerable<T> sequence, T other, IEqualityComparer comparer = null)  where T : class, IComparable
    {
        object first = null;
        foreach(var item in sequence)
        {
            if (first == null)
                first = item;
            else if (comparer != null && !comparer.Equals(first, item))
                return other;
            else if (!first.Equals(item))
                return other;
        }
        return (T)first ?? other;
    }
}

2
public int GetResult(List<int> list){
int first = list.First();
return list.All(x => x == first) ? first : SOME_OTHER_VALUE;
}

1

使用LINQ的替代方法:

var set = new HashSet<int>(values);
return (1 == set.Count) ? values.First() : otherValue;

我发现对于长度不超过6,000个整数的列表,使用HashSet<T>比以下方法更快:

var value1 = items.First();
return values.All(v => v == value1) ? value1: otherValue;

首先,这可能会创建大量垃圾。此外,它比其他LINQ答案不够清晰,但比扩展方法答案慢。 - Ian Ringrose
1
是的。然而,如果我们要确定一小组值是否全部相同,那么垃圾量不会太多。当我在 LINQPad 中运行这个和一个 LINQ 语句来处理一小组值时,使用 HashSet 更快(使用 Stopwatch 类计时)。 - Ɖiamond ǤeezeƦ
如果你在命令行中以发布版本运行它,可能会得到不同的结果。 - Ian Ringrose
1
创建了一个控制台应用程序并发现在我的答案中使用HashSet<T>最初比使用LINQ语句更快。然而,如果我在循环中执行此操作,则LINQ更快。 - Ɖiamond ǤeezeƦ
这个解决方案的一个问题是,如果你使用自定义类,就必须实现自己的 GetHashCode() 方法,而这很难正确地做到。请参考:https://dev59.com/yXRC5IYBdhLWcg3wOeSB#371348 获取更多细节。 - Cameron

-2

对上述简化方法的轻微变化。

var result = yyy.Distinct().Count() == yyy.Count();


5
这恰恰相反。这将检查列表中的每个元素是否唯一。 - Mario Galea

-5
如果一个数组是多维的,就像下面这样,那么我们必须编写下面的linq来检查数据。
例如:这里的元素都是0,我正在检查所有值是否都为0。 ip1 = 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
    var value=ip1[0][0];  //got the first index value
    var equalValue = ip1.Any(x=>x.Any(xy=>xy.Equals()));  //check with all elements value 
    if(equalValue)//returns true or false  
    {  
    return "Same Numbers";  
    }else{  
    return "Different Numbers";   
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接