Як я можу розрахувати різницю між двома списками масивів?


81

У мене є два ArrayLists.

ArrayList A містить:

['2009-05-18','2009-05-19','2009-05-21']

ArrayList B містить:

['2009-05-18','2009-05-18','2009-05-19','2009-05-19','2009-05-20','2009-05-21','2009-05-21','2009-05-22']

Мені потрібно порівняти ArrayList A та ArrayList B. Результат ArrayList повинен містити Список, якого не існує в ArrayList A.

Результатом ArrayList має бути:

['2009-05-20','2009-05-22']

як порівняти?

Відповіді:


194

У Java ви можете використовувати метод Collectionінтерфейсу removeAll.

// Create a couple ArrayList objects and populate them
// with some delicious fruits.
Collection firstList = new ArrayList() {{
    add("apple");
    add("orange");
}};

Collection secondList = new ArrayList() {{
    add("apple");
    add("orange");
    add("banana");
    add("strawberry");
}};

// Show the "before" lists
System.out.println("First List: " + firstList);
System.out.println("Second List: " + secondList);

// Remove all elements in firstList from secondList
secondList.removeAll(firstList);

// Show the "after" list
System.out.println("Result: " + secondList);

Наведений вище код дасть такий результат:

First List: [apple, orange]
Second List: [apple, orange, banana, strawberry]
Result: [banana, strawberry]

7
Якщо ваш список призначений для власного класу, то вам доведеться замінити метод equals для вашого класу, так?
RTF

5
@RTF Так, вам потрібно надати реалізацію, equalsяка дозволяє порівнювати ваші об'єкти. Прочитайте також про впровадження hashCode. Наприклад, зверніть увагу , як String::equalsце відчутно до регістру , тому «яблуко» і «Яблуко» не вважатимуться таким же.
Василь Бурк

1
Насправді відповідь залежить від того, що ви хочете зробити. RemoveAll не збереже дублікатів. Якщо ви додасте ще один рядок "яблука" до свого другого списку, він також буде видалений, що не завжди може бути тим, що ви хочете.
Jules Testard

2
Це настільки неефективно. Сумно, що це і обрана, і найкраща оцінка відповіді. removeAllзвертається firstList.containsдо кожного елемента secondList. Використання a HashSetб запобігти цьому, і є кілька хороших відповідей нижче.
Vlasec

20

Ви вже маєте правильну відповідь. А якщо ви хочете зробити більш складні та цікаві операції між Списками (колекціями), використовуйте колекції apache commons ( CollectionUtils ). Це дозволяє робити кон’юкцію / диз’юнкцію, знаходити перетин, перевіряти, чи одна колекція є підмножиною іншої та інші приємні речі.



12

У Java 8 з потоками це насправді досить просто. EDIT: Може бути ефективним без потоків, див. Нижче.

List<String> listA = Arrays.asList("2009-05-18","2009-05-19","2009-05-21");
List<String> listB = Arrays.asList("2009-05-18","2009-05-18","2009-05-19","2009-05-19",
                                   "2009-05-20","2009-05-21","2009-05-21","2009-05-22");

List<String> result = listB.stream()
                           .filter(not(new HashSet<>(listA)::contains))
                           .collect(Collectors.toList());

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

Ви не можете легко заперечити предикат без чогось подібного до цього методу утиліти (або явного приведення), оскільки ви не можете безпосередньо викликати посилання на метод заперечення (спочатку потрібно зробити висновок типу).

private static <T> Predicate<T> not(Predicate<T> predicate) {
    return predicate.negate();
}

Якби в потоках був filterOutметод чи щось подібне, це виглядало б приємніше.


Крім того, @Holger дав мені ідею. ArrayListмає свій removeAllметод, оптимізований для багаторазового видалення, він переставляє свої елементи лише один раз. Однак він використовує containsметод, наданий даною колекцією, тому нам потрібно оптимізувати цю частину, якщо вона listAє лише крихітною.

З listAі listBзаявив раніше, це рішення не потребує в Java 8 , і це дуже ефективно.

List<String> result = new ArrayList(listB);
result.removeAll(new HashSet<>(listA));

1
@Bax Чому редагування? Оригінал був чистішим і функціонально ідентичним.
shmosel

1
@Bax Ні, це не так.
shmosel

