Простий спосіб дізнатися, чи містять два різні списки абсолютно однакові елементи?


252

Який найпростіший спосіб знайти, якщо два Списки містять абсолютно однакові елементи в стандартних бібліотеках Java?

Не має значення, чи два списки однакові, чи ні, і не повинно мати значення, чи параметр типу списків відрізняється.

напр

List list1
List<String> list2; 
// ... construct etc

list1.add("A");
list2.add("A"); 
// the function, given these two lists, should return true

Мабуть, щось дивиться мені в обличчя, яке я знаю :-)


EDIT: Щоб уточнити, я шукав ТОЧНІ ж елементи та кількість елементів, за порядком.


Чи повинні елементи бути в одному порядку?
Майкл Майерс

Це ніколи не може вплинути на вас , але врахуйте , що зимують постійні набори іноді не шанує одно контракт - пошук opensource.atlassian.com/projects/hibernate/browse/HHH-3799
Pablojim

Відповіді:


366

Якщо ви дбаєте про порядок, тоді просто використовуйте метод рівних:

list1.equals(list2)

Від javadoc:

Порівняє вказаний об'єкт із цим списком для рівності. Повертає істину, якщо і лише якщо вказаний об'єкт також є списком, обидва списки мають однаковий розмір, і всі відповідні пари елементів у двох списках рівні. (Два елементи e1 і e2 рівні, якщо (e1 == null? E2 == null: e1.equals (e2)). Іншими словами, два списки визначаються рівними, якщо вони містять однакові елементи в одному порядку . Це визначення гарантує, що метод рівних працює належним чином у різних реалізаціях інтерфейсу List.

Якщо ви хочете перевірити незалежно від порядку, ви можете скопіювати всі елементи до наборів та використати рівні на отриманих наборах:

public static <T> boolean listEqualsIgnoreOrder(List<T> list1, List<T> list2) {
    return new HashSet<>(list1).equals(new HashSet<>(list2));
}

Обмеженням цього підходу є те, що він не тільки ігнорує порядок, але й частоту повторюваних елементів. Наприклад, якщо list1було ["A", "B", "A"] і list2було ["A", "B", "B"], Setпідхід вважав би їх рівними.

Якщо вам потрібно бути нечутливим до порядку, але чутливим до частоти копій, ви можете:


54
Чи не можете ви використовувати, що ви хочете перевірити незалежно від замовлення?
лаз

6
Я не знаю про деталі реалізації змісту, але здається, що це може бути погано. Якщо міститьВсі виклики містять () знову і знову, ви матимете алгоритм O (n ^ 2). Загальна кількість сетів повинна бути O (nlogn)
Том

6
Насправді, якщо набори просто будуть O (nlogn), інший підхід - викликати Collections.sort () у списку, а потім використовувати equals. Якщо ви хочете зберегти порядок, вам потрібно буде скопіювати список, а це може бути дорогим і надати перевагу встановленому рішенню ... тому вам доведеться подумати над своєю ситуацією :-).
Том

1
@amischiefr: ви припускаєте, що O (n ^ 2) - це найкраще, що ви можете зробити?
Том

8
@Dennis Перевірка розміру дійсно працює лише в тому випадку, якщо ви знаєте, що кожен список містить лише різні елементи. Наприклад, задані a = [x, y, x]і b = [x, y, z]тоді розміри рівні і b.containsAll(a)повернуть істинні, але bмістять елемент не в a.
Лоранс Гонсалвс

95

Я розмістив купу речей у коментарях, я думаю, що це вимагає власної відповіді.

Як тут всі кажуть, використання рівняння () залежить від порядку. Якщо ви не дбаєте про замовлення, у вас є 3 варіанти.

Варіант 1

Використовуйте containsAll(). На мою думку, цей варіант не є ідеальним, оскільки він пропонує найгірші показники, O (n ^ 2).

Варіант 2

Для цього є дві варіанти:

2а) Якщо вам не байдуже підтримувати порядок своїх списків ... скористайтеся Collections.sort()обома списками. Потім використовуйте equals(). Це O (nlogn), тому що ви робите два сорти, а потім O (n) порівняння.

