Перетин та об'єднання ArrayLists на Java


130

Чи є якісь методи для цього? Я шукав, але не зміг знайти жодного.

Ще одне питання: мені потрібні ці методи, щоб я міг фільтрувати файли. Деякі - це ANDфільтри, а деякі - ORфільтри (як у теорії наборів), тому мені потрібно фільтрувати відповідно до всіх файлів та об'єднувати / перетинати ArrayLists, що містить ці файли.

Чи варто використовувати іншу структуру даних для зберігання файлів? Чи є ще щось, що запропонувало б кращий час виконання?


1
Якщо ви не хочете створити новий список, Vector.retainAll (Vector) обрізає ваш оригінальний вектор лише до перетину з другим вектором.
user2808054

@ user2808054 чому Vector? Цей клас відмовився від Java 1.2.
dimo414

@ dimo414 інтерфейс, який я використовую (у мене немає можливості) повертає речі як вектори. Я не знав, що це відлякувало! Дякую за інформацію .. Хто відволікав кого? Я не бачив жодної записки про її застарілу, тому це сюрприз
user2808054

1
З Javadocs: " Станом на платформу Java 2 v1.2 ... рекомендується використовувати ArrayList замість Vector. " Єдиний час, який вам може знадобитися, Vectorце взаємодія міжпотокових потоків, але є безпечніші структури даних і для тих випадків використання. Дивіться також це питання . Будь-яка бібліотека, яка все ще використовується Vectorу 2016 році, на мою думку, є дуже підозрілою.
dimo414

@ dimo414 це бібліотека IBM, так! (Дані Lotus Domino api). Дякую за інформацію, дуже корисна
user2808054

Відповіді:


122

Ось звичайна реалізація без використання будь-якої сторонньої бібліотеки. Основна перевага перед retainAll, removeAllі в addAllтому , що ці методи не змінюють початкові списки вкладу методів.

public class Test {

    public static void main(String... args) throws Exception {

        List<String> list1 = new ArrayList<String>(Arrays.asList("A", "B", "C"));
        List<String> list2 = new ArrayList<String>(Arrays.asList("B", "C", "D", "E", "F"));

        System.out.println(new Test().intersection(list1, list2));
        System.out.println(new Test().union(list1, list2));
    }

    public <T> List<T> union(List<T> list1, List<T> list2) {
        Set<T> set = new HashSet<T>();

        set.addAll(list1);
        set.addAll(list2);

        return new ArrayList<T>(set);
    }

    public <T> List<T> intersection(List<T> list1, List<T> list2) {
        List<T> list = new ArrayList<T>();

        for (T t : list1) {
            if(list2.contains(t)) {
                list.add(t);
            }
        }

        return list;
    }
}

16
ви можете створити новий список з елементами list1, а потім викликати retainAll, addAll method
lukastymo

чому ви використовуєте у такому рішенні Strostfp?
lukastymo

9
Слід використовувати a HashSetдля, intersectionщоб середня ефективність випадку була O (n) замість O (n ^ 2).
Зонг

1
Ця публікація може використовувати оновлення, щоб продемонструвати переваги Java 8 Stream API.
SME_Dev

Я отримую помилку, коли я намагаюся призначити це значення -> Приклад: ArrayList <String> загальний загальний = (ArrayList <String>) перетин (list2, list1) ---> не може передати java.util.arraylist java.util.arraylist < string>
доставити

123

Колекція (так що також ArrayList) має:

col.retainAll(otherCol) // for intersection
col.addAll(otherCol) // for union

Використовуйте реалізацію списку, якщо ви приймаєте повторення, якщо встановити програму, якщо не:

Collection<String> col1 = new ArrayList<String>(); // {a, b, c}
// Collection<String> col1 = new TreeSet<String>();
col1.add("a");
col1.add("b");
col1.add("c");

Collection<String> col2 = new ArrayList<String>(); // {b, c, d, e}
// Collection<String> col2 = new TreeSet<String>();
col2.add("b");
col2.add("c");
col2.add("d");
col2.add("e");

col1.addAll(col2);
System.out.println(col1); 
//output for ArrayList: [a, b, c, b, c, d, e]
//output for TreeSet: [a, b, c, d, e]

