如何在流分组或分区时忽略objects?
How to neglect objects during stream grouping or partitioning?
在Collectors.groupingBy
或Collectors.partitioningBy
期间可以忽略某些元素吗?
当然我知道我可以在流中的任何位置放置 .filter()
。但我的问题是,我必须 运行 一个相当复杂和昂贵的评估,来决定我的 objects 应该被划分到哪个“组”。
而且,总有很多objects我在收集的时候想忽略。
示例:想象一个 List<Foo>
,我想分成 2 个列表。这很简单,但是我怎么能另外忽略所有不符合我评估条件的 objects 呢?
var map = foos.stream().collect(Collectors.groupingBy(
foo -> {
int bar = complexEvaluation(foo);
if (bar > 1000) return true;
if (bar < 0) return false;
//TODO how to neglect the rest between 0-1000
},
Collectors.mapping(foo -> foo.id, Collectors.toSet())
));
要在 filter
和 groupingBy
中重用 complexEvaluation 的结果,您可以在过滤之前调用并将结果存储在包装器中 class。
foos.stream()
.map(foo -> {
int bar = complexEvaluation(foo);
if (bar > 1000) Pair.of(foo, true);
if (bar < 0) Pair.of(foo, false);
return Pair.of(foo, null);
)
.filter(fooPair -> fooPair.getRight() != null)
.collect(Collectors.groupingBy(
Pair::getRight(),
Collectors.mapping(fooPair -> fooPair.getLeft().id, Collectors.toSet()
);
但是,这仅适用于您出于某种原因坚持使用 groupingBy
的情况。
使用 foreach 的替代方案会更容易阅读:
Map<Boolean, Set<Foo>> groups = new HashMap<>();
foos.stream()
.forEach(foo -> {
int bar = complexEvaluation(foo);
if (bar > 1000) groups.computeIfAbsent(true, k->new HashSet<>()).add(foo);
if (bar < 0) groups.computeIfAbsent(false, k->new HashSet<>()).add(foo);
})
如果我正确理解您的意图,您希望在收集值时过滤掉一些值。 Collectors.filtering()
.
是可行的
请注意,filtering()
可以消除某些存储桶的所有值,但不会导致删除空存储桶。在下面的示例中,1、2、3、4、5 的存储桶将为空。
public static void main(String[] args) {
var foos = List.of(-100, 1, 2, 3, 4, 5, 1001, 1002, 1003);
var map = foos.stream()
.collect(Collectors.groupingBy(
UnaryOperator.identity(),
Collectors.filtering(foo -> foo < 0 || foo > 1000,
Collectors.toSet())));
System.out.println(map);
}
地图
{1=[], 2=[], 3=[], -100=[-100], 4=[], 5=[], 1001=[1001], 1002=[1002], 1003=[1003]}
更新
我修改了这个问题,下面提供的解决方案预先计算了值。
在此版本中,流中不需要的条目被过滤掉,这使得 Collector 中的代码更易于阅读。我希望使用整数而不是 foo
对象不是问题。
public static void main(String[] args) {
List<Integer> foos = List.of(-100, 1, 2, 3, 4, 5, 101, 102, 103);
Map<Integer, Integer> fooToValue = getFooToValueMap(foos);
Map<Boolean, Set<Integer>> map = getFoosMap(fooToValue);
System.out.println(map);
}
private static Map<Boolean, Set<Integer>> getFoosMap(Map<Integer, Integer> fooToValue) {
return fooToValue.entrySet().stream()
.filter(entry -> entry.getValue() < 0 || entry.getValue() > 1000)
.collect(Collectors.partitioningBy(
entry -> entry.getValue() > 1000,
Collectors.mapping(Map.Entry::getKey, Collectors.toSet())
));
}
private static Map<Integer, Integer> getFooToValueMap(List<Integer> list) {
return list.stream()
.collect(Collectors.toMap(UnaryOperator.identity(), foo -> complexEvaluation(foo)));
}
private static int complexEvaluation(int foo) {
return (int) Math.signum(foo) * foo * foo;
}
地图
{false=[-100], true=[101, 102, 103]}
只需使用 enum
来定义您的 3 个案例:
enum Categories {
HIGH, LOW, NEGATIVE
}
var map = foos.stream().collect(Collectors.groupingBy(
foo -> {
int bar = complexEvaluation(foo);
if (bar > 1000) return HIGH;
if (bar < 0) return NEGATIVE;
return LOW;
},
Collectors.mapping(foo -> foo.id, Collectors.toSet())
));
如果不需要,请忽略或删除 LOW
。它还具有为您的类别赋予更多意义而不是仅仅命名它们的额外好处 true
/false
,并且如果您将来需要更多类别,可以更轻松地重构。
唯一的缺点是它构建了一个无用的 LOW
集合,但这只是一个问题,如果它与其他集合和 complexEvaluation()
操作相比真的很大。
如果你想避免临时存储,你必须实现自己的收集器:
var map = foos.parallelStream().collect(
() -> Map.of(true, new HashSet<ID>(), false, new HashSet<ID>()),
(o, foo) -> {
int bar = complexEvaluation(foo);
ID id = foo.id;
if (bar > 1000) o.get(true).add(id);
else if (bar < 0) o.get(false).add(id);
},
(a, b) -> { a.get(true).addAll(b.get(true)); a.get(false).addAll(b.get(false)); }
);
此示例与 partitioningBy
具有相同的行为,始终为 true
和 false
创建条目。
ID
是您未包含在问题中的 foo.id
类型的占位符。
在Collectors.groupingBy
或Collectors.partitioningBy
期间可以忽略某些元素吗?
当然我知道我可以在流中的任何位置放置 .filter()
。但我的问题是,我必须 运行 一个相当复杂和昂贵的评估,来决定我的 objects 应该被划分到哪个“组”。
而且,总有很多objects我在收集的时候想忽略。
示例:想象一个 List<Foo>
,我想分成 2 个列表。这很简单,但是我怎么能另外忽略所有不符合我评估条件的 objects 呢?
var map = foos.stream().collect(Collectors.groupingBy(
foo -> {
int bar = complexEvaluation(foo);
if (bar > 1000) return true;
if (bar < 0) return false;
//TODO how to neglect the rest between 0-1000
},
Collectors.mapping(foo -> foo.id, Collectors.toSet())
));
要在 filter
和 groupingBy
中重用 complexEvaluation 的结果,您可以在过滤之前调用并将结果存储在包装器中 class。
foos.stream()
.map(foo -> {
int bar = complexEvaluation(foo);
if (bar > 1000) Pair.of(foo, true);
if (bar < 0) Pair.of(foo, false);
return Pair.of(foo, null);
)
.filter(fooPair -> fooPair.getRight() != null)
.collect(Collectors.groupingBy(
Pair::getRight(),
Collectors.mapping(fooPair -> fooPair.getLeft().id, Collectors.toSet()
);
但是,这仅适用于您出于某种原因坚持使用 groupingBy
的情况。
使用 foreach 的替代方案会更容易阅读:
Map<Boolean, Set<Foo>> groups = new HashMap<>();
foos.stream()
.forEach(foo -> {
int bar = complexEvaluation(foo);
if (bar > 1000) groups.computeIfAbsent(true, k->new HashSet<>()).add(foo);
if (bar < 0) groups.computeIfAbsent(false, k->new HashSet<>()).add(foo);
})
如果我正确理解您的意图,您希望在收集值时过滤掉一些值。 Collectors.filtering()
.
请注意,filtering()
可以消除某些存储桶的所有值,但不会导致删除空存储桶。在下面的示例中,1、2、3、4、5 的存储桶将为空。
public static void main(String[] args) {
var foos = List.of(-100, 1, 2, 3, 4, 5, 1001, 1002, 1003);
var map = foos.stream()
.collect(Collectors.groupingBy(
UnaryOperator.identity(),
Collectors.filtering(foo -> foo < 0 || foo > 1000,
Collectors.toSet())));
System.out.println(map);
}
地图
{1=[], 2=[], 3=[], -100=[-100], 4=[], 5=[], 1001=[1001], 1002=[1002], 1003=[1003]}
更新
我修改了这个问题,下面提供的解决方案预先计算了值。
在此版本中,流中不需要的条目被过滤掉,这使得 Collector 中的代码更易于阅读。我希望使用整数而不是 foo
对象不是问题。
public static void main(String[] args) {
List<Integer> foos = List.of(-100, 1, 2, 3, 4, 5, 101, 102, 103);
Map<Integer, Integer> fooToValue = getFooToValueMap(foos);
Map<Boolean, Set<Integer>> map = getFoosMap(fooToValue);
System.out.println(map);
}
private static Map<Boolean, Set<Integer>> getFoosMap(Map<Integer, Integer> fooToValue) {
return fooToValue.entrySet().stream()
.filter(entry -> entry.getValue() < 0 || entry.getValue() > 1000)
.collect(Collectors.partitioningBy(
entry -> entry.getValue() > 1000,
Collectors.mapping(Map.Entry::getKey, Collectors.toSet())
));
}
private static Map<Integer, Integer> getFooToValueMap(List<Integer> list) {
return list.stream()
.collect(Collectors.toMap(UnaryOperator.identity(), foo -> complexEvaluation(foo)));
}
private static int complexEvaluation(int foo) {
return (int) Math.signum(foo) * foo * foo;
}
地图
{false=[-100], true=[101, 102, 103]}
只需使用 enum
来定义您的 3 个案例:
enum Categories {
HIGH, LOW, NEGATIVE
}
var map = foos.stream().collect(Collectors.groupingBy(
foo -> {
int bar = complexEvaluation(foo);
if (bar > 1000) return HIGH;
if (bar < 0) return NEGATIVE;
return LOW;
},
Collectors.mapping(foo -> foo.id, Collectors.toSet())
));
如果不需要,请忽略或删除 LOW
。它还具有为您的类别赋予更多意义而不是仅仅命名它们的额外好处 true
/false
,并且如果您将来需要更多类别,可以更轻松地重构。
唯一的缺点是它构建了一个无用的 LOW
集合,但这只是一个问题,如果它与其他集合和 complexEvaluation()
操作相比真的很大。
如果你想避免临时存储,你必须实现自己的收集器:
var map = foos.parallelStream().collect(
() -> Map.of(true, new HashSet<ID>(), false, new HashSet<ID>()),
(o, foo) -> {
int bar = complexEvaluation(foo);
ID id = foo.id;
if (bar > 1000) o.get(true).add(id);
else if (bar < 0) o.get(false).add(id);
},
(a, b) -> { a.get(true).addAll(b.get(true)); a.get(false).addAll(b.get(false)); }
);
此示例与 partitioningBy
具有相同的行为,始终为 true
和 false
创建条目。
ID
是您未包含在问题中的 foo.id
类型的占位符。