2b) Якщо вам потрібно підтримувати порядок списків, ви можете скопіювати обидва списки спочатку. ТАКОЖ ви можете використовувати рішення 2a в обох скопійованих списках. Однак це може бути непривабливим, якщо копіювання дуже дороге.

Це призводить до:

Варіант 3

Якщо ваші вимоги збігаються з частиною 2b , але копіювання занадто дороге. Ви можете використовувати TreeSet, щоб зробити сортування за вами. Скиньте кожен список у свій власний TreeSet. Він буде відсортований у наборі, а оригінальні списки залишаться недоторканими. Потім проведіть equals()порівняння на обох TreeSets. В TreeSetsи можуть бути вбудовані в O (NlogN) часу, і являє equals()собою О (п).

Обирайте :-).

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


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

Більш конкретно, якщо наявність дублікатів вказує на нерівність, розмір списків повинен бути однаковим, перш ніж будь-яка перевірка рівності матиме шанс на успіх.
лаз

7
@laz: перевірка розміру не спрацює, якщо різні елементи дублюються у двох списках. наприклад: [A, A, B] vs [A, B, B] мають однаковий розмір.
Лоранс Гонсалвес

@Laurence: Я погоджуюся, що пост Лаза трохи заплутаний (я прочитав його кілька разів, перш ніж зрозумів це). Я вважаю, що він просто намагається надати "ярлик" для особливого випадку, коли дотримуються 2 умови: (1) копії мають значення, і (2) розміри списку різні. У вашому прикладі, я думаю, що Лаз все ще говорить, що потрібно робити всі ті ж перевірки, які ми обговорювали. (Принаймні, так я читаю). Якщо копії НЕ мають значення, то ви не можете використовувати розмір як спеціальну перевірку справ. Але коли утримують 2 умови, ви можете просто сказати : «якщо (list1.size () = list2.size (!)) Повернення хибним;.
Том

9
Здається, я думаю, що ви дасте неправильну відповідь, вам потрібно містити всі обидва способи. a.containsAll(b) && b.containsAll(a)
Річард Тінгл

24

Якщо ви використовуєте (або раді використовувати) колекції Apache Commons, ви можете використовувати CollectionUtils.isEqualCollection, яка "повертає справжнє, якщо дані колекції містять абсолютно однакові елементи з абсолютно однаковими кардиналіями".


Дуже приємна реалізація на основі хешмапу. Час виконання повинен бути O (n), і якщо є багато повторюваних елементів, він використовує мінімальну пам'ять для відстеження (в основному відстежує частоту (кардинальність) елементів, використовуючи карту для кожної колекції). Недоліком є ​​додаткове використання O (n) пам'яті.
Мухд

17

Дуже пізно на вечірку, але хотів додати цю нульову безпечну перевірку:

Objects.equals(list1, list2)

8

Я знаю, що це стара тема, але жодна з інших відповідей повністю не вирішила мій варіант використання (я думаю, Guava Multiset може зробити те саме, але прикладу тут немає). Вибачте, будь ласка, про моє форматування. Я все ще нова в публікаціях на обміні стеками. Додатково повідомте мене, якщо є якісь помилки

Скажімо, у вас є List<T>a і List<T>b, і ви хочете перевірити, чи вони рівні з такими умовами:

1) O (n) очікуваний час виконання
2) Рівність визначається як: Для всіх елементів у a або b кількість разів, коли елемент виникає в a, дорівнює кількості разів, коли елемент виникає в b. Рівність елементів визначається як T.equals ()

private boolean listsAreEquivelent(List<? extends Object> a, List<? extends Object> b) {
    if(a==null) {
        if(b==null) {
            //Here 2 null lists are equivelent. You may want to change this.
            return true;
        } else {
            return false;
        }
    }
    if(b==null) {
        return false;
    }
    Map<Object, Integer> tempMap = new HashMap<>();
    for(Object element : a) {
        Integer currentCount = tempMap.get(element);
        if(currentCount == null) {
            tempMap.put(element, 1);
        } else {
            tempMap.put(element, currentCount+1);
        }
    }
    for(Object element : b) {
        Integer currentCount = tempMap.get(element);
        if(currentCount == null) {
            return false;
        } else {
            tempMap.put(element, currentCount-1);
        }
    }
    for(Integer count : tempMap.values()) {
        if(count != 0) {
            return false;
        }
    }
    return true;
}