3
Було запропоновано редагувати, що цей союз "невірний, оскільки він буде містити загальні елементи двічі" . Для редагування рекомендується використовувати HashSetзамість цього.
Кос

5
Насправді це було відредаговано, див .: "Використовуйте реалізацію списку, якщо ви приймаєте повторення, встановіть реалізацію, якщо цього не зробите:"
lukastymo

7
Ні, retainAll не є перетином для списку. Вище видаляються всі елементи в col, яких немає в іншихCol. Скажімо, otherCol є {a, b, b, c} і col є {b, b, b, c, d}. Тоді col закінчується на {b, b, b, c}, що не є строго перетином двох. Я б очікував, що це буде {b, b, c}. Здійснюється інша операція.
демонголем

1
Я також не бачу, як addAll()об’єднує списки; це просто об'єднання другого списку в кінець першого. Операція об'єднання дозволить уникнути додавання елемента, якщо перший список його вже містить.
dimo414

66

Ця публікація досить стара, але, тим не менш, вона була першою, що з’явилася в Google, коли шукали цю тему.

Я хочу дати оновлення, використовуючи потоки Java 8, роблячи (в основному) те ж саме в одному рядку:

List<T> intersect = list1.stream()
    .filter(list2::contains)
    .collect(Collectors.toList());

List<T> union = Stream.concat(list1.stream(), list2.stream())
    .distinct()
    .collect(Collectors.toList());

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


19
На жаль, це може бути хороший однолінійний, але це потребує часу O (n ^ 2). Перетворіть один із списків у Setпотім, використовуючи containsметод набору . Не все в житті доводиться робити потоками.
dimo414

31
list1.retainAll(list2) - is intersection

союз буде removeAllі тоді addAll.

Дізнайтеся більше в документації колекції (ArrayList - колекція) http://download.oracle.com/javase/1.5.0/docs/api/java/util/Collection.html


1
Обидва retainAll()і removeAll()є операціями O (n ^ 2) у списках. Ми можемо зробити краще.
dimo414

1
Я проголосував, але зараз у мене питання. retainAllз {1, 2, 2, 3, 4, 5} над {1, 2, 3} призводить до {1, 2, 2, 3}. Чи не слід {1, 2, 3} бути перетином?
GyuHyeon Choi

21

Союзи та перехрестя визначені лише для множин, а не для списків. Як ви згадали.

Перевірте бібліотеку guava на наявність фільтрів. Також guava забезпечує реальні перехрестя та союзи

 static <E> Sets.SetView<E >union(Set<? extends E> set1, Set<? extends E> set2)
 static <E> Sets.SetView<E> intersection(Set<E> set1, Set<?> set2)


7

Позначене рішення є неефективним. Він має складну часову складність O (n ^ 2). Що ми можемо зробити - це сортувати обидва списки та виконати алгоритм перетину як наведений нижче.

private  static ArrayList<Integer> interesect(ArrayList<Integer> f, ArrayList<Integer> s) { 
    ArrayList<Integer> res = new ArrayList<Integer>();

    int i = 0, j = 0; 
    while (i != f.size() && j != s.size()) { 

        if (f.get(i) < s.get(j)) {
            i ++;
        } else if (f.get(i) > s.get(j)) { 
            j ++;
        } else { 
            res.add(f.get(i)); 
            i ++;  j ++;
        }
    }


    return res; 
}

Цей має складність O (n log n + n), який знаходиться в O (n log n). Об’єднання робиться аналогічно. Просто переконайтеся, що ви внесли відповідні зміни в оператори if-elseif-else.

Ви також можете використовувати ітератори, якщо хочете (я знаю, що вони ефективніші в C ++, я не знаю, чи так це і в Java).


1
Не достатньо загального характеру, T може бути не порівнянним, а в деяких випадках порівняння коштує дорого ...
Борис Чурзін,

Не загальний, я повністю згоден. Порівняння дорого? як би ти це вирішив?
AJed

На жаль - було б дешевше зробити це в O (n ^ 2) :) Для чисел це рішення добре ...
Борис Чурзін

Сумно - ти не відповів на моє запитання. Дозвольте перефразувати це, як O (n ^ 2) краще давати функцію порівняння вартості c (n)?
AJed

