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

时间:2015-06-26 13:30:26

标签: java list java-8 java-stream

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

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

示例代码:

.bxslider

7 个答案:

答案 0 :(得分:8)

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

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

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

您似乎真的想要Set,而不是List。如果您使用Set s,则Tagir Valeev’s solution是合适的。但是它没有使用List的实际语义,即如果源列表包含重复项则不起作用。

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

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

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

documentation of Collectors.toMap表示合并功能被视为“提供给Map.merge(Object, Object, BiFunction)”,我们可以从那里学习,只需将副本对映射到true即可删除该条目。

之后,地图的null包含不相交的集合。

答案 1 :(得分:4)

这样的事情可能有用:

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<Car, Long>,其中值是遇到的此类车辆的数量。在那之后我们过滤这张地图,只留下恰好曾经过一次的车,丢弃计数并收集到最后的List

答案 2 :(得分:0)

一点点数学

disjoint =如果相交为空,则A和B不相交。

不相交不是一个集合,它是一个指示器,显示两个集合是否不相交。根据您的描述,我认为您在搜索symmetric difference

对称差异

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

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

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

    Collector<Car, ArrayList<Car>, ArrayList<Car>> inter
        = produceCollector(car -> {
          return bigCarList.contains(car) && smallCarList.contains(car);
        });

    Collector<Car, ArrayList<Car>, ArrayList<Car>> symDiff
        = produceCollector(car -> {
          return bigCarList.contains(car) ^ smallCarList.contains(car);
        });

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

    List<Car> intersect = union.stream().collect(inter);

    //Get all cars that only exist not exists in both Lists
    List<Car> 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<Car, ArrayList<Car>, ArrayList<Car>> produceCollector(Predicate<Car> predicate) {
    Collector<Car, ArrayList<Car>, ArrayList<Car>> 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<Car> 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<Car> intersect = union.stream().collect(inter);
long after = System.nanoTime();
System.out.println("Time for collector solution: " + (after - before));

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

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

答案 3 :(得分:0)

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

解决方案就是“holger”给了我的意思。感谢。

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

)收集(Collectors.toList());

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

感谢您的帮助。

答案 4 :(得分:0)

另一种方法,尽管不如一行流优雅:

    HashMap<Integer, Boolean> y = new HashMap<>();
    bigCarSet ().forEach(i -> y.put(i, !y.containsKey(i)));
    bigCarList().forEach(i -> y.put(i, !y.containsKey(i)));
    y.entrySet().stream().filter(Map.Entry::getValue).map(Map.Entry::getKey)
     .collect(Collectors.toList());

至少可以简化为:

    HashMap<Integer, Boolean> y = new HashMap<>();
    Stream.concat(list1.stream(), list2.stream()).forEach(i -> y.put(i, !y.containsKey(i)));
    y.entrySet().stream().filter(Map.Entry::getValue)
                 .map(Map.Entry::getKey).collect(Collectors.toList());

答案 5 :(得分:0)

OP正在要求对称差异。对称差可以表示为:

  1. 并集和相交之间的区别:

    A△B =(A∪B)-(B∩A)

  2. 或差异的并集:

    A△B =(A – B)∪(B – A)

this answer的第一部分通过#2实现,而第二部分通过#1实现。在这里,我将展示方法#1的变体:

List<Car> result = new ArrayList<>(bigCarList);
result.addAll(smallCarList); // (A ∪ B)

result.removeIf(c -> bigCarList.contains(c) && smallCarList.contains(c)); // (B ∩ A)

如果将列表转换为集合,则可以进行优化,以便使用containsO(1)

List<Car> bigCarList = get5DefaultCars();
List<Car> smallCarList = get3DefaultCars();

Set<Car> bigCarSet = new HashSet<>(bigCarList);
Set<Car> smallCarSet = new HashSet<>(smallCarList);

Set<Car> result = new LinkedHashSet<>(bigCarList);
result.addAll(smallCarList); // (A ∪ B)

result.removeIf(c -> bigCarSet.contains(c) && smallCarSet.contains(c)); // (B ∩ A)

答案 6 :(得分:0)

带有 groupingBy 的lambda解:
两个列表中带有true键的地图值
使用false键的地图值是不相交的

Map<Boolean,List<Car>> map = Stream.concat(bigCarList.stream(),
    smallCarList.stream()).collect(
        groupingBy( b -> bigCarList.stream().anyMatch( s -> b.equals( s ) )
            && smallCarList.stream().anyMatch( s -> b.equals( s ) ) ) );
List<Car> disjoint = map.get( false );  // [Toyota Corolla, Ford Focus]


原理相同,但没有内联流更短:

Map<Boolean,List<Car>> map = Stream.concat(bigCarList.stream(),
    smallCarList.stream()).collect(
        groupingBy( b -> bigCarList.contains( b )
            && smallCarList.contains( b ) ) );
List<Car> disjoint = map.get( false );  // [Toyota Corolla, Ford Focus]

都在处理重复项
意思是:一个列表中的重复项不包含在另一列表中
如果数据量不是那么大,以至于您遇到磁盘空间问题,那么简单的groupingBy -无需过滤或进行其他查询以减少结果集-应该是最清晰,最快的解决方案。