如何使用Java 8流方法(例如,对于数组T[] values
,创建一个Map<T,Integer>
,其中Map.get(values[i]) == i
计算结果为true
(习惯性地枚举将每个T
实例映射到唯一整数的Stream<T>
?
目前,我正在定义一个匿名类,该类递增一个int
字段以用于Collectors.toMap(..)
方法:
private static <T> Map<T, Integer> createIdMap(final Stream<T> values) {
return values.collect(Collectors.toMap(Function.identity(), new Function<T, Integer>() {
private int nextId = 0;
@Override
public Integer apply(final T t) {
return nextId++;
}
}));
}
但是,使用Java 8流API没有更简洁/优雅的方式来做到这一点吗?如果可以安全地并行化,则加分。
如果存在重复的元素,您的方法将失败。
除此之外,您的任务需要可变状态,因此可以通过可变缩减来解决。当我们填充地图时,我们可以简单地使用地图的大小来获取未使用的 id。
更棘手的部分是合并操作。以下操作只是重复正确映射的分配,这将处理潜在的重复项。
private static <T> Map<T, Integer> createIdMap(Stream<T> values) {
return values.collect(HashMap::new, (m,t) -> m.putIfAbsent(t,m.size()),
(m1,m2) -> {
if(m1.isEmpty()) m1.putAll(m2);
else m2.keySet().forEach(t -> m1.putIfAbsent(t, m1.size()));
});
}
如果我们依赖唯一元素,或者插入一个明确的distinct()
,我们可以使用
private static <T> Map<T, Integer> createIdMap(Stream<T> values) {
return values.distinct().collect(HashMap::new, (m,t) -> m.put(t,m.size()),
(m1,m2) -> { int leftSize=m1.size();
if(leftSize==0) m1.putAll(m2);
else m2.forEach((t,id) -> m1.put(t, leftSize+id));
});
}
我会这样做:
private static <T> Map<T, Integer> createIdMap2(final Stream<T> values) {
List<T> list = values.collect(Collectors.toList());
return IntStream.range(0, list.size()).boxed()
.collect(Collectors.toMap(list::get, Function.identity()));
}
为了或并行,它可以更改为
return IntStream.range(0, list.size()).parallel().boxed().
(...)
输入流转换为 List 首先在 Andremoniy 提供的解决方案中。我宁愿以不同的方式执行此操作,因为我们不知道"toList(("和"list.get(i("的成本,并且没有必要创建一个额外的列表,它可以很小或更大
private static <T> Map<T, Integer> createIdMap2(final Stream<T> values) {
final MutableInt idx = MutableInt.of(0); // Or: final AtomicInteger idx = new AtomicInteger(0);
return values.collect(Collectors.toMap(Function.identity(), e -> idx.getAndIncrement()));
}
不管这个问题如何,我认为在方法中将流作为参数传递是一个糟糕的设计。