Як перевірити, чи всі елементи списку мають однакове значення, і повернути його, або повернути "otherValue", якщо вони не мають?


122

Якщо всі елементи в списку мають однакове значення, то мені потрібно використовувати це значення, інакше мені потрібно використовувати "otherValue". Я не можу придумати простий і зрозумілий спосіб зробити це.

Дивіться також Акуратний спосіб написання циклу, який має особливу логіку для першого елемента колекції.


На вашому досить нахабно уваги добувача, я б з відповіддю Ані stackoverflow.com/questions/4390232 / ...
Binary неспокійний

5
Що ви хочете зробити, якщо немає першого значення, оскільки список порожній? У цьому випадку вірно, що "всі елементи в списку мають однакове значення" - якщо ви мені не вірите, знайдіть мене такого, якого немає! Ви не визначаєте, що робити в цій ситуації. Якщо це кине виняток, повертає значення "інше", чи що?
Ерік Ліпперт

@Eric, вибачте, коли список порожній, він повинен повернути значення "інше"
Ian Ringrose

Відповіді:


153
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дозволить перерахування достроково припинити перелік у випадках, коли є різні значення?
Джефф Огата

12
@adrift: Allприпиняється, як тільки він потрапляє на елемент xпослідовності, для якого x.Value != val. Аналогічно, Any(x => x.Value != val)він припиняється, як тільки потрапляє на елемент xпослідовності, для якого x.Value != val. Тобто і те, Allі Anyдемонструють «коротке замикання», аналогічне &&і ||(що ефективно є Allі Anyє).
Язон

@Jason: точно. Усі (умова) ефективно! Будь-яка (! Умова), і оцінка будь-якого припиняється, як тільки відповідь буде відома.
KeithS

4
Мікрооптимізація:return yyy.Skip(1).All(x=>x.Value == val) ? val : otherValue;
Caltor

100

Хороший швидкий тест для всіх рівних:

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

1
Це не працюватиме з будь-якими Class, хоча це має працювати зі структурами. Хоча чудово підходить для списку примітивів.
Ендрю Беккер

2
+1 набагато чистіше, ніж ІМО рішення KeithS. Ви можете використовувати, collection.Distinct().Count() <= 1 якщо ви хочете дозволити пусті колекції.
3dGrabber

4
Будьте уважні, .Distinct()не завжди працює так, як очікувалося - особливо коли ви працюєте з предметами, дивіться це питання. У цьому випадку вам потрібно реалізувати інтерфейс IEquatable.
Метт

16
Очищувач, так, але менш ефективний у середньому випадку; Розрізнювач () гарантовано перемикає кожен елемент колекції один раз, а в гіршому випадку, коли кожен елемент відрізняється, Count () пройде повний список двічі. Distinct () також створює HashSet, так що його поведінка може бути лінійною, а не NlogN або гіршою, і це буде збільшувати використання пам'яті. All () робить один повний пропуск у гіршому випадку, коли всі елементи рівні, і не створює нових колекцій.
KeithS

1
@KeithS Як я сподіваюся, ви зрозуміли, що зараз Distinctколекція взагалі не Countпройде , і зробить одну обхід через Distinctітератор.
NetMage

22

Хоча ви, безумовно, можете створити такий пристрій із існуючих операторів послідовностей, я б у цьому випадку схильний записати це як спеціальний оператор послідовності. Щось на зразок:

// 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>, залишається як вправа. :-)


Скажімо, наприклад, у вас є послідовність нульових значень, а вилучене значення також є нульовим. У такому випадку послідовність може бути порожньою, або кожен елемент у послідовності може мати нульове значення, що витягується. Злиття в цьому випадку поверне тоді, otherколи nullнасправді була (імовірно) правильна відповідь. Скажіть, функція була T Unanimous<U, T>(this IEnumerable<U> sequence, T other)чи якась така підпис, що трохи її ускладнює.
Ентоні Пеграм

@Anthony: Дійсно, тут є багато ускладнень, але вони досить легко справляються. Я використовую нульовий int як зручність, так що мені не потрібно оголошувати прапор "Я вже бачив перший елемент". Ви могли легко просто оголосити прапор. Також я використовую "int" замість T, тому що я знаю, що ви завжди можете порівняти два int для рівності, що не стосується двох Ts. Це скоріше ескіз рішення, ніж повністю функціональне загальне рішення.
Ерік Ліпперт

13
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 () є тривіальним. У великих колекціях це може стати проблемою.
Джастін Ніссснер

1
"У невеликих колекціях виклик First () є тривіальним." - Це залежить від джерела колекції. Що стосується списку чи масиву простих об’єктів, ви абсолютно праві. Але деякі перелічувачі не є кінцевими наборами примітивів, кешованих пам’яттю. Колекція делегатів або перелік, який отримує результати алгоритмічного обчислення рядів (наприклад, Фібоначчі), буде дуже дорого оцінювати First () щоразу.
KeithS

5
Або ще гірше, якщо запит - це запит до бази даних, а виклик "Перший" щоразу потрапляє в базу даних.
Ерік Ліпперт

1
Це стає гірше, коли у вас одноразова ітерація, як читання з файлу ... Тож відповідь Ані з іншої теми виглядає найкраще.
Олексій Левенков

@Eric - C'mon. Немає нічого поганого в тому, щоб тричі вдарити по базі даних по кожному елементу ... :-P
Джастін Нісснер

3

Це може бути пізно, але розширення, яке працює як для значень, так і для еталонних типів, грунтується на відповіді Еріка:

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


1

Альтернатива використанню LINQ:

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

Я виявив, що використання HashSet<T>швидше для списків до ~ 6000 цілих чисел порівняно з:

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

По-перше, це може створити багато сміття. Крім того, менш зрозуміло, ніж інші відповіді LINQ, але повільніше, ніж відповідає метод розширення.
Ян Рінроуз

Правда. Однак сміття не буде багато, якщо ми говоримо про те, чи є невеликий набір значень однаковим. Коли я запустив це та висловлювання LINQ в LINQPad для невеликого набору значень, HashSet був швидшим (приурочений до використання класу секундомір).
Ɖiamond ǤeezeƦ

Якщо запустити його у збірці випуску з командного рядка, ви можете отримати різні результати.
Ян Рінроуз

Створив консольний додаток і вияви, що HashSet<T>спочатку швидше, ніж використання тверджень LINQ у моїй відповіді. Однак якщо я це роблю в циклі, LINQ швидше.
Ɖiamond ǤeezeƦ

Велика проблема з цим рішенням є те , що якщо ви використовуєте свої власні класи, ви повинні реалізувати свої власні GetHashCode(), що важко зробити правильно Дивіться: stackoverflow.com/a/371348/2607840 для більш докладної інформації.
Камерон

0

Невелика різниця у вищезазначеному спрощеному підході.

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


3
Це саме навпаки. Це дозволить перевірити, що кожен елемент у списку є унікальним.
Маріо Галея

-1

Якщо масив має багатовимірний тип, як показано нижче, то для перевірки даних нам слід написати нижче 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";   
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.