извлекать необычные элементы из списка на основе атрибута в другом списке - PullRequest
0 голосов
/ 22 июня 2019

Моя структура

   A {
       String id;
       String bid;
   }

   B {
       String id;
   }

Дано

List<A> aList = Arrays.asList(
   new A (1,2),
   new A (2,5),
   new A (3,9),
   new A (4,10),
   new A (5, 20),
   new A (6, 8),
   new A (7, 90)
   new A (8, 1)
);

List<B> bList = Arrays.asList(
   new B (2),
   new B (9),
   new B (10)
);

Теперь я хочу, чтобы элементы A, которые не совпадают ни с одним элементом B, были собраны в другой коллекции, и эти элементы должны быть удалены из самой коллекции A.

Результат

 List<A> aList = Arrays.asList(
       new A (1,2),
       new A (3,9),
       new A (4,10)
    );

    List<A> aListBin = Arrays.asList(
       new A (2,5),
       new A (5, 20),
       new A (6, 8),
       new A (7, 90)
       new A (8, 1)
    );

МОЙ дубль

Я могу подумать об итерации A с использованием итератора, и для каждого элемента в A итерации по B, а если найдены, продолжайте добавлять в отдельный список и удалять с помощью итератора remove.

Есть ли лучший способ сделать это с помощью потоковой магии? Спасибо

Ответы [ 5 ]

1 голос
/ 22 июня 2019

Коллекционеры # разделBy ваш друг.

Сначала мы извлечем id s из списка B s в чистое Set<Integer>, поэтому мы можем использовать его для поиска:

Set<Integer> bSet = bList.stream()
    .map(b -> b.id)
    .collect(Collectors.toSet());

Как упоминал Дж. Б. Низет, для работы подходит HashSet.

Тогда это так же просто, как это - мы разделим по предикату. Предикат заключается в том, содержится ли A.bid в каком-либо B.id (который мы для удобства сохранили в bSet).

Map<Boolean, List<A>> map = aList.stream()
    .collect(Collectors.partitioningBy(a -> bSet.contains(a.bid)));

Теперь map.get(true) содержит все элементы, содержащиеся в B, map.get(false) всех остальных.

Чтобы заменить aList, просто переназначьте aList:

aList = map.get(true);
0 голосов
/ 22 июня 2019

Поскольку вы имеете дело с двумя разными классами, вы не можете сравнивать их напрямую.Таким образом, вам нужно уменьшить до наименьшего общего знаменателя, который является целочисленными идентификаторами.

   // start a stream of aList.
   List<A> aListBin = aList.stream()

   // Convert the bList to a collection of
   // of ID's so you can filter.       
   .filter(a -> !bList.stream()

         // get the b ID
         .map(b->b.id)

         // put it in a list         
        .collect(Collectors.toList())

         // test to see if that list of b's ID's
         // contains a's bID   
         .contains(a.bid))

    //if id doesn't contain it, then at to the list.
    .collect(Collectors.toList());

To finish up, remove the newly created list from the aList.

        aList.removeAll(aListBin);

Они отображаются следующим образом:

        System.out.println("aListBin = " + aListBin);
        System.out.println("aList = " + aList);
        aListBin = [[2, 5], [5, 20], [6, 8], [7, 90], [8, 1]]
        aList = [[1, 2], [3, 9], [4, 10]]

Примечание:

  • Чтобы изменить содержимое каждого окончательного списка, удалите удар (!) Из фильтра.
  • Я добавил методы toString в классы, чтобы разрешить печать.
0 голосов
/ 22 июня 2019

Да, вы можете использовать Java 8 Streams .

Вот полный пример из вашего ввода:

import java.util.*;
import java.util.stream.*;
import static java.util.stream.Collectors.toList;

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

        class A {
            public int id;
            public int bid;
            public A(int id, int bid) { this.id = id; this.bid = bid; }
            public String toString() { return "(" + id + "," + bid + ")"; }
        };

        class B {
            public int id;
            public B(int id) { this.id = id; }
            public String toString() { return "" + id; }
        };

        List<A> aList = Arrays.asList(
                new A (1,2),  // not removed
                new A (2,5),  // removed
                new A (3,9),  // not removed
                new A (4,10), // not removed
                new A (5, 20),// not removed
                new A (6, 8), // not removed
                new A (7, 90),// not removed
                new A (8, 1)// not removed
        );


        List<B> bList = Arrays.asList(
                new B (2),
                new B (9),
                new B (10)
        );


        List<A> aListBin = new ArrayList<>();
        aList.stream()
            .forEach( a -> {
                if (bList.stream().noneMatch(b -> b.id == a.bid )) {
                    aListBin.add(a);        
                }
            });

        aList = aList.stream()
        .filter( a -> bList.stream().anyMatch(b -> b.id == a.bid))
        .collect(toList());

        System.out.println("Alist-> " + aList);
        System.out.println("Blist-> " + bList);
        System.out.println("Removed-> " + aListBin);
    }
}

Выход:

Alist-> [(1,2), (3,9), (4,10)]
Blist-> [2, 9, 10]
Removed-> [(2,5), (5,20), (6,8), (7,90), (8,1)]
0 голосов
/ 22 июня 2019

Вы можете использовать Collectors.partitioningBy. Это лучше? Зависит от вашего определения лучше. Это гораздо более краткий код, однако он не так эффективен, как описанный вами простой цикл итератора.

Я не могу придумать более эффективный способ, чем маршрут итератора, за исключением, возможно, использования строкового хэш-набора для поиска идентификатора класса B.

Однако, если вы предпочитаете сжатый код, вот код с использованием partitioningBy:

class A {
    int id;
    int bid;

    public A(int id, int bid){
        this.id = id;
        this.bid = bid;
    }

    public boolean containsBId(List<B> bList) {
        return bList.stream().anyMatch(b -> bid == b.id);
    }
}

class B {
    int id;

    public B(int id){
        this.id = id;
    }
}

class Main {

public static void main(String[] args) {
    List<A> aList = Arrays.asList(
        new A (1,2),
        new A (2,5),
        new A (3,9),
        new A (4,10),
        new A (5, 20),
        new A (6, 8),
        new A (7, 90),
        new A (8, 1)
    );
    List<B> bList = Arrays.asList(
        new B (2),
        new B (9),
        new B (10)
    );
    Map<Boolean, List<A>> split = aList.stream()
        .collect(Collectors.partitioningBy(a -> a.containsBId(bList)));

    aList = split.get(true);
    List<A> aListBin = split.get(false);
}
0 голосов
/ 22 июня 2019

Вы не можете удалить элементы из списка, созданного с помощью Arrays.asList (). Он возвращает представление массива, который вы передаете в качестве аргумента, поэтому вызов remove выдает исключение UnsupportedOperationException.

Предполагая, что вместо этого у вас есть ArrayList, я все еще не думаю, что вы сможете достичь этого за один шаг, и уж точно, не с помощью "волшебства потока", поскольку потоки не позволят вам изменить исходную коллекцию.

В два этапа, что-то вроде:

List<A> newList = aList.stream()
    .filter(a -> !bList.contains(a.bid))
    .collect(Collectors.toList());
aList.removeAll(newList);

Если производительность представляет собой проблему, используйте Set или Map (с идентификатором в качестве ключа) вместо List, чтобы выполнить функции contains () и removeAll () в O (1) и O (n) соответственно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...