由多个属性区分的Java流

时间:2017-07-13 10:39:04

标签: java filtering java-stream distinct-values

我在流中有以下对象:

class Foo{
    String a;
    String b;
    int c;
}

我想根据以下条件过滤流:

例如。在流中添加条目:foo1foo2

foo1foo2的{​​{1}}和a值相同,但b属性不同。

我希望摆脱在这种情况下c更高的条目。

6 个答案:

答案 0 :(得分:3)

因此,如果我从您的评论中正确理解,它应该如下所示:

 List<Foo> foos = Stream.of(new Foo("a", "b", 1), new Foo("a", "b", 2), new Foo("a", "b", 3),
            new Foo("a", "bb", 3), new Foo("aa", "b", 3))
            .collect(Collectors.collectingAndThen(
                    Collectors.groupingBy(
                            x -> new AbstractMap.SimpleEntry<>(x.getA(), x.getB()),
                            Collectors.minBy(Comparator.comparing(Foo::getC))),
                    map -> map.values().stream().map(Optional::get).collect(Collectors.toList())));

    System.out.println(foos);

答案 1 :(得分:3)

在语义上等同于Eugene’s answer,但更简单一点:

List<Foo> foos = Stream.of(new Foo("a", "b", 1), new Foo("a", "b", 2),
                 new Foo("a", "b", 3), new Foo("a", "bb", 3), new Foo("aa", "b", 3))
    .collect(Collectors.collectingAndThen(
        Collectors.toMap(x -> Arrays.asList(x.getA(), x.getB()), x -> x,
                         BinaryOperator.minBy(Comparator.comparing(Foo::getC))),
            map -> new ArrayList<>(map.values())));

您需要按一个包含两个属性的键进行分组,并且由于缺少标准Pair类型,您可以使用带有两个元素的ListMap.Entry 。但是使用List更简单(在Java 9中,您可以使用更简单的List.of(…, …))并且如果两个属性中可能出现相同的值,则具有更好的哈希码。

当下游操作纯粹减少时,例如选择C属性的最小值,toMap收集器更合适,因为它不需要处理Optional

答案 2 :(得分:1)

必须有一个更好的方法来做到这一点,但这是一个解决方案。

List<Foo> list = new ArrayList<>();

list.stream().filter(foo ->
    list.stream()
    .filter(oth -> foo.a.equals(oth.a) && foo.b.equals(oth.b))
    .sorted(Comparator.comparingInt(x -> x.c))
    .findFirst()
    .equals(Optional.of(foo))
)
.collect(Collectors.toList());
  1. 对于列表中的所有元素
  2. 浏览所有元素,
  3. 并找到匹配AB
  4. 的广告
  5. C排序并获得最低
  6. 保留第1步中的元素,如果它是具有最低C
  7. 的Foo
  8. 将结果收集到新列表

答案 3 :(得分:1)

简单的解决方案是

.stream()
.sorted((f1,f2) -> Integer.compare(f1.c, f2.c))
.distinct()

但它需要在Foo中进行丑陋的覆盖,这可能会破坏代码的另一部分

public boolean equals(Object other) {
    return a.equals(((Foo)other).a) && b.equals(((Foo)other).b);
}

public int hashCode() {
    return a.hashCode() + b.hashCode();
}

答案 4 :(得分:1)

没有溪流就可以做到这一点。我知道这个问题特别要求基于流的解决方案,但我认为这是实现相同目标的好方法。我写这个答案主要是作为其他答案的补充,也许对未来的读者有用。

以下是代码:

List<Foo> list = Arrays.asList(
    new Foo("a", "b", 1),
    new Foo("a", "b", 2),
    new Foo("a", "b", 3),
    new Foo("a1", "b", 1));

Map<List<String>, Foo> map = new HashMap<>();
list.forEach(foo -> map.merge(Arrays.asList(foo.getA(), foo.getB()), foo,
    (oldFoo, newFoo) -> newFoo.getC() < oldFoo.getC() ? newFoo : oldFoo));
Collection<Foo> distinct = map.values();

System.out.println(distinct);

这会迭代列表并使用Map.merge来减少具有相同Fooa的{​​{1}}个实例。

注意:您也可以在答案中使用Holger,并使用b

进行缩减
BinaryOperator.minBy

答案 5 :(得分:0)

您可以使用groupByFoo个对象进行分组,并将其视为列表:

    List<Foo> filtered = list.stream()
            .collect(Collectors.groupingBy(
                foo -> foo.a.hashCode() + foo.b.hashCode()))   // group them by attributes
            .values().stream()                                 // get a stream of List<Foo>
            .map(fooList -> {
                fooList.sort((o1, o2) -> o2.c - o1.c);         // order the list
                return fooList;
            })
               .map(fooList -> {                               // if there is more than 1 item remove it
                   if (fooList.size() > 1)
                       return fooList.subList(0, fooList.size() - 1);
                   else
                       return fooList;
               })
            .flatMap(Collection::stream)                        // Stream<List<Foo>> -> Stream<Foo>
            .collect(Collectors.toList());                      // collect