1
Перетворення одного вводу в набір і виклик contains()у циклі (як це пропонує Devenv) зайняло б O (n + m) час. Сортування зайве складне і займає час O (n log n + m log n + n). Зазначено, що скорочується до часу O (n log n), але це все-таки гірше, ніж лінійний час, і набагато складніше.
dimo414

4

Я думаю, ви повинні використовувати a, Setщоб утримувати файли, якщо ви хочете зробити перехрестя та об'єднати їх. Потім ви можете використовувати гуави «s Встановлює клас робити union, intersectionі фільтрацію по Predicateа. Різниця між цими методами та іншими пропозиціями полягає в тому, що всі ці методи створюють ледачі погляди на об'єднання, перетин тощо. Apache Commons створює нову колекцію та копіює в неї дані. retainAllзмінює одну зі своїх колекцій, видаляючи з неї елементи.


4

Ось спосіб, як можна зробити перехрестя з потоками (пам’ятайте, що вам потрібно використовувати java 8 для потоків):

List<foo> fooList1 = new ArrayList<>(Arrays.asList(new foo(), new foo()));
List<foo> fooList2 = new ArrayList<>(Arrays.asList(new foo(), new foo()));
fooList1.stream().filter(f -> fooList2.contains(f)).collect(Collectors.toList());

Приклад для списків різних типів. Якщо у вас є реалізація між foo та bar і ви можете отримати смугу-об'єкт з foo, ніж ви можете змінити потік:

List<foo> fooList = new ArrayList<>(Arrays.asList(new foo(), new foo()));
List<bar> barList = new ArrayList<>(Arrays.asList(new bar(), new bar()));

