Java Streams – 从另外两个列表中获取“对称差异列表”

我试图使用Java 8流来组合列表。 如何从两个现有列表中获取“对称差异列表”(仅存在于一个列表中的所有对象)。 我知道如何获得交叉列表以及如何获取联合列表。

在下面的代码中,我想要来自两个汽车列表(bigCarList,smallCarList)的不相交的汽车。 我希望结果能够列出2辆车(“丰田卡罗拉”和“福特福克斯”)

示例代码:

public void testDisjointLists() { List bigCarList = get5DefaultCars(); List smallCarList = get3DefaultCars(); //Get cars that exists in both lists List intersect = bigCarList.stream().filter(smallCarList::contains).collect(Collectors.toList()); //Get all cars in both list as one list List union = Stream.concat(bigCarList.stream(), smallCarList.stream()).distinct().collect(Collectors.toList()); //Get all cars that only exist in one list //List disjoint = ??? } public List get5DefaultCars() { List cars = get3DefaultCars(); cars.add(new Car("Toyota Corolla", 2008)); cars.add(new Car("Ford Focus", 2010)); return cars; } public List get3DefaultCars() { List cars = new ArrayList(); cars.add(new Car("Volvo V70", 1990)); cars.add(new Car("BMW I3", 1999)); cars.add(new Car("Audi A3", 2005)); return cars; } class Car { private int releaseYear; private String name; public Car(String name) { this.name = name; } public Car(String name, int releaseYear) { this.name = name; this.releaseYear = releaseYear; } //Overridden equals() and hashCode() } 

根据您自己的代码,有一个直接的解决方案:

 List disjoint = Stream.concat( bigCarList.stream().filter(c->!smallCarList.contains(c)), smallCarList.stream().filter(c->!bigCarList.contains(c)) ).collect(Collectors.toList()); 

只需过滤一个列表,查看未包含在另一个中的所有项目,反之亦然,并将两个结果连接起来。 这对于小型列表非常有效,并且在考虑优化解决方案(如散列或使结果为distinct()您应该问自己为什么要使用列表,如果您既不需要,也不需要重复,也不需要特定的顺序。

看起来你真的想要Set s,而不是List s。 如果您使用Set s, Tagir Valeev的解决方案是合适的。 但是它没有使用List的实际语义,即如果源列表包含重复项则不起作用。


但是,如果您使用的是Set ,则代码可以更简单:

 Set disjoint = Stream.concat(bigCarSet.stream(), smallCarSet.stream()) .collect(Collectors.toMap(Function.identity(), t->true, (a,b)->null)) .keySet(); 

这使用toMap收集器创建一个Map (该值无关紧要,我们只是在这里映射为true )并使用合并函数来处理重复。 因为对于两个集合,重复只能在两个集合中包含项目时发生,这些是我们想要删除的项目。

Collectors.toMap的文档说合并函数被视为“提供给Map.merge(Object, Object, BiFunction) ”,我们可以从那里学习,简单地将副本对映射为null将删除该条目。

所以之后,地图的keySet()包含不相交的集合。

这样的事情可能有用:

 Stream.concat(bigCarList.stream(), smallCarList.stream()) .collect(groupingBy(Function.identity(), counting())) .entrySet().stream() .filter(e -> e.getValue().equals(1L)) .map(e -> e.getKey()) .collect(toList()); 

在这里,我们首先将所有车辆收集到Map ,其中值是遇到的此类车辆的数量。 在那之后我们过滤这张地图,只留下恰好曾经过一次的车,丢弃计数并收集到最终的List

一点点数学

disjoint = A和B如果它们的相交是空的则是不相交的。

不相交不是一个集合,它是一个指示器,显示两个集合是否不相交。 根据您的描述,我认为您在搜索对称差异的地方 。

对称差异

但无论如何,如果你只想收集到新的列表,那么你所需要的只是一个collections家。

我创建了一个创建收集器的方法。 此收集器仅“收集”值,其中谓词的计算结果为true。 因此,如果您正在搜索对称差异,那么您只需要一个谓词。

  public void testDisjointLists() { List bigCarList = get5DefaultCars(); List smallCarList = get3DefaultCars(); Collector, ArrayList> inter = produceCollector(car -> { return bigCarList.contains(car) && smallCarList.contains(car); }); Collector, ArrayList> symDiff = produceCollector(car -> { return bigCarList.contains(car) ^ smallCarList.contains(car); }); //Get all cars in both list as one list List union = Stream.concat(bigCarList.stream(), smallCarList.stream()).distinct().collect(Collectors.toList()); List intersect = union.stream().collect(inter); //Get all cars that only exist not exists in both Lists List symmetricDifference = union.stream().collect(symDiff); System.out.println("Union Cars:"); union.stream().forEach(car -> System.out.println("Car: " + car)); System.out.println(""); System.out.println("Intersect Cars: "); intersect.stream().forEach(car -> System.out.println("Car: " + car)); System.out.println(""); System.out.println("Symmetric Difference: "); symmetricDifference.stream().forEach(car -> System.out.println("Car: " + car)); System.out.println(""); } public Collector, ArrayList> produceCollector(Predicate predicate) { Collector, ArrayList> collector = Collector.of( ArrayList::new, (al, car) -> { if (predicate.test(car)) { al.add(car); } }, (al1, al2) -> { al1.addAll(al2); return al1; } ); return collector; } 

对于表演怪胎

经过一些研究后,收集器似乎比第一个滤波器解决方案快14倍。

 long before2 = System.nanoTime(); List intersect2 = union.stream().filter(car -> { return bigCarList.contains(car) && smallCarList.contains(car); }).collect(Collectors.toList()); long after2 = System.nanoTime(); System.out.println("Time for first filter solution: " + (after2 - before2)); long before = System.nanoTime(); List intersect = union.stream().collect(inter); long after = System.nanoTime(); System.out.println("Time for collector solution: " + (after - before)); 

第一次过滤解决方案的时间:540906

收集器解决方案的时间:37543

我所寻求的是两个列表的对称差异(我已经改变了问题):为什么我使用Lists而不是Set只是因为我在我的方法中有2个列表,否则一个集合会更合适。

解决方案是“holger”给我的上述内容。 谢谢。

 List disjoint = Stream.concat( bigCarList.stream().filter(c->!smallCarList.contains(c)), smallCarList.stream().filter(c->!bigCarList.contains(c)) 

).collect(Collectors.toList());

这个列表实际上得到了两辆车丰田和福特只存在于任何一个列表中(我尝试了两个列表与独特的汽车,结果是正确的)。

谢谢你的帮助。