¿Cómo comprobar si todos los elementos de la lista tienen el mismo valor y devolverlo, o devolver un "otherValue" si no es así?

122

Si todos los elementos de una lista tienen el mismo valor, entonces necesito usar ese valor; de lo contrario, necesito usar un "otherValue". No puedo pensar en una forma simple y clara de hacer esto.

Consulte también Forma ordenada de escribir un bucle que tiene una lógica especial para el primer elemento de una colección.

Ian Ringrose
fuente
En su captador de atención bastante descarado, iría con la respuesta de Ani stackoverflow.com/questions/4390232/…
Binary Worrier
5
¿Qué quiere que suceda si no hay un primer valor porque la lista está vacía? En ese caso, es cierto que "todos los elementos de la lista tienen el mismo valor". Si no me cree, ¡búsqueme uno que no lo tenga! No define qué hacer en esta situación. ¿Debería lanzar una excepción, devolver el valor "otro" o qué?
Eric Lippert
@Eric, lo siento cuando la lista está vacía, debería devolver el valor "otro"
Ian Ringrose

Respuestas:

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

La forma más limpia que se me ocurre. Puede convertirlo en una línea introduciendo val, pero First () se evaluaría n veces, duplicando el tiempo de ejecución.

Para incorporar el comportamiento de "conjunto vacío" especificado en los comentarios, simplemente agregue una línea más antes de las dos anteriores:

if(yyy == null || !yyy.Any()) return otherValue;
KeithS
fuente
1
+1, ¿el uso .Anypermitiría que la enumeración salga temprano en los casos en que hay valores diferentes?
Jeff Ogata
12
@adrift: Allterminará tan pronto como llegue a un elemento xde la secuencia para la cual x.Value != val. De manera similar, Any(x => x.Value != val)terminaría tan pronto como golpee un elemento xde la secuencia para la cual x.Value != val. Es decir, ambos Ally Anyexhiben "cortocircuito" análogo a &&y ||(que es efectivamente lo que Ally Anyson).
Jason
@Jason: exactamente. ¡Todo (condición) es efectivamente! Cualquier (! Condición), y la evaluación de cualquiera de ellos terminará tan pronto como se conozca la respuesta.
KeithS
4
Microoptimización:return yyy.Skip(1).All(x=>x.Value == val) ? val : otherValue;
Caltor
101

Buena prueba rápida para todos iguales:

collection.Distinct().Count() == 1
Jeremy Bell
fuente
1
Esto no funcionará con cualquiera Class, aunque debería funcionar con estructuras. Sin embargo, genial para una lista de primitivas.
Andrew Backer
2
+1 mucho más limpio que la solución de KeithS en mi opinión. Es posible que desee utilizar collection.Distinct().Count() <= 1 si desea permitir colecciones vacías.
3dGrabber
4
Tenga cuidado, .Distinct()no siempre funciona como se esperaba, especialmente cuando trabaja con objetos, consulte esta pregunta. En esos casos, debe implementar la interfaz IEquatable.
Matt
16
Más limpio, sí, pero menos eficaz en el caso medio; Se garantiza que Distinct () recorrerá cada elemento de la colección una vez, y en el peor de los casos en que cada elemento sea diferente, Count () recorrerá la lista completa dos veces. Distinct () también crea un HashSet para que su comportamiento pueda ser lineal y no NlogN o algo peor, y eso inflará el uso de la memoria. All () realiza una pasada completa en el peor de los casos en que todos los elementos sean iguales y no crea ninguna colección nueva.
KeithS
1
@KeithS Como espero que se dé cuenta a estas alturas, Distinctno atravesará la colección en absoluto y Counthará un recorrido a través Distinctdel iterador.
NetMage
22

Aunque ciertamente puede construir un dispositivo de este tipo a partir de operadores de secuencia existentes, en este caso me inclinaría a escribir este como un operador de secuencia personalizado. Algo como:

// 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;
}

Eso es bastante claro, breve, cubre todos los casos y no crea innecesariamente iteraciones adicionales de la secuencia.

Convertir esto en un método genérico que funcione IEnumerable<T>se deja como ejercicio. :-)