1
З Гуавою ви можете це зробити Predicates.in(new HashSet<>(listA)).negate().
shmosel 03

1
Я просто запустив деякий тест, і це рішення на ~ 10-20% швидше, ніж listB.removeAll (новий HashSet <> (listA)). і Гуава Набори. різниця (...) si в 2 рази повільніша за потоки.
телебог

1
@Vlasec ArrayList.removeмає лінійну складність, але ArrayList.removeAllне покладається, removeа виконує операцію лінійного оновлення масиву, копіюючи кожен решту елемент в останнє місце. На відміну від цього, еталонна реалізація LinkedListне має оптимізованої, removeAllале виконує removeоперацію для кожного порушеного елемента, яка кожного разу оновлюватиме до п’яти посилань. Таким чином, в залежності від співвідношення між віддаленими і іншими елементами, ArrayList«s removeAllвсе ще може значно краще , ніж виконувати LinkedLists», навіть для великих списків.
Холгер

9

РЕДАГУВАТИ: оригінальне запитання не вказувало мову. Моя відповідь - на C #.

Натомість для цього слід використовувати HashSet. Якщо вам потрібно використовувати ArrayList, ви можете використовувати такі методи розширення:

var a = arrayListA.Cast<DateTime>();
var b = arrayListB.Cast<DateTime>();    
var c = b.Except(a);

var arrayListC = new ArrayList(c.ToArray());

за допомогою HashSet ...

var a = new HashSet<DateTime>(); // ...and fill it
var b = new HashSet<DateTime>(); // ...and fill it
b.ExceptWith(a); // removes from b items that are in a

8

Я використовував набори гуави. Різниця .

Параметри безліч і не загальні колекції, але зручний спосіб для створення наборів з будь-якої колекції (з унікальними предметами) є гуава ImmutableSet.copyOf (Iterable).

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


8

Хоча це дуже давнє запитання в Java 8, ви можете зробити щось подібне

 List<String> a1 = Arrays.asList("2009-05-18", "2009-05-19", "2009-05-21");
 List<String> a2 = Arrays.asList("2009-05-18", "2009-05-18", "2009-05-19", "2009-05-19", "2009-05-20", "2009-05-21","2009-05-21", "2009-05-22");

 List<String> result = a2.stream().filter(elem -> !a1.contains(elem)).collect(Collectors.toList());

Я люблю Java 8, але нам все одно слід думати про складність. Хоча у списках також є Collectionметод '' contains, він дуже неефективний. Його потрібно пройти через весь список, якщо його не знайти. Виконання цього для кожного елемента a2може бути болісно повільним у більших списках, ось чому я роблю набір a1у своїй відповіді.
Vlasec

2

Я думаю, ви говорите про C #. Якщо так, ви можете спробувати це

    ArrayList CompareArrayList(ArrayList a, ArrayList b)
    {
        ArrayList output = new ArrayList();
        for (int i = 0; i < a.Count; i++)
        {
            string str = (string)a[i];
            if (!b.Contains(str))
            {
                if(!output.Contains(str)) // check for dupes
                    output.Add(str);
            }
        }
        return output;
    }

Вибачте, я не згадав про мову програмування, це нормально, але мені потрібна Java, подяка за ур. Відтворення
Навен

Це вірно. Це також дуже неефективний спосіб зробити це. Ви в основному прокрутите весь bсписок списку a.Count. Ви можете створити HashSetзамість цього, щоб використовувати для Containsабо використовувати RemoveAllметод на наборі, щоб отримати саме ті результати, які ви хочете.
Vlasec

1

Ви просто порівнюєте рядки.

Помістіть значення в ArrayList A як ключі в HashTable A.
Помістіть значення в ArrayList B як ключі в HashTable B.

Потім для кожного ключа в HashTable A видаліть його з HashTable B, якщо він існує.

У HashTable B у вас залишились рядки (ключі), які не були значеннями в ArrayList A.

Приклад C # (3.0) додано у відповідь на запит коду:

List<string> listA = new List<string>{"2009-05-18","2009-05-19","2009-05-21'"};
List<string> listB = new List<string>{"2009-05-18","2009-05-18","2009-05-19","2009-05-19","2009-05-20","2009-05-21","2009-05-21","2009-05-22"};

HashSet<string> hashA = new HashSet<string>();
HashSet<string> hashB = new HashSet<string>();