Час виконання - це O (n), оскільки ми робимо вставки O (2 * n) у хешмап і вибираємо хешмап O (3 * n). Я не повністю перевірив цей код, тому будьте уважні :)

//Returns true:
listsAreEquivelent(Arrays.asList("A","A","B"),Arrays.asList("B","A","A"));
listsAreEquivelent(null,null);
//Returns false:
listsAreEquivelent(Arrays.asList("A","A","B"),Arrays.asList("B","A","B"));
listsAreEquivelent(Arrays.asList("A","A","B"),Arrays.asList("A","B"));
listsAreEquivelent(Arrays.asList("A","A","B"),null);

5

Спробуйте цю версію, яка не вимагає того, щоб замовлення було однаковим, але підтримує множини одного і того ж значення. Вони відповідають, лише якщо кожен має однакову кількість будь-якого значення.

public boolean arraysMatch(List<String> elements1, List<String> elements2) {
    // Optional quick test since size must match
    if (elements1.size() != elements2.size()) {
        return false;
    }
    List<String> work = newArrayList(elements2);
    for (String element : elements1) {
        if (!work.remove(element)) {
            return false;
        }
    }
    return work.isEmpty();
}

work.remove (element) - O (n), тому це рішення - O (n ^ 2)
Андрій

Або O (n1 * n2), який такий самий
Лі Меадор

Я також використовував ту саму стратегію, оскільки вона обробляє всі сценарії, і розмір колекції не такий великий, тоді O (n ^ 2) не має значення
Naresh Joshi

3

Метод рівних у списку зробить це так, списки впорядковані, тому для рівності два списки повинні мати однакові елементи в одному порядку.

return list1.equals(list2);

3
Списки не замовляються, якщо ви їх не сортуєте.
Майкл Майерс

Зітхни @ себе. Така очевидна відповідь. Ви знаєте, що день вже занадто довгий, коли ви навіть більше не можете Ctrl + F веб-сторінку. :)
Grundlefleck

2
@mmyers: елементи в списках не упорядковуються, якщо ви їх не сортуєте. Самі списки мають неявне впорядкування елементів (за індексом), які не змінюються, якщо ви не зміните елементи у списку. (проти наборів або колекцій, де немає гарантії послідовного замовлення, якщо повторити їх двічі)
Jason S

Я думаю, що означає Daveb, кажучи, що списки впорядковані, це те, що List.equals враховує порядок елементів для визначення рівності. Дивіться Javadoc.
лаз

2
Я маю на увазі те, що список, що містить {"A", "B"} і список, що містить {"B", "A"}, був би нерівним із цим методом. Це може бути цілком задумане, але я хотів переконатися, що ніхто не оглядає це.
Майкл Майерс

3

Рішення для випадку, коли два списки мають однакові елементи, але різний порядок:

public boolean isDifferentLists(List<Integer> listOne, List<Integer> listTwo) {
    if(isNullLists(listOne, listTwo)) {
        return false;
    }

    if (hasDifferentSize(listOne, listTwo)) {
        return true;
    }

    List<Integer> listOneCopy = Lists.newArrayList(listOne);
    List<Integer> listTwoCopy = Lists.newArrayList(listTwo);
    listOneCopy.removeAll(listTwoCopy);

    return CollectionUtils.isNotEmpty(listOneCopy);
}

private boolean isNullLists(List<Integer> listOne, List<Integer> listTwo) {
    return listOne == null && listTwo == null;
}

private boolean hasDifferentSize(List<Integer> listOne, List<Integer> listTwo) {
    return (listOne == null && listTwo != null) || (listOne != null && listTwo == null) || (listOne.size() != listTwo.size());
}

2
Я думаю, вам не потрібно копіювати listTwo.
AjahnCharles

