Как я могу рассчитать разницу между двумя списками ArrayList?


81

У меня есть два списка ArrayList.

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это чувствительно к регистру , поэтому «яблоко» и «Яблоко» не будут считаться таким же.
Basil Bourque

1
На самом деле ответ зависит от того, чем вы хотите заниматься. RemoveAll не сохраняет дубликаты. Если вы добавите еще одну строку «яблоко» во второй список, она также будет удалена, что не всегда может быть тем, что вам нужно.
Julles Testard 01

2
Это так неэффективно. Печально, что это и выбранный, и лучший ответ. removeAllпризывает firstList.containsк каждому элементу secondList. Использование a HashSetмогло бы предотвратить это, и ниже есть несколько хороших ответов.
Vlasec

20

У тебя уже есть правильный ответ. И если вы хотите сделать более сложные и интересные операции между списками (коллекциями), используйте коллекции общего доступа apache ( CollectionUtils ). Это позволяет вам создавать соединение / дизъюнкцию, находить пересечения, проверять, является ли одна коллекция подмножеством другой, и другие приятные вещи.



12

В Java 8 с потоками все довольно просто. РЕДАКТИРОВАТЬ: может быть эффективным без потоков, см. Ниже.

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());

Обратите внимание, что хэш-набор создается только один раз: ссылка на метод привязана к его методу contains. То же самое с лямбда-выражением потребует наличия набора в переменной. Создание переменной - неплохая идея, особенно если вы находите ее неприглядной или трудной для понимания.

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

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 02

1
@Bax Нет, это не так.
shmosel 03

1
С Guava вы можете это сделать Predicates.in(new HashSet<>(listA)).negate().
shmosel 03

1
Я просто провожу тест, и эти решения на ~ 10-20% быстрее, чем listB.removeAll (new HashSet <> (listA)). и Guava Sets.difference (...) si в 2 раза медленнее, чем стримы.
telebog

1
@Vlasec ArrayList.removeимеет линейную сложность, но ArrayList.removeAllне полагается, removeа выполняет операцию обновления линейного массива, копируя каждый оставшийся элемент на свое последнее место. Напротив, эталонная реализация LinkedListне оптимизирована, removeAllно выполняет removeоперацию для каждого затронутого элемента, которая будет обновлять до пяти ссылок каждый раз. Таким образом, в зависимости от соотношения между удаленными и оставшимися элементами, ArrayList's removeAllможет работать значительно лучше, чем LinkedList' s, даже для огромных списков.
Holger

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

Я использовал Guava Sets.difference .

Параметры - это наборы, а не общие коллекции, но удобный способ создания наборов из любой коллекции (с уникальными элементами) - это Guava 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.