Jak sprawdzić, czy wszystkie elementy listy mają tę samą wartość i zwrócić ją lub zwrócić „inną wartość”, jeśli tak nie jest?

122

Jeśli wszystkie elementy na liście mają tę samą wartość, muszę użyć tej wartości, w przeciwnym razie muszę użyć „otherValue”. Nie mogę wymyślić prostego i jasnego sposobu na zrobienie tego.

Zobacz także Zgrabny sposób pisania pętli, która ma specjalną logikę dla pierwszego elementu w kolekcji.

Ian Ringrose
źródło
Jeśli chodzi o twoje raczej bezczelne przyciąganie uwagi, poszedłbym z odpowiedzią Ani stackoverflow.com/questions/4390232/ ...
Binary Worrier
5
Co chcesz, aby się stało, jeśli nie ma pierwszej wartości, ponieważ lista jest pusta? W takim przypadku prawdą jest, że „wszystkie pozycje na liście mają tę samą wartość” - jeśli mi nie wierzysz, znajdź mi taką, która tak nie jest! Nie definiujesz, co robić w tej sytuacji. Czy powinno to zgłosić wyjątek, zwrócić „inną” wartość, czy co?
Eric Lippert
@Eric, przepraszam, gdy lista jest pusta, powinna zwrócić wartość „other”
Ian Ringrose,

Odpowiedzi:

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

Najczystszy sposób, jaki przychodzi mi do głowy. Możesz uczynić go jednowierszowym przez wbudowanie wartości val, ale metoda First () byłaby oceniana n razy, podwajając czas wykonania.

Aby uwzględnić zachowanie „pustego zestawu” określone w komentarzach, wystarczy dodać jeszcze jedną linię przed dwoma powyższymi:

if(yyy == null || !yyy.Any()) return otherValue;
KeithS
źródło
1
+1, czy użycie .Anypozwoliłoby na wcześniejsze zakończenie wyliczania w przypadkach, w których istnieją różne wartości?
Jeff Ogata,
12
@adrift: Allzakończy działanie, gdy tylko trafi w element xsekwencji, dla którego x.Value != val. Podobnie Any(x => x.Value != val)zakończy się, gdy tylko trafi w element xsekwencji, dla którego x.Value != val. Oznacza to, że oba Alli Anywykazują „zwarcie” analogiczne do &&i ||(co jest w rzeczywistości tym, czym Alli Anysą).
jason
@Jason: dokładnie. Wszystko (warunek) jest efektywne! Dowolny (! Warunek), a ocena jednego z nich zakończy się, gdy tylko znana będzie odpowiedź.
KeithS
4
Mikrooptymalizacja:return yyy.Skip(1).All(x=>x.Value == val) ? val : otherValue;
Caltor
101

Dobry szybki test dla wszystkich równych:

collection.Distinct().Count() == 1
Jeremy Bell
źródło
1
To nie zadziała z żadnymi Class, chociaż powinno działać ze strukturami. Świetnie nadaje się jednak do listy prymitywów.
Andrew Backer,
2
+1 znacznie czystsze niż rozwiązanie IMO KeithS. Możesz użyć, collection.Distinct().Count() <= 1 jeśli chcesz zezwolić na puste kolekcje.
3dGrabber
4
Uważaj, .Distinct()nie zawsze działa zgodnie z oczekiwaniami - szczególnie podczas pracy z obiektami zobacz to pytanie. W takich przypadkach musisz zaimplementować interfejs IEquatable.
Matt
16
Czystsze, tak, ale mniej wydajne w przeciętnym przypadku; Distinct () gwarantuje, że raz przejdzie każdy element kolekcji, aw najgorszym przypadku, gdy każdy element będzie inny, Count () przejdzie dwukrotnie pełną listę. Distinct () również tworzy HashSet, więc jego zachowanie może być liniowe i nie NlogN lub gorsze, a to spowoduje zawyżenie użycia pamięci. All () wykonuje jeden pełny przebieg w najgorszym przypadku, gdy wszystkie elementy są równe i nie tworzy żadnych nowych kolekcji.
KeithS
1
@KeithS Jak mam nadzieję, że już zdajesz sobie sprawę, w Distinctogóle nie przejdzie przez kolekcję i Countwykona jedno przejście przez Distinctiterator.
NetMage
22

Chociaż z pewnością można zbudować takie urządzenie z istniejących operatorów sekwencji, w tym przypadku byłbym skłonny napisać to jako niestandardowy operator sekwencji. Coś jak:

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

To dość jasne, krótkie, obejmuje wszystkie przypadki i nie tworzy niepotrzebnie dodatkowych iteracji sekwencji.

Przekształcenie tego w ogólną metodę, nad którą działa, IEnumerable<T>pozostaje jako ćwiczenie. :-)

