Java 8、Stream of Integer,按Integers对流的索引进行分组?
Java 8, Stream of Integer, Grouping indexes of a stream by the Integers?
我得到了一个整数流,我想根据每个元素的值对元素的索引进行分组。
例如,{1, 1, 1, 2, 3, 3, 4}
被分组为整数到索引映射列表:
1 -> 0, 1, 2
2 -> 3
3 -> 4, 5
4 -> 6
我尝试过使用流,但还有一个额外的 class:
@Test
public void testGrouping() throws Exception {
// actually it is being read from a disk file
Stream<Integer> nums = Stream.of(1, 1, 1, 2, 3, 3, 4);
// list to map by index
int[] ind = {0}; // capture array, effectively final
class Pair {
int left;
int right;
public Pair(int left, int right) {
this.left = left;
this.right = right;
}
}
Map<Integer, List<Integer>> map = nums.map(e -> new Pair(ind[0]++, e))
.collect(Collectors.groupingBy(e -> e.right))
.entrySet().parallelStream()
.collect(Collectors.toConcurrentMap(
Map.Entry::getKey,
e -> e.getValue().parallelStream().map(ee -> ee.left).collect(Collectors.toList())
));
}
我必须读取流,因为整数流是从我的应用程序中的磁盘文件中读取的。
我觉得我按照上面的方式做这件事是非常次优的。有没有更好或更优雅的方法来做到这一点?
谢谢你的帮助。
- 可以使用
IntStream#range(int startInclusive, int endExclusive)
方法获取每个元素的索引
- 然后使用
IntStream.boxed()
方法将IntStream
转换为带盒装Integer
s 的Stream
- 通过将每个索引映射到数组中的相应元素进行分组
i -> array[i]
并将重复元素收集到一个列表中。
例如:
int[] array = {1, 1, 1, 2, 3, 3, 4};
Map<Integer, List<Integer>> result =
IntStream.range(0, array.length)
.boxed()
.collect(Collectors.groupingBy(i -> array[i], Collectors.toList()));
更新:
如果你没有数组(因此元素计数),但是 Stream<Integer>
,你可以 收集 初始 Stream
的元素到 List<Integer>
。这样你就会知道 Stream
的大小然后你可以这样做:
Stream<Integer> = .... // The input stream goes here
//Collecting the input stream to a list, so that we get it's size.
List<Integer> list = stream.collect(Collectors.toList());
//Grouping process
Map<Integer, List<Integer>> result =
IntStream.range(0, list.size())
.boxed()
.collect(Collectors.groupingBy(i -> list.get(i), Collectors.toList()));
用一点辅助收集方法:
class MapAndIndex {
Map<Integer,List<Integer>> map=new HashMap<>();
int index;
void add(int value) {
map.computeIfAbsent(value, x->new ArrayList<>()).add(index++);
}
void merge(MapAndIndex other) {
other.map.forEach((value,list) -> {
List<Integer> l=map.computeIfAbsent(value, x->new ArrayList<>());
for(int i: list) l.add(i+index);
} );
index+=other.index;
}
}
整个操作变成:
Map<Integer,List<Integer>> map = IntStream.of(1, 1, 1, 2, 3, 3, 4)
.parallel()
.collect(MapAndIndex::new, MapAndIndex::add, MapAndIndex::merge).map;
当您需要跟踪事先未知的索引时,您需要可变状态,因此需要调用 “mutable reduction”.
的操作
请注意,此处不需要 ConcurrentMap
。 Stream
实现将已经处理并发。它将为每个涉及的线程创建一个 MapAndIndex
容器,并在两个相关线程完成其工作后对两个容器调用 merge
操作。如果 Stream
有一个顺序,这也将以保留顺序的方式完成,就像在这个例子中一样(否则你记录索引的任务没有意义......)。
你能做的是
Map<Integer, List<Integer>> map = nums.map(e -> new Pair(ind[0]++, e))
.collect(groupingBy(p -> p.right, HashMap::new,
mapping(p -> p.left, toList())));
这允许您在将元素添加到列表之前对其应用映射。
为什么不呢:
Stream<Integer> nums = Stream.of(1, 1, 1, 2, 3, 3, 4);
OfInt indexes = IntStream.iterate(0, x -> x + 1).iterator();
Map<Integer, List<Integer>> result = new HashMap<>();
nums.iterator().forEachRemaining(i -> result.merge(i,
new ArrayList<>(Arrays.asList(indexes.next())),
(l1, l2) -> {l1.addAll(l2); return l1;})
);
结果:
{1=[0, 1, 2], 2=[3], 3=[4, 5], 4=[6]}
我得到了一个整数流,我想根据每个元素的值对元素的索引进行分组。
例如,{1, 1, 1, 2, 3, 3, 4}
被分组为整数到索引映射列表:
1 -> 0, 1, 2
2 -> 3
3 -> 4, 5
4 -> 6
我尝试过使用流,但还有一个额外的 class:
@Test
public void testGrouping() throws Exception {
// actually it is being read from a disk file
Stream<Integer> nums = Stream.of(1, 1, 1, 2, 3, 3, 4);
// list to map by index
int[] ind = {0}; // capture array, effectively final
class Pair {
int left;
int right;
public Pair(int left, int right) {
this.left = left;
this.right = right;
}
}
Map<Integer, List<Integer>> map = nums.map(e -> new Pair(ind[0]++, e))
.collect(Collectors.groupingBy(e -> e.right))
.entrySet().parallelStream()
.collect(Collectors.toConcurrentMap(
Map.Entry::getKey,
e -> e.getValue().parallelStream().map(ee -> ee.left).collect(Collectors.toList())
));
}
我必须读取流,因为整数流是从我的应用程序中的磁盘文件中读取的。
我觉得我按照上面的方式做这件事是非常次优的。有没有更好或更优雅的方法来做到这一点?
谢谢你的帮助。
- 可以使用
IntStream#range(int startInclusive, int endExclusive)
方法获取每个元素的索引 - 然后使用
IntStream.boxed()
方法将IntStream
转换为带盒装Integer
s 的 - 通过将每个索引映射到数组中的相应元素进行分组
i -> array[i]
并将重复元素收集到一个列表中。
Stream
例如:
int[] array = {1, 1, 1, 2, 3, 3, 4};
Map<Integer, List<Integer>> result =
IntStream.range(0, array.length)
.boxed()
.collect(Collectors.groupingBy(i -> array[i], Collectors.toList()));
更新:
如果你没有数组(因此元素计数),但是 Stream<Integer>
,你可以 收集 初始 Stream
的元素到 List<Integer>
。这样你就会知道 Stream
的大小然后你可以这样做:
Stream<Integer> = .... // The input stream goes here
//Collecting the input stream to a list, so that we get it's size.
List<Integer> list = stream.collect(Collectors.toList());
//Grouping process
Map<Integer, List<Integer>> result =
IntStream.range(0, list.size())
.boxed()
.collect(Collectors.groupingBy(i -> list.get(i), Collectors.toList()));
用一点辅助收集方法:
class MapAndIndex {
Map<Integer,List<Integer>> map=new HashMap<>();
int index;
void add(int value) {
map.computeIfAbsent(value, x->new ArrayList<>()).add(index++);
}
void merge(MapAndIndex other) {
other.map.forEach((value,list) -> {
List<Integer> l=map.computeIfAbsent(value, x->new ArrayList<>());
for(int i: list) l.add(i+index);
} );
index+=other.index;
}
}
整个操作变成:
Map<Integer,List<Integer>> map = IntStream.of(1, 1, 1, 2, 3, 3, 4)
.parallel()
.collect(MapAndIndex::new, MapAndIndex::add, MapAndIndex::merge).map;
当您需要跟踪事先未知的索引时,您需要可变状态,因此需要调用 “mutable reduction”.
的操作请注意,此处不需要 ConcurrentMap
。 Stream
实现将已经处理并发。它将为每个涉及的线程创建一个 MapAndIndex
容器,并在两个相关线程完成其工作后对两个容器调用 merge
操作。如果 Stream
有一个顺序,这也将以保留顺序的方式完成,就像在这个例子中一样(否则你记录索引的任务没有意义......)。
你能做的是
Map<Integer, List<Integer>> map = nums.map(e -> new Pair(ind[0]++, e))
.collect(groupingBy(p -> p.right, HashMap::new,
mapping(p -> p.left, toList())));
这允许您在将元素添加到列表之前对其应用映射。
为什么不呢:
Stream<Integer> nums = Stream.of(1, 1, 1, 2, 3, 3, 4);
OfInt indexes = IntStream.iterate(0, x -> x + 1).iterator();
Map<Integer, List<Integer>> result = new HashMap<>();
nums.iterator().forEachRemaining(i -> result.merge(i,
new ArrayList<>(Arrays.asList(indexes.next())),
(l1, l2) -> {l1.addAll(l2); return l1;})
);
结果:
{1=[0, 1, 2], 2=[3], 3=[4, 5], 4=[6]}