1
Ви також можете відзначити, чому ви використовували removeAll()замість цього containsAll()(я розумію, що якщо listTwo містить дублікати, які містяться лише один раз у listOne, підхід содержащий (() неправильно повідомить про списки як рівні).
AjahnCharles

3

Відповідь Тома відмінна. Я повністю згоден з його відповідями!

Цікавим аспектом цього питання є те, чи потрібен вам сам Listтип та притаманне йому впорядкування.

Якщо ні, то ви не зможете перейти на деградацію, Iterableабо Collectionце дає певну гнучкість при проходженні структур даних, відсортованих за часом вставки, а не в той час, коли ви хочете перевірити.

Якщо замовлення ніколи не має значення (а у вас немає дублікатів елементів), спробуйте скористатись a Set.

Якщо порядок має значення, але визначено часом вставки (а у вас немає дублікатів), розгляньте, LinkedHashSetщо це як TreeSet, але впорядковано часом вставки (дублікати не враховуються). Це також дає вам O(1)доступний амортизований доступ O(log n).


2

Приклад коду:

public static '<'T'>' boolean isListDifferent(List'<'T'>' previousList,
        List'<'T'>' newList) {

    int sizePrevoisList = -1;
    int sizeNewList = -1;

    if (previousList != null && !previousList.isEmpty()) {
        sizePrevoisList = previousList.size();
    }
    if (newList != null && !newList.isEmpty()) {
        sizeNewList = newList.size();
    }

    if ((sizePrevoisList == -1) && (sizeNewList == -1)) {
        return false;
    }

    if (sizeNewList != sizePrevoisList) {
        return true;
    }

    List n_prevois = new ArrayList(previousList);
    List n_new = new ArrayList(newList);

    try {
        Collections.sort(n_prevois);
        Collections.sort(n_new);
    } catch (ClassCastException exp) {
        return true;
    }

    for (int i = 0; i < sizeNewList; i++) {
        Object obj_prevois = n_prevois.get(i);
        Object obj_new = n_new.get(i);
        if (obj_new.equals(obj_prevois)) {
            // Object are same
        } else {
            return true;
        }
    }

    return false;
}

2

На додаток до відповіді Лоранса, якщо ви також хочете зробити його безпечним:

private static <T> boolean listEqualsIgnoreOrder(List<T> list1, List<T> list2) {
    if (list1 == null)
        return list2==null;
    if (list2 == null)
        return list1 == null;
    return new HashSet<>(list1).equals(new HashSet<>(list2));
}

1
Ви можете спростити чеки:if (list1 == null) return list2==null; if (list2 == null) return false;
Xerus

Не працює, якщо списки [a, a, b, c] & [a, b, c] і повернуться істинними, якщо не додати додаткову перевірку, щоб забезпечити однаковий розмір списків.
Венкат Мадхав

2
list1.equals(list2);

Якщо ваш список містить користувацький клас MyClass, цей клас повинен перекривати equalsфункцію.

 class MyClass
  {
  int field=0;
  @0verride
  public boolean equals(Object other)
        {
        if(this==other) return true;
        if(other==null || !(other instanceof MyClass)) return false;
        return this.field== MyClass.class.cast(other).field;
        }
  }

Примітка: якщо ви хочете перевірити рівність на java.util.Set, а не a java.util.List, ваш об'єкт повинен перекрити hashCode функцію.


1
повинен рядок: повернути this.field == MyClass.class.cast (інше); be return this.field == MyClass.class.cast (інше) .field;
alpere

@alpere о! ти маєш рацію ! Я це виправлю. Дякую !
П’єр


0

Ви можете використовувати бібліотеку org.apache.commons.collections Apache: http://commons.apache.org/collections/apidocs/org/apache/commons/collections/ListUtils.html

public static boolean isEqualList(java.util.Collection list1,
                              java.util.Collection list2)

Для цього також потрібно, щоб елементи списку були в одному порядку.
josh-cain

ви можете сортувати список перед порівнянням
Девід Чжао

Звичайно, ви можете зробити це за умови збереження типів у списку чи сортування (або у вас налаштований компаратор). Однак алгоритм реалізації Apache не відрізняється від звичайного list1.equals (list2), за винятком статичного. Я бачу, де я неправильно зрозумів питання, і насправді запитував, як порівняти елементи списку в тому ж порядку. Моє ліжко!
josh-cain

@DavidZhao: посилання мертва.
Анікет Кулкарні


0

Перевірка обох списків не є нульовими. Якщо їх розміри різні, то ці списки не рівні. Створіть карти, що складаються з елементів списків як ключів та їх повторів як значень, та порівняйте карти.

Припущення, якщо обидва списки є нульовими, я вважаю їх рівними.

private boolean compareLists(List<?> l1, List<?> l2) {
    if (l1 == null && l2 == null) {
        return true;
    } else if (l1 == null || l2 == null) {
        return false;
    }

    if (l1.size() != l2.size()) {
        return false;
    }

    Map<?, Integer> m1 = toMap(l1);
    Map<?, Integer> m2 = toMap(l2);

    return m1.equals(m2);
}

private Map<Object, Integer> toMap(List<?> list) {
    //Effective size, not to resize in the future.
    int mapSize = (int) (list.size() / 0.75 + 1);
    Map<Object, Integer> map = new HashMap<>(mapSize);

    for (Object o : list) {
        Integer count = map.get(o);
        if (count == null) {
            map.put(o, 1);
        } else {
            map.put(o, ++count);
        }
    }

    System.out.println(map);
    return map;
}

Зверніть увагу, для цих об'єктів повинні бути правильно визначені рівні методу. https://stackoverflow.com/a/24814634/4587961


1
Ви припустили, що елемент не може бути представлений різною кількістю разів у кожному списку, наприклад, [x, x, y]vs [x, y, y]повернеться вірно з вашою реалізацією.
AjahnCharles

@CodeConfident, дякую, стільки! Я оновив відповідь. Я буду використовувати мао!
Ян Хонський

-2

Це залежить від того, який конкретний клас List ви використовуєте. Абстрактний клас AbstractCollection має метод, який називається міститьAll (Колекція), який приймає іншу колекцію (Список - це колекція) та:

Повертає істину, якщо ця колекція містить усі елементи зазначеної колекції.

Отже, якщо ArrayList передається у вас, ви можете зателефонувати за цим методом, щоб побачити, чи точно вони однакові.

       List foo = new ArrayList();
    List bar = new ArrayList();
    String str = "foobar";

    foo.add(str);
    bar.add(str);

    foo.containsAll(bar);

Причиною contentAll () є те, що воно повторюється через перший список, шукаючи відповідність у другому списку. Тож якщо вони вийшли з ладу, дорівнює (), це не підбере.

EDIT: Я просто хочу зауважити тут про амортизований час виконання різних пропонованих варіантів. Чи важливий час роботи? Звичайно. Це єдине, що вам слід врахувати? Немає.

Вартість копіювання КОГО-небудь одного елемента зі своїх списків до інших списків вимагає часу, а також займає хороший фрагмент пам’яті (що фактично збільшує подвоєння пам'яті, яку ви використовуєте).

Отже, якщо пам'ять у вашому JVM не викликає проблем (що це, як правило, має бути), то вам все одно потрібно врахувати час, необхідний для копіювання кожного елемента з двох списків у два TreeSets. Пам'ятайте, вона сортує кожен елемент під час їх введення.

Моя остання порада? Потрібно врахувати свій набір даних та кількість елементів у вашому наборі даних, а також наскільки великий кожен об’єкт у вашому наборі даних, перш ніж ви зможете прийняти тут хороше рішення. Пограйте з ними, створіть один із них і подивіться, який з них працює швидше. Це гарна вправа.


2
Хіба це не повинно бути foo.containsAll (bar) && bar.containsAll (foo); ?
Карл Манастер

Ні, він проходить через кожен елемент в foo і бачить, чи містить бар цей елемент. Потім гарантується, що довжина буде однаковою для двох списків. Якщо для кожного foo є елемент в рядку, такий як foo.element == bar.element і foo.length == bar.length, вони містять однакові елементи.
amischiefr

чи знаємо ми, чи є гарантія ефективності? або це, як правило, O (n ^ 2)?
Том

Як і будь-який інший масив, який повторюється за допомогою пошуку відповідного елемента, найгіршим часом роботи буде O (n ^ 2). У цьому випадку схоже, що реалізація дійсно повторюється через один елемент, який шукає відповідність одночасно. Я не буду міркувати про амортизований час роботи, але так, найгірший випадок - це O (n ^ 2).
amischiefr

1
Це не працює: {1,2,2} .containsAll ({1,1,2}) і зворотній вірш, і два списки мають однаковий розмір.
comco
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.