Eric Lippert
fuente
Digamos, por ejemplo, que tiene una secuencia de valores nulos y el valor extraído también es un valor anulable. En cuyo caso, la secuencia podría estar vacía o todos los elementos de la secuencia podrían tener un valor nulo en el valor extraído. La fusión, en este caso, devolvería el othercuando en nullrealidad era la respuesta (presumiblemente) correcta. Digamos que la función era T Unanimous<U, T>(this IEnumerable<U> sequence, T other)o alguna firma similar, eso lo complica un poco.
Anthony Pegram
@Anthony: De hecho, hay muchas complicaciones aquí, pero se solucionan con bastante facilidad. Estoy usando un int que acepta valores NULL como una conveniencia para no tener que declarar un indicador "Ya he visto el primer elemento". Fácilmente podría declarar la bandera. También estoy usando "int" en lugar de T porque sé que siempre puedes comparar dos ints para la igualdad, que no es el caso de dos Ts. Esto es más un boceto de una solución que una solución genérica completamente funcional.
Eric Lippert
13
return collection.All(i => i == collection.First())) 
    ? collection.First() : otherValue;.

O si le preocupa ejecutar First () para cada elemento (lo que podría ser un problema de rendimiento válido):

var first = collection.First();
return collection.All(i => i == first) ? first : otherValue;
Justin Niessner
fuente
@KeithS - Por eso agregué la segunda parte de mi respuesta. En colecciones pequeñas, llamar a First () es trivial. En colecciones grandes, eso podría comenzar a ser un problema.
Justin Niessner
1
"En colecciones pequeñas, llamar a First () es trivial". - Eso depende de la fuente de la colección. Para una lista o matriz de objetos simples, tiene toda la razón. Sin embargo, algunos enumerables no son conjuntos finitos de primitivos almacenados en memoria caché. Una colección de delegados, o un enumerador que rinde a través de un cálculo algorítmico en serie (por ejemplo, Fibonacci), sería muy costoso evaluar First () cada vez.
KeithS
5
O peor aún, si la consulta es una consulta de base de datos y llamar a "Primero" llega a la base de datos nuevamente cada vez.
Eric Lippert
1
Se pone peor cuando tiene una iteración única, como leer desde un archivo ... Entonces, la respuesta de Ani de otro hilo se ve mejor.
Alexei Levenkov
@Eric - Vamos. No hay nada de malo en acceder a la base de datos tres veces para cada elemento ...
:-P
3

Esto puede llegar tarde, pero es una extensión que funciona tanto para los tipos de valor como de referencia según la respuesta de 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;
    }
}
batta
fuente
1
public int GetResult(List<int> list){
int first = list.First();
return list.All(x => x == first) ? first : SOME_OTHER_VALUE;
}
hacker
fuente
1

Una alternativa al uso de LINQ:

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

He descubierto que el uso HashSet<T>es más rápido para listas de hasta ~ 6000 enteros en comparación con:

var value1 = items.First();
return values.All(v => v == value1) ? value1: otherValue;
Ɖiamond ǤeezeƦ
fuente
En primer lugar, esto puede generar mucha basura. Además, es menos claro que las otras respuestas LINQ, pero más lento que las respuestas del método de extensión.
Ian Ringrose
Cierto. Sin embargo, no habrá mucha basura si hablamos de determinar si un pequeño conjunto de valores son todos iguales. Cuando ejecuté esto y una declaración LINQ en LINQPad para un pequeño conjunto de valores, HashSet fue más rápido (cronometrado usando la clase Stopwatch).
Ɖiamond ǤeezeƦ
Si lo ejecuta en una versión de versión desde la línea de comandos, puede obtener resultados diferentes.
Ian Ringrose
Creé una aplicación de consola y descubrí que HashSet<T>inicialmente es más rápido que usar las declaraciones LINQ en mi respuesta. Sin embargo, si hago esto en un bucle, LINQ es más rápido.
Ɖiamond ǤeezeƦ
El gran problema con esta solución es que si está utilizando sus clases personalizadas, debe implementar las suyas propias GetHashCode(), lo cual es difícil de hacer correctamente. Consulte: stackoverflow.com/a/371348/2607840 para obtener más detalles.
Cameron
0

Una ligera variación del enfoque simplificado anterior.

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

David Ehnis
fuente
3
Esto es exactamente al revés. Esto comprobará que todos los elementos de la lista sean únicos.
Mario Galea
-1

Si una matriz es de tipo multidimensional como a continuación, tenemos que escribir a continuación linq para verificar los datos.

ejemplo: aquí los elementos son 0 y estoy comprobando que todos los valores son 0 o no.
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";   
    }
Pradeep Kumar Das
fuente