简体   繁体   English

根据另一个列表中的属性从列表中提取不常见的元素

[英]extract uncommon elements from list based on attribute in another list

My Structure 我的结构

   A {
       String id;
       String bid;
   }

   B {
       String id;
   }

Given 特定

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

Now i want the elements of A which don't match with any B's element should be collected in another collection and these elements should be deleted from A collection itself. 现在,我希望将与任何B元素都不匹配的A元素收集到另一个集合中,并且应该从A集合本身中删除这些元素。

Result 结果

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

MY take 我拿

I can think of iterating A using iterator and for each element in A iterate through B and if found keep else keep adding to separate list and delete using iterator remove. 我可以想到使用迭代器来迭代A,并针对A中的每个元素遍历B,如果找到,则继续添加其他内容,并使用迭代器remove删除。

Is there a better way to do this using stream magic? 有使用流魔术的更好方法吗? Thanks 谢谢

Collectors#partitionBy is your friend. Collectors#partitionBy是您的朋友。

First, we'll extract the id s from the list of B s into a bare Set<Integer> , so we can use that for lookup: 首先,我们将从B的列表中提取id到裸露的Set<Integer> ,因此我们可以将其用于查找:

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

As mentioned by JB Nizet, a HashSet is fit for the job. 正如JB Nizet提到的那样, HashSet适合该工作。

Then it's just as simple as this – we'll partition by a given predicate. 然后就这么简单–我们将按给定的谓词进行分区。 The predicate is whether A.bid is contained in any B.id (which we stored into bSet for convenience). 谓词是A.bid是否包含在任何B.id (为方便起见,我们将其存储在bSet中)。

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

Now map.get(true) contains all items contained in B , map.get(false) all others. 现在map.get(true)包含B包含的所有项目, map.get(false)所有其他项目。

In order to replace aList , simply reassign aList : 为了替换aList ,只需重新分配aList

aList = map.get(true);

You can't remove elements from a list created with Arrays.asList(). 您不能从使用Arrays.asList()创建的列表中删除元素。 It returns a view of the array you pass as argument, so calling remove will throw UnsupportedOperationException. 它返回您作为参数传递的数组的视图,因此调用remove将引发UnsupportedOperationException。

Assuming you have ArrayList instead, I still don't think you can achieve this on a single step, and certainly not with "stream magic", since streams won't allow you to modify the original collection. 假设您有ArrayList,我仍然认为您不能一步一步实现,当然也不能用“流魔术”来实现,因为流不允许您修改原始集合。

In two steps, something like: 分两个步骤:

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

If performance is an issue, use Set or Map(with id as key) instead of List in order to perform the contains() and the removeAll() in O(1) and O(n) respectively. 如果性能是一个问题,请使用Set或Map(以id为键)而不是List来分别在O(1)和O(n)中执行contains()和removeAll()。

Yes, you can use Java 8 Streams . 是的,您可以使用Java 8 Streams

Here's the full example from your input: 这是您输入中的完整示例:

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

Output: 输出:

Alist-> [(1,2), (3,9), (4,10)]
Blist-> [2, 9, 10]
Removed-> [(2,5), (5,20), (6,8), (7,90), (8,1)]

You can use Collectors.partitioningBy . 您可以使用Collectors.partitioningBy Is it better? 好点吗? Depends on your definition of better. 取决于您的定义更好。 It is much more concise code, however it is not as efficient as the simple iterator loop you described. 它是更简洁的代码,但是效率不如您描述的简单迭代器循环。

I cannot think of a more efficient way than the iterator route, except maybe using a string hashset for lookup of the B class id. 除了可能使用字符串哈希集查找B类ID之外,我没有想到一种比迭代器路由更有效的方法。

However, if you prefer concise code, here is the code using partitioningBy: 但是,如果您希望使用简洁的代码,则下面是使用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);
}

Since you are dealing with two different classes you can't compare them directly. 由于您要处理两个不同的类,因此无法直接比较它们。 So you need to reduce to the least common denominator which is the integer ID's. 因此,您需要简化为最小公分母,即整数ID。

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

They are displayed as follows: 它们显示如下:

        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]]

Note: 注意:

  • To reverse the contents of each final list, remove the bang(!) from the filter. 要反转每个最终列表的内容,请从过滤器中删除bang(!)。
  • I added toString methods in the classes to allow printing. 我在类中添加了toString方法以允许打印。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM