Java Streams - 从另外两个列表中获取"对称差异列表"

tor*_*bro 6 java list java-8 java-stream

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

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

示例代码:

public void testDisjointLists() {
    List<Car> bigCarList = get5DefaultCars();
    List<Car> smallCarList = get3DefaultCars();

    //Get cars that exists in both lists
    List<Car> intersect = bigCarList.stream().filter(smallCarList::contains).collect(Collectors.toList());

    //Get all cars in both list as one list
    List<Car> union = Stream.concat(bigCarList.stream(), smallCarList.stream()).distinct().collect(Collectors.toList());

    //Get all cars that only exist in one list
    //List<Car> disjoint = ???

}

public List<Car> get5DefaultCars() {
    List<Car> cars = get3DefaultCars();
    cars.add(new Car("Toyota Corolla", 2008));
    cars.add(new Car("Ford Focus", 2010));
    return cars;
}

public List<Car> get3DefaultCars() {
    List<Car> 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()
}
Run Code Online (Sandbox Code Playgroud)

Hol*_*ger 9

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

List<Car> disjoint = Stream.concat(
    bigCarList.stream().filter(c->!smallCarList.contains(c)),
    smallCarList.stream().filter(c->!bigCarList.contains(c))
).collect(Collectors.toList());
Run Code Online (Sandbox Code Playgroud)

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

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


但是如果你使用Sets,代码可以更简单:

Set<Car> disjoint = Stream.concat(bigCarSet.stream(), smallCarSet.stream())
  .collect(Collectors.toMap(Function.identity(), t->true, (a,b)->null))
  .keySet();
Run Code Online (Sandbox Code Playgroud)

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

的文件Collectors.toMap说,合并功能被视为"作为提供给Map.merge(Object, Object, BiFunction)",我们可以从那里学习,简单地映射重复配对null会删除该条目.

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

  • @Holger 不错!!!我没有注意到文档中的那部分:*.. 或者如果结果为空则将其删除*。惊人的 (2认同)

Tag*_*eev 6

像这样的事情可能会起作用:

Stream.concat(bigCarList.stream(), smallCarList.stream())
      .collect(groupingBy(Function.identity(), counting()))
      .entrySet().stream()
      .filter(e -> e.getValue().equals(1L))
      .map(Map.Entry::getKey)
      .collect(toList());
Run Code Online (Sandbox Code Playgroud)

这里我们首先收集所有的汽车,Map<Car, Long>其中的值是遇到的此类汽车的数量。在那之后,我们filter这个Map只留下遇到正好一次车,落计数并收集到最后List

  • 如果源列表包含重复项,这将不起作用。但我认为这是问题的先决条件的问题,因为 OP 似乎实际上想要`Set`s。但是当使用`Set`s时,解决方案可以[更简单](http://stackoverflow.com/a/31077178/2711488)... (2认同)