Java 不同的流 属性
Java stream that is distinct by more than one property
我在流中有以下对象:
class Foo{
String a;
String b;
int c;
}
我想根据以下条件过滤流:
例如。在流中有条目:foo1
和 foo2
:
foo1
和foo2
对于a
和b
具有相同的值,但它们在c
属性.[=21中不同=]
在这种情况下,我想删除 c
更高的条目。
一定有更好的方法来做到这一点,但这里有一个解决方案。
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());
- 对于列表中的所有元素
- 遍历所有元素,
- 并找到匹配
A
和 B
的那些
- 按
C
排序并获得最低的
- 保留第 1 步中的元素,如果它是最低的 Foo
C
- 将结果收集到新列表
您可以使用 groupBy
对 Foo
对象进行分组并将它们视为列表:
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
所以如果我从你的评论中理解正确的话,它应该是这样的:
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);
简单的解决方案是
.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();
}
语义等同于,但更简单:
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
类型,您可以使用具有两个元素的 List
或 Map.Entry
,两者都有效。但是使用 List
更简单(在 Java 9 中,您可以使用 List.of(…, …)
,这甚至更简单)并且如果两个属性中可能出现相同的值,则具有更好的哈希码。
当 dowstream 操作是纯粹的减少时,比如选择 C
属性 中的最小值,toMap
收集器更适合,因为它不需要处理 Optional
.
有一种方法可以在没有流的情况下做到这一点。我知道这个问题特别要求基于流的解决方案,但我认为这是实现相同目标的好方法。我写这个答案主要是作为对其他答案的补充,也许对未来的读者有用。
代码如下:
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
来减少 Foo
个具有相同 a
和 b
的实例。
注意:您也可以像 Holger 在他的回答中那样做,并使用 BinaryOperator.minBy
:
来减少
list.forEach(foo -> map.merge(Arrays.asList(foo.getA(), foo.getB()), foo,
BinaryOperator.minBy(Comparator.comparingInt(Foo::getC))));
我在流中有以下对象:
class Foo{
String a;
String b;
int c;
}
我想根据以下条件过滤流:
例如。在流中有条目:foo1
和 foo2
:
foo1
和foo2
对于a
和b
具有相同的值,但它们在c
属性.[=21中不同=]
在这种情况下,我想删除 c
更高的条目。
一定有更好的方法来做到这一点,但这里有一个解决方案。
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());
- 对于列表中的所有元素
- 遍历所有元素,
- 并找到匹配
A
和B
的那些
- 按
C
排序并获得最低的 - 保留第 1 步中的元素,如果它是最低的 Foo
C
- 将结果收集到新列表
您可以使用 groupBy
对 Foo
对象进行分组并将它们视为列表:
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
所以如果我从你的评论中理解正确的话,它应该是这样的:
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);
简单的解决方案是
.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();
}
语义等同于
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
类型,您可以使用具有两个元素的 List
或 Map.Entry
,两者都有效。但是使用 List
更简单(在 Java 9 中,您可以使用 List.of(…, …)
,这甚至更简单)并且如果两个属性中可能出现相同的值,则具有更好的哈希码。
当 dowstream 操作是纯粹的减少时,比如选择 C
属性 中的最小值,toMap
收集器更适合,因为它不需要处理 Optional
.
有一种方法可以在没有流的情况下做到这一点。我知道这个问题特别要求基于流的解决方案,但我认为这是实现相同目标的好方法。我写这个答案主要是作为对其他答案的补充,也许对未来的读者有用。
代码如下:
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
来减少 Foo
个具有相同 a
和 b
的实例。
注意:您也可以像 Holger 在他的回答中那样做,并使用 BinaryOperator.minBy
:
list.forEach(foo -> map.merge(Arrays.asList(foo.getA(), foo.getB()), foo,
BinaryOperator.minBy(Comparator.comparingInt(Foo::getC))));