Comment vérifier si tous les éléments de la liste ont la même valeur et la renvoyer, ou renvoyer une «otherValue» si ce n'est pas le cas?

122

Si tous les éléments d'une liste ont la même valeur, alors je dois utiliser cette valeur, sinon je dois utiliser une «otherValue». Je ne peux pas penser à une manière simple et claire de faire cela.

Voir aussi Méthode soignée pour écrire une boucle qui a une logique spéciale pour le premier élément d'une collection.

Ian Ringrose
la source
Sur votre capteur d'attention plutôt effronté, j'irais avec la réponse d'Ani stackoverflow.com/questions/4390232/...
Binary Worrier
5
Que voulez-vous qu'il se passe s'il n'y a pas de première valeur parce que la liste est vide? Dans ce cas, il est vrai que "tous les éléments de la liste ont la même valeur" - si vous ne me croyez pas, trouvez-m'en un qui n'en a pas! Vous ne définissez pas quoi faire dans cette situation. Cela devrait-il lever une exception, renvoyer la valeur «autre», ou quoi?
Eric Lippert
@Eric, désolé quand la liste est vide, elle devrait renvoyer la valeur "autre"
Ian Ringrose

Réponses:

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

La manière la plus propre que je puisse imaginer. Vous pouvez en faire une ligne unique en insérant val, mais First () serait évalué n fois, doublant le temps d'exécution.

Pour incorporer le comportement "ensemble vide" spécifié dans les commentaires, il vous suffit d'ajouter une ligne supplémentaire avant les deux ci-dessus:

if(yyy == null || !yyy.Any()) return otherValue;
KeithS
la source
1
+1, utiliserait .Any permettrait à l'énumération de s'arrêter tôt dans les cas où les valeurs diffèrent?
Jeff Ogata
12
@adrift: Allse terminera dès qu'il atteindra un élément xde la séquence pour laquelle x.Value != val. De même, Any(x => x.Value != val)se terminerait dès qu'il atteint un élément xde la séquence pour laquelle x.Value != val. Autrement dit, les deux Allet Anyprésentent un "court-circuit" analogue à &&et ||(qui est effectivement ce que Allet Anysont).
jason
@Jason: exactement. Toute (condition) est effectivement! Toute (! Condition), et l'évaluation de l'une ou l'autre se terminera dès que la réponse sera connue.
KeithS
4
Microoptimisation:return yyy.Skip(1).All(x=>x.Value == val) ? val : otherValue;
Caltor
101

Bon test rapide pour tous égaux:

collection.Distinct().Count() == 1
Jeremy Bell
la source
1
Cela ne fonctionnera pas avec n'importe lequel Class, bien que cela devrait fonctionner avec les structures. Idéal pour une liste de primitifs, cependant.
Andrew Backer
2
+1 beaucoup plus propre que la solution IMO de KeithS. Vous pouvez utiliser collection.Distinct().Count() <= 1 si vous souhaitez autoriser les collections vides.
3dGrabber
4
Attention, .Distinct()ne fonctionne pas toujours comme prévu - en particulier lorsque vous travaillez avec des objets, voir cette question. Dans ce cas, vous devez implémenter l'interface IEquatable.
Matt
16
Plus propre, oui, mais moins performant dans le cas moyen; Distinct () est garanti de parcourir chaque élément de la collection une fois, et dans le pire des cas où chaque élément est différent, Count () parcourra la liste complète deux fois. Distinct () crée également un HashSet afin que son comportement puisse être linéaire et non NlogN ou pire, ce qui gonflera l'utilisation de la mémoire. All () effectue une passe complète dans le pire des cas où tous les éléments sont égaux, et ne crée aucune nouvelle collection.
KeithS
1
@KeithS Comme j'espère que vous vous en rendez compte maintenant, Distinctne traversera pas du tout la collection, et Countfera un parcours via Distinctl'itérateur de.
NetMage
22

Bien que vous puissiez certainement construire un tel appareil à partir d'opérateurs de séquence existants, je serais dans ce cas enclin à écrire celui-ci en tant qu'opérateur de séquence personnalisé. Quelque chose comme:

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

C'est assez clair, court, couvre tous les cas et ne crée pas inutilement des itérations supplémentaires de la séquence.

Faire de cela une méthode générique qui fonctionne IEnumerable<T>est laissé comme un exercice. :-)

