此任务类似于数组/列表中元素的常见计数频率,但是,相邻元素(子序列)应计为 1。也就是说,在计数频率时,如果前一个元素相同,则 delta 为 0作为当前的。
要使用 Java Stream 解决这个任务,我们需要实现:
-
Collector类累加最终值和索引列表,包括merge方法添加索引和频率。
static class CollectorList {
int sum;
List<Integer> indexes = new ArrayList<>();
public CollectorList(List<Integer> h) {
this.indexes.add(h.get(0));
this.sum += h.get(1);
}
public CollectorList merge(CollectorList next) {
this.sum += next.sum;
this.indexes.addAll(next.indexes);
return this;
}
}
那么获取自定义频率列表的方法可以实现如下:
public static List<Integer> getFrequencyList(int ... arr) {
return new ArrayList<>(
IntStream.range(0, arr.length) // stream of indexes
.mapToObj(i -> Map.entry(
arr[i], // key: current element
Arrays.asList(i, i > 0 && arr[i] == arr[i - 1] ? 0 : 1))) // store index and delta
.collect(Collectors.toMap(
Map.Entry::getKey, // for array elements
e -> new CollectorList(e.getValue()), // init collector of helper
CollectorList::merge // and merge
))
.values() // get Collection<CollectorList>
.stream() // convert CollectorList back to index -> frequency
.flatMap(c -> c.indexes.stream().map(i -> Map.entry(i, c.sum)))
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(i1, i2) -> i1, TreeMap::new // sort by index
))
.values() // frequencies by index
);
}
为简洁起见,上面的代码使用 Java 9 Map.entry,如果需要兼容 Java 8,可以将其替换为构造 new AbstractMap.SimpleEntry()。
测试:
int[][] tests = {
{},
{0},
{1,2,1,2,1,3,1},
{1,1,0,1},
{3,0,1,2,1,1,1,3},
{1,0,1,2,1,3},
{1,0,0,2,1,3,0}
};
for (int[] arr : tests) {
List<Integer> result = getFrequencyList(arr);
System.out.println(Arrays.toString(arr) + " -> " + result);
}
输出
[] -> []
[0] -> [1]
[1, 2, 1, 2, 1, 3, 1] -> [4, 2, 4, 2, 4, 1, 4]
[1, 1, 0, 1] -> [2, 2, 1, 2]
[3, 0, 1, 2, 1, 1, 1, 3] -> [2, 1, 2, 1, 2, 2, 2, 2]
[1, 0, 1, 2, 1, 3] -> [3, 1, 3, 1, 3, 1]
[1, 0, 0, 2, 1, 3, 0] -> [2, 2, 2, 1, 2, 1, 2]