fooList.stream().filter(f -> barList.contains(f.getBar()).collect(Collectors.toList());

3
  • retainAll змінить ваш список
  • У Guava немає API для списку (лише для набору)

Я знайшов ListUtils дуже корисним для цього випадку використання.

Використовуйте ListUtils з org.apache.commons.collections, якщо ви не хочете змінювати існуючий список.

ListUtils.intersection(list1, list2)


3

Ви можете використовувати commons-collection4 КолекціїUtils

Collection<Integer> collection1 = Arrays.asList(1, 2, 4, 5, 7, 8);
Collection<Integer> collection2 = Arrays.asList(2, 3, 4, 6, 8);

Collection<Integer> intersection = CollectionUtils.intersection(collection1, collection2);
System.out.println(intersection); // [2, 4, 8]

Collection<Integer> union = CollectionUtils.union(collection1, collection2);
System.out.println(union); // [1, 2, 3, 4, 5, 6, 7, 8]

Collection<Integer> subtract = CollectionUtils.subtract(collection1, collection2);
System.out.println(subtract); // [1, 5, 7]

2

У Java 8 я використовую такі прості допоміжні методи, як цей:

public static <T> Collection<T> getIntersection(Collection<T> coll1, Collection<T> coll2){
    return Stream.concat(coll1.stream(), coll2.stream())
            .filter(coll1::contains)
            .filter(coll2::contains)
            .collect(Collectors.toSet());
}

public static <T> Collection<T> getMinus(Collection<T> coll1, Collection<T> coll2){
    return coll1.stream().filter(not(coll2::contains)).collect(Collectors.toSet());
}

public static <T> Predicate<T> not(Predicate<T> t) {
    return t.negate();
}

1

Якщо об'єкти у списку є хешируемими (тобто мають пристойний хеш-код і дорівнює функції), найшвидший підхід між таблицями прибл. size> 20 - це побудувати HashSet для більшого з двох списків.

public static <T> ArrayList<T> intersection(Collection<T> a, Collection<T> b) {
    if (b.size() > a.size()) {
        return intersection(b, a);
    } else {
        if (b.size() > 20 && !(a instanceof HashSet)) {
            a = new HashSet(a);
        }
        ArrayList<T> result = new ArrayList();
        for (T objb : b) {
            if (a.contains(objb)) {
                result.add(objb);
            }
        }
        return result;
    }
}

1

Я також працював над подібною ситуацією і дістався тут, шукаючи допомоги. Закінчила пошук власного рішення для масивів. ArrayList AbsentDates = новий ArrayList (); // Зберігатиме Array1-Array2

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

ArrayList<String> AbsentDates = new ArrayList<String>();//This Array will store difference
      public void AbsentDays() {
            findDates("April", "2017");//Array one with dates in Month April 2017
            findPresentDays();//Array two carrying some dates which are subset of Dates in Month April 2017

            for (int i = 0; i < Dates.size(); i++) {

                for (int j = 0; j < PresentDates.size(); j++) {

                    if (Dates.get(i).equals(PresentDates.get(j))) {

                        Dates.remove(i);
                    }               

                }              
                AbsentDates = Dates;   
            }
            System.out.println(AbsentDates );
        }

1

Перетин двох списків різних об'єктів на основі спільного ключа - Java 8

 private List<User> intersection(List<User> users, List<OtherUser> list) {

        return list.stream()
                .flatMap(OtherUser -> users.stream()
                        .filter(user -> user.getId()
                                .equalsIgnoreCase(OtherUser.getId())))
                .collect(Collectors.toList());
    }

як щодо різниці, встановленої між цими двома списками?
жан

1
public static <T> Set<T> intersectCollections(Collection<T> col1, Collection<T> col2) {
    Set<T> set1, set2;
    if (col1 instanceof Set) {
        set1 = (Set) col1;
    } else {
        set1 = new HashSet<>(col1);
    }

    if (col2 instanceof Set) {
        set2 = (Set) col2;
    } else {
        set2 = new HashSet<>(col2);
    }

    Set<T> intersection = new HashSet<>(Math.min(set1.size(), set2.size()));

    for (T t : set1) {
        if (set2.contains(t)) {
            intersection.add(t);
        }
    }

    return intersection;
}

JDK8 + (мабуть, найкраща продуктивність)

public static <T> Set<T> intersectCollections(Collection<T> col1, Collection<T> col2) {
    boolean isCol1Larger = col1.size() > col2.size();
    Set<T> largerSet;
    Collection<T> smallerCol;

    if (isCol1Larger) {
        if (col1 instanceof Set) {
            largerSet = (Set<T>) col1;
        } else {
            largerSet = new HashSet<>(col1);
        }
        smallerCol = col2;
    } else {
        if (col2 instanceof Set) {
            largerSet = (Set<T>) col2;
        } else {
            largerSet = new HashSet<>(col2);
        }
        smallerCol = col1;
    }

    return smallerCol.stream()
            .filter(largerSet::contains)
            .collect(Collectors.toSet());
}

Якщо ви не дбаєте про ефективність і віддаєте перевагу менший код, просто використовуйте:

col1.stream().filter(col2::contains).collect(Collectors.toList());

0

Остаточне рішення:

//all sorted items from both
public <T> List<T> getListReunion(List<T> list1, List<T> list2) {
    Set<T> set = new HashSet<T>();
    set.addAll(list1);
    set.addAll(list2);
    return new ArrayList<T>(set);
}

//common items from both
public <T> List<T> getListIntersection(List<T> list1, List<T> list2) {
    list1.retainAll(list2);
    return list1;
}

//common items from list1 not present in list2
public <T> List<T> getListDifference(List<T> list1, List<T> list2) {
    list1.removeAll(list2);
    return list1;
}

0

По-перше, я копіюю всі значення масивів в один масив, потім я видаляю дублюючі значення в масив. У рядку 12, що пояснює, якщо однакове число трапляється більше, ніж час, тоді переведіть додаткове значення сміття у положення "j". Наприкінці перейдіть від початку до кінця та перевірте, чи є однакове значення сміття, а потім відкиньте.

public class Union {
public static void main(String[] args){

    int arr1[]={1,3,3,2,4,2,3,3,5,2,1,99};
    int arr2[]={1,3,2,1,3,2,4,6,3,4};
    int arr3[]=new int[arr1.length+arr2.length];

    for(int i=0;i<arr1.length;i++)
        arr3[i]=arr1[i];

    for(int i=0;i<arr2.length;i++)
        arr3[arr1.length+i]=arr2[i];
    System.out.println(Arrays.toString(arr3));

    for(int i=0;i<arr3.length;i++)
    {
        for(int j=i+1;j<arr3.length;j++)
        {
            if(arr3[i]==arr3[j])
                arr3[j]=99999999;          //line  12
        }
    }
    for(int i=0;i<arr3.length;i++)
    {
        if(arr3[i]!=99999999)
            System.out.print(arr3[i]+" ");
    }
}   
}

1
Ласкаво просимо до переповнення стека! Зверніть увагу, що питання стосується ArrayList. Також я боюся, що саме ця реалізація залишає бажати кращого. Значення 99999999, яке використовується як дозорний, може виникнути на вході. Було б краще використовувати динамічну структуру, наприклад ArrayList, для зберігання результатів об'єднання.
SL Barth - Відновіть Моніку

1
Поясніть, будь ласка, код, який ви подали, а не просто відповідь на код.
tmarois

Я лише даю підказку, що ви повинні поставити будь-яке значення сміття
Ашутош

Я радий, що ти додав пояснення. На жаль, сама відповідь все ще погана. Немає підстав використовувати масиви. Ви повинні використовувати таку динамічну структуру, як ArrayList. Якщо (з якихось причин) ви повинні використовувати масиви, вам слід розглянути можливість використання масиву, Integerа не int. Тоді ви можете використовувати nullзамість свого "значення сміття". "Значення сміття" або "дозорні значення", як правило, є поганою ідеєю, оскільки ці значення все ще можуть виникати на вводі.
SL Barth - Відновіть Моніку

0

Після тестування, ось мій найкращий підхід до перетину.

Більш швидка порівняно з чистим підходом HashSet. HashSet і HashMap нижче мають схожі показники для масивів з більш ніж 1 мільйоном записів.

Що стосується підходу Java 8 Stream, швидкість є досить повільною для розміру масиву, що перевищує 10 к.

Сподіваюся, що це може допомогти.

public static List<String> hashMapIntersection(List<String> target, List<String> support) {
    List<String> r = new ArrayList<String>();
    Map<String, Integer> map = new HashMap<String, Integer>();
    for (String s : support) {
        map.put(s, 0);
    }
    for (String s : target) {
        if (map.containsKey(s)) {
            r.add(s);
        }
    }
    return r;
}
public static List<String> hashSetIntersection(List<String> a, List<String> b) {
    Long start = System.currentTimeMillis();

    List<String> r = new ArrayList<String>();
    Set<String> set = new HashSet<String>(b);

    for (String s : a) {
        if (set.contains(s)) {
            r.add(s);
        }
    }
    print("intersection:" + r.size() + "-" + String.valueOf(System.currentTimeMillis() - start));
    return r;
}

public static void union(List<String> a, List<String> b) {
    Long start = System.currentTimeMillis();
    Set<String> r= new HashSet<String>(a);
    r.addAll(b);
    print("union:" + r.size() + "-" + String.valueOf(System.currentTimeMillis() - start));
}

0

використання методу retainAll () для пошуку загального елемента..ie; список перетину1.retainAll (list2)


-1

Якщо у вас були ваші дані в Sets, ви могли б використовувати Setsклас Guava .


-1

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

class Intersection
{
public static void main(String[] args)
 {
  String s="";
    int[] array1 = {1, 2, 5, 5, 8, 9, 7,2,3512451,4,4,5 ,10};
    int[] array2 = {1, 0, 6, 15, 6, 5,4, 1,7, 0,5,4,5,2,3,8,5,3512451};


       for (int i = 0; i < array1.length; i++)
       {
           for (int j = 0; j < array2.length; j++)
           {
               char c=(char)(array1[i]);
               if(array1[i] == (array2[j])&&s.indexOf(c)==-1)
               {    
                System.out.println("Common element is : "+(array1[i]));
                s+=c;
                }
           }
       }    
}

}


2
Не просто розміщуйте код як відповідь, дайте трохи пояснень, що ви робите
Брендон Замудіо

це моя перша програма, яку я завантажив
Ашутош

2
Хоча цей код може допомогти вирішити проблему, він не пояснює, чому та / або як він відповідає на питання. Надання цього додаткового контексту значно покращило б його довгострокове значення. Будь ласка , змініть свій відповідь , щоб додати пояснення, в тому числі те , що застосовувати обмеження і допущення.
Toby Speight
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.