Eric Lippert
źródło
Załóżmy na przykład, że masz sekwencję wartości null, a wyodrębniona wartość jest również wartością null. W takim przypadku sekwencja może być pusta lub każdy element w sekwencji może mieć wartość null w wyodrębnionej wartości. W tym przypadku koalescencja zwróciłaby, otherkiedy nullfaktycznie (przypuszczalnie) była to poprawna odpowiedź. Powiedzmy, że funkcja była T Unanimous<U, T>(this IEnumerable<U> sequence, T other)lub jakaś taka sygnatura, to trochę ją komplikuje.
Anthony Pegram
@Anthony: Rzeczywiście, jest tutaj wiele komplikacji, ale można je dość łatwo obejść. Używam wartości nullable int jako wygody, aby nie musieć deklarować flagi „Już widziałem pierwszy element”. Możesz łatwo zadeklarować flagę. Używam również „int” zamiast T, ponieważ wiem, że zawsze można porównać dwie liczby int dla równości, co nie ma miejsca w przypadku dwóch Ts. To bardziej szkic rozwiązania niż w pełni funkcjonalne rozwiązanie ogólne.
Eric Lippert
13
return collection.All(i => i == collection.First())) 
    ? collection.First() : otherValue;.

Lub jeśli martwisz się wykonywaniem First () dla każdego elementu (co może być ważnym problemem wydajności):

var first = collection.First();
return collection.All(i => i == first) ? first : otherValue;
Justin Niessner
źródło
@KeithS - dlatego dodałem drugą część mojej odpowiedzi. W przypadku małych kolekcji wywołanie First () jest trywialne. W przypadku dużych kolekcji może to stanowić problem.
Justin Niessner
1
„W przypadku małych kolekcji wywołanie First () jest trywialne”. - To zależy od źródła kolekcji. Jeśli chodzi o listę lub tablicę prostych obiektów, masz absolutną rację. Ale niektóre wyliczalne nie są skończonymi zbiorami prymitywów w pamięci podręcznej. Zbiór delegatów lub moduł wyliczający, który daje wynik przez algorytmiczne obliczanie szeregów (np. Fibonacci), byłby bardzo kosztowny do oceny First () za każdym razem.
KeithS
5
Lub, co gorsza, jeśli zapytanie jest zapytaniem do bazy danych i wywołanie „First” za każdym razem ponownie trafia do bazy danych.
Eric Lippert
1
Gorzej, gdy masz jednorazową iterację, taką jak czytanie z pliku ... Więc odpowiedź Ani z innego wątku wygląda najlepiej.
Alexei Levenkov
@Eric - No dalej. Nie ma nic złego w trafieniu do bazy danych trzy razy dla każdego elementu ... :-P
Justin Niessner
3

To może być późno, ale rozszerzenie, które działa zarówno w przypadku typów wartości, jak i typów referencyjnych w oparciu o odpowiedź Erica:

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
źródło
1
public int GetResult(List<int> list){
int first = list.First();
return list.All(x => x == first) ? first : SOME_OTHER_VALUE;
}
hackerhasid
źródło
1

Alternatywa dla korzystania z LINQ:

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

Zauważyłem, że używanie HashSet<T>jest szybsze w przypadku list do ~ 6000 liczb całkowitych w porównaniu z:

var value1 = items.First();
return values.All(v => v == value1) ? value1: otherValue;
Ɖiamond ǤeezeƦ
źródło
Po pierwsze, może to spowodować wiele śmieci. Jest również mniej jasne niż inne odpowiedzi LINQ, ale wolniej niż odpowiada metoda rozszerzenia.
Ian Ringrose
Prawdziwe. Jednak nie będzie to dużo śmieci, jeśli mówimy o określaniu, czy mały zestaw wartości jest taki sam. Kiedy uruchomiłem to i instrukcję LINQ w LINQPad dla małego zestawu wartości, HashSet był szybszy (czas przy użyciu klasy Stopwatch).
Ɖiamond ǤeezeƦ
Jeśli uruchomisz go w kompilacji wydania z wiersza poleceń, możesz uzyskać inne wyniki.
Ian Ringrose
Utworzono aplikację konsolową i okazało się, że HashSet<T>jest to początkowo szybsze niż użycie instrukcji LINQ w mojej odpowiedzi. Jeśli jednak zrobię to w pętli, LINQ jest szybszy.
Ɖiamond ǤeezeƦ
Duży problem z tego rozwiązania jest to, że jeśli używasz niestandardowych klas, trzeba zaimplementować własną rękę GetHashCode(), co jest trudne do zrobienia poprawnie Patrz: stackoverflow.com/a/371348/2607840 więcej szczegółów.
Cameron
0

Niewielkie odstępstwo od powyższego uproszczonego podejścia.

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

David Ehnis
źródło
3
To jest dokładnie na odwrót. Spowoduje to sprawdzenie, czy każdy element na liście jest unikalny.
Mario Galea
-1

Jeśli tablica jest typu wielowymiarowego, jak poniżej, musimy napisać poniżej linq, aby sprawdzić dane.

przykład: tutaj elementy mają wartość 0 i sprawdzam, czy wszystkie wartości są równe 0 lub nie.
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
źródło