foreach (string dateStrA in listA) hashA.Add(dateStrA);
foreach (string dateStrB in listB) hashB.Add(dateStrB);

foreach (string dateStrA in hashA)
{
    if (hashB.Contains(dateStrA)) hashB.Remove(dateStrA);
}

List<string> result = hashB.ToList<string>();

У вашому коді C # hashAзмінна фактично марна. listAЗамість цього ви можете зробити foreach , hashAякий лише повторюється і Containsніколи не викликається.
Vlasec

(Крім того, за умови, що в C # є метод RemoveAll, як у Java, ви можете уникнути створення власного циклу ... але знову ж таки, я підтримав вас, оскільки це рішення є принаймні набагато ефективнішим, ніж вибране.)
Vlasec,

1

Привіт, використовуйте цей клас, він порівняє обидва списки і точно відображає невідповідність чорно-білих обох списків.

import java.util.ArrayList;
import java.util.List;


public class ListCompare {

    /**
     * @param args
     */
    public static void main(String[] args) {
        List<String> dbVinList;
        dbVinList = new ArrayList<String>();
        List<String> ediVinList;
        ediVinList = new ArrayList<String>();           

        dbVinList.add("A");
        dbVinList.add("B");
        dbVinList.add("C");
        dbVinList.add("D");

        ediVinList.add("A");
        ediVinList.add("C");
        ediVinList.add("E");
        ediVinList.add("F");
        /*ediVinList.add("G");
        ediVinList.add("H");
        ediVinList.add("I");
        ediVinList.add("J");*/  

        List<String> dbVinListClone = dbVinList;
        List<String> ediVinListClone = ediVinList;

        boolean flag;
        String mismatchVins = null;
        if(dbVinListClone.containsAll(ediVinListClone)){
            flag = dbVinListClone.removeAll(ediVinListClone);   
            if(flag){
                mismatchVins = getMismatchVins(dbVinListClone);
            }
        }else{
            flag = ediVinListClone.removeAll(dbVinListClone);
            if(flag){
                mismatchVins = getMismatchVins(ediVinListClone);
            }
        }
        if(mismatchVins != null){
            System.out.println("mismatch vins : "+mismatchVins);
        }       

    }

    private static String getMismatchVins(List<String> mismatchList){
        StringBuilder mismatchVins = new StringBuilder();
        int i = 0;
        for(String mismatch : mismatchList){
            i++;
            if(i < mismatchList.size() && i!=5){
                mismatchVins.append(mismatch).append(",");  
            }else{
                mismatchVins.append(mismatch);
            }
            if(i==5){               
                break;
            }
        }
        String mismatch1;
        if(mismatchVins.length() > 100){
            mismatch1 = mismatchVins.substring(0, 99);
        }else{
            mismatch1 = mismatchVins.toString();
        }       
        return mismatch1;
    }

}

Чи знали ви, що клони насправді взагалі не є клонами?
Vlasec

1

ЦЯ РОБОТА ТАКОЖ З Arraylist

    // Create a couple ArrayList objects and populate them
    // with some delicious fruits.
    ArrayList<String> firstList = new ArrayList<String>() {/**
         * 
         */
        private static final long serialVersionUID = 1L;

    {
        add("apple");
        add("orange");
        add("pea");
    }};

    ArrayList<String> secondList = new ArrayList<String>() {

    /**
         * 
         */
        private static final long serialVersionUID = 1L;

    {
        add("apple");
        add("orange");
        add("banana");
        add("strawberry");
    }};

    // Show the "before" lists
    System.out.println("First List: " + firstList);
    System.out.println("Second List: " + secondList);

    // Remove all elements in firstList from secondList
    secondList.removeAll(firstList);

    // Show the "after" list
    System.out.println("Result: " + secondList);

1
результат: Перший список: [яблуко, апельсин, піппо] Другий список: [яблуко, апельсин, банан, полуниця] Результат: [банан, полуниця]
психо

Це робить. Але коли ви так говорите, не слід забувати зазначити, що у великих списках це може бути болісно повільним. Майте на увазі, що методи подобаються removeі containsпотребують пошуку по всьому списку. Якщо повторно викликати в циклі (що трапляється в removeAll), ви отримуєте квадратичну складність. Однак ви можете використовувати хеш-набір і мати його просто лінійним.
Vlasec
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.