Як перевірити, чи всі елементи списку мають однакове значення, і повернути його, або повернути "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.