Eric Lippert
la source
Disons, par exemple, que vous avez une séquence de nullables et que la valeur extraite est également une valeur nullable. Dans ce cas, la séquence peut être vide ou chaque élément de la séquence peut avoir un nul dans la valeur extraite. La coalescence, dans ce cas, renverrait le otherlorsque la nullréponse était (vraisemblablement) correcte. Disons que la fonction était T Unanimous<U, T>(this IEnumerable<U> sequence, T other)ou une telle signature, cela la complique un peu.
Anthony Pegram
@Anthony: En effet, il y a beaucoup de complications ici, mais elles sont assez faciles à contourner. J'utilise un int nullable par commodité pour ne pas avoir à déclarer un indicateur "J'ai déjà vu le premier élément". Vous pouvez facilement déclarer le drapeau. J'utilise également "int" au lieu de T car je sais que vous pouvez toujours comparer deux entiers pour l'égalité, ce qui n'est pas le cas pour deux Ts. Il s'agit plus d'une esquisse d'une solution que d'une solution générique entièrement fonctionnelle.
Eric Lippert
13
return collection.All(i => i == collection.First())) 
    ? collection.First() : otherValue;.

Ou si vous craignez d'exécuter First () pour chaque élément (ce qui pourrait être un problème de performance valide):

var first = collection.First();
return collection.All(i => i == first) ? first : otherValue;
Justin Niessner
la source
@KeithS - C'est pourquoi j'ai ajouté la deuxième partie de ma réponse. Sur les petites collections, appeler First () est trivial. Sur les grandes collections, cela pourrait commencer à être un problème.
Justin Niessner
1
"Sur les petites collections, appeler First () est trivial." - Cela dépend de la source de la collection. Pour une liste ou un tableau d'objets simples, vous avez absolument raison. Mais certains énumérables ne sont pas des ensembles finis de primitives mis en cache en mémoire. Une collection de délégués, ou un énumérateur qui produit grâce à un calcul de série algorithmique (par exemple Fibonacci), coûterait très cher d'évaluer First () à chaque fois.
KeithS
5
Ou pire, si la requête est une requête de base de données et que l'appel "First" frappe à nouveau la base de données à chaque fois.
Eric Lippert
1
Cela empire quand vous avez une itération ponctuelle comme la lecture d'un fichier ... Donc, la réponse d'Ani à partir d'un autre thread est la meilleure.
Alexei Levenkov
@Eric - Allez. Il n'y a rien de mal à frapper la base de données trois fois pour chaque élément ... :-P
Justin Niessner
3

Cela peut être tardif, mais une extension qui fonctionne pour les types valeur et référence de la même manière basée sur la réponse d'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
la source
1
public int GetResult(List<int> list){
int first = list.First();
return list.All(x => x == first) ? first : SOME_OTHER_VALUE;
}
hackerhasid
la source
1

Une alternative à l'utilisation de LINQ:

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

J'ai trouvé que l'utilisation HashSet<T>est plus rapide pour les listes jusqu'à ~ 6000 entiers par rapport à:

var value1 = items.First();
return values.All(v => v == value1) ? value1: otherValue;
Ɖ diamant ǤeezeƦ
la source
Premièrement, cela peut créer beaucoup de déchets. De plus, il est moins clair que les autres réponses LINQ, mais plus lentement que la méthode d'extension répond.
Ian Ringrose
Vrai. Cependant, ce ne sera pas beaucoup de déchets si nous parlons de déterminer si un petit ensemble de valeurs est identique. Lorsque j'ai exécuté ceci et une instruction LINQ dans LINQPad pour un petit ensemble de valeurs, HashSet était plus rapide (chronométré à l'aide de la classe Stopwatch).
Ɖiamond ǤeezeƦ
Si vous l'exécutez dans une version publiée à partir de la ligne de commande, vous obtiendrez peut-être des résultats différents.
Ian Ringrose
HashSet<T>J'ai créé une application console et trouvez que c'est initialement plus rapide que d'utiliser les instructions LINQ dans ma réponse. Cependant, si je fais cela en boucle, LINQ est plus rapide.
Ɖiamond ǤeezeƦ
Le gros problème avec cette solution est que si vous utilisez vos classes personnalisées, vous devez implémenter les vôtres GetHashCode(), ce qui est difficile à faire correctement Voir: stackoverflow.com/a/371348/2607840 pour plus de détails.
Cameron
0

Une légère variation par rapport à l'approche simplifiée ci-dessus.

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

David Ehnis
la source
3
C'est exactement l'inverse. Cela vérifiera que chaque élément de la liste est unique.
Mario Galea
-1

Si un tableau est de type multidimensionnel comme ci-dessous, nous devons écrire ci-dessous linq pour vérifier les données.

exemple: ici les éléments sont 0 et je vérifie que toutes les valeurs sont 0 ou non.
ip1 =
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
la source