Java 8 流式处理并在执行归约时维护索引
Java 8 Stream and maintaining index while performing reduction
这几天我一直在想怎么用Java8流API进行归约,同时维护一个索引。这是一个例子:
我有以下字符串:
String charSequence = "kjsfjsfajdsfjsaaaaaasssddfddddbbbdddaaa";
作为流操作的结果,我想 return 一个三元组 (I, N, C)
其中:
C
是一个字符
N
是出现的次数 - 应该是最大值
I
是字符串第一次出现的索引(如果有多个则为第一个)
示例:
"ddaaaacccjcccccjjj"
returns (10, 5, c)
"ddaaacccaaa"
第一次出现 "aaa"
是 2
所以结果是:
(2, 3, a)
不要认为流在这里很有用。这是一个使用正则表达式和结果列表的解决方案:
Pattern p = Pattern.compile("(\w)\1+");
Matcher m = p.matcher(charSequence);
List<Triple> list = new ArrayList<>();
while (m.find()) {
int start = m.start();
int end = m.end();
int diff = end - start;
if (list.isEmpty()) {
list.add(new Triple(m.group(0).charAt(0), diff, start));
} else if (list.get(list.size() - 1).getN() == diff) {
list.add(new Triple(m.group(0).charAt(0), diff, start));
} else if (diff > list.get(list.size() - 1).getN()) {
list.clear();
list.add(new Triple(m.group(0).charAt(0), diff, start));
}
}
还有一个Triple
:
static class Triple {
private final Character c;
private final long n;
private final int i;
public Triple(Character c, long n, int i) {
this.c = c;
this.n = n;
this.i = i;
}
// getters
}
我有这样的解决方案,例如:
List<Triple> result = p.matcher(charSequence).results()
.collect(
Collector.of(
ArrayList::new,
(l, mr) -> {
int diff = mr.end() - mr.start();
if (!l.isEmpty() && l.get(l.size() - 1).getN() < diff) {
l.clear();
}
if (l.isEmpty() || l.get(l.size() - 1).getN() == diff) {
l.add(new Triple(mr.group(0).charAt(0), diff, mr.start()));
}
},
(left, right) -> {
throw new UnsupportedOperationException("Not for parallel");
}));
可能是这样的:
我分几步写的。
result1 :第一步 splits repeated character 然后按第一个字符分组。
char c
的示例:
{'c',["ccc","ccccc"]}
Map<Character,List<String>> result1 = Stream.of(charSequence.split("(?<=(.))(?!\1)"))
.collect(Collectors.groupingBy(s->s.charAt(0)));
result2 :在此步骤中,结果是具有先前结果的最大长度的字符串列表。如您所见,我们有 ["ccc","ccccc"]
,所以这里我们只使用 ccccc
序列。
List<String> result2 = result1.entrySet()
.stream()
.map(entry->entry.getValue()
.stream().max(Comparator.comparingInt(String::length)).get())
.collect(Collectors.toList());
结果:最后一步是您的预期结果。
List<Triple> result = result2
.stream()
.map(str1->new Triple(str1.charAt(0),str1.length(),charSequence.indexOf(str1)))
.collect(Collectors.toList());
Stream.of(charSequence.split("(?<=(.))(?!\1)"))
.collect(groupingBy(s -> s.charAt(0),
collectingAndThen(maxBy(comparingInt(String::length)), Optional::get)))
.entrySet().stream()
.map(m1 -> new Triple(m1.getKey(), m1.getValue().length(), charSequence.indexOf(m1.getValue())))
.collect(Collectors.toList());
I am trying to understand why maintaining index is difficult...
Stream API 的目的是通过专注于元素而非索引的管道执行操作。每个元素的索引都需要对 Stream 进行顺序处理,这与并行流的点冲突,并行流必须同步才能与索引一起工作 - 它扼杀了这个想法。
or why it can not be done if that is the case.
然而,仍然有一种方法可以同时迭代两个或多个源(集合、数组...),使用 IntStream::range
来迭代被迭代的索引本身:
IntStream.range(0, 10).map(i -> list.get(i) + array[i])...
... trying figure out how can I using Java 8 Stream API perform a reduction and at the same time maintain an index
... 但是上面的解决方案和任何其他解决方案都不关心前面的 n
元素。处理后的元素应该独立于其他元素。
在这种情况下忘记 Stream API。回到传统和程序for-loop
。单循环即可得到结果。
换位思考,向外看。这是 StreamEx 的替代解决方案之一,您可能不接受您的声明:
String str = "ddaaaacccjcccccjjj";
IntStreamEx.range(0, str.length()).boxed()
.collapse((i, j) -> str.charAt(i) == str.charAt(j), Collectors.toList())
.maxBy(l -> l.size())
.map(l -> Triple.of(l.get(0), l.size(), str.charAt(l.get(0))))
.ifPresent(System.out::println);
// output: [10, 5, c]
并获得全部:
String str = "ddaaacccaaa";
IntStreamEx.range(0, str.length()).boxed()
.collapse((i, j) -> str.charAt(i) == str.charAt(j), Collectors.toList())
.collect(MoreCollectors.maxAll(Comparators.comparingBy(l -> l.size())))
.stream().map(l -> Triple.of(l.get(0), l.size(), str.charAt(l.get(0))))
.forEach(System.out::println);
// output
// [2, 3, a]
// [5, 3, c]
// [8, 3, a]
按字符区分结果:
Collector<List<Integer>, ?, StreamEx<List<Integer>>> collector = Collectors.collectingAndThen(
MoreCollectors.maxAll(Comparators.comparingBy(l -> l.size())), StreamEx::of);
IntStreamEx.range(0, str.length()).boxed()
.collapse((i, j) -> str.charAt(i) == str.charAt(j), Collectors.toList())
.collect(collector)
.distinct(l -> str.charAt(l.get(0)))
.map(l -> Triple.of(l.get(0), l.size(), str.charAt(l.get(0))))
.forEach(System.out::println);
// output
// [2, 3, a]
// [5, 3, c]
更新:
够好了吗?实际上不是,因为它会创建不必要的临时 List
。我认为 intervalMap
.
有更好的解决方案
IntStreamEx.range(0, str.length()).boxed()
.intervalMap((i, j) -> str.charAt(i) == str.charAt(j), Pair::of)
.maxBy(p -> p.right - p.left)
.map(p -> Triple.of(p.left, p.right - p.left + 1, str.charAt(p.left)))
.ifPresent(System.out::println);
这几天我一直在想怎么用Java8流API进行归约,同时维护一个索引。这是一个例子:
我有以下字符串:
String charSequence = "kjsfjsfajdsfjsaaaaaasssddfddddbbbdddaaa";
作为流操作的结果,我想 return 一个三元组 (I, N, C)
其中:
C
是一个字符N
是出现的次数 - 应该是最大值I
是字符串第一次出现的索引(如果有多个则为第一个)
示例:
"ddaaaacccjcccccjjj"
returns(10, 5, c)
"ddaaacccaaa"
第一次出现"aaa"
是2
所以结果是:(2, 3, a)
不要认为流在这里很有用。这是一个使用正则表达式和结果列表的解决方案:
Pattern p = Pattern.compile("(\w)\1+");
Matcher m = p.matcher(charSequence);
List<Triple> list = new ArrayList<>();
while (m.find()) {
int start = m.start();
int end = m.end();
int diff = end - start;
if (list.isEmpty()) {
list.add(new Triple(m.group(0).charAt(0), diff, start));
} else if (list.get(list.size() - 1).getN() == diff) {
list.add(new Triple(m.group(0).charAt(0), diff, start));
} else if (diff > list.get(list.size() - 1).getN()) {
list.clear();
list.add(new Triple(m.group(0).charAt(0), diff, start));
}
}
还有一个Triple
:
static class Triple {
private final Character c;
private final long n;
private final int i;
public Triple(Character c, long n, int i) {
this.c = c;
this.n = n;
this.i = i;
}
// getters
}
我有这样的解决方案,例如:
List<Triple> result = p.matcher(charSequence).results()
.collect(
Collector.of(
ArrayList::new,
(l, mr) -> {
int diff = mr.end() - mr.start();
if (!l.isEmpty() && l.get(l.size() - 1).getN() < diff) {
l.clear();
}
if (l.isEmpty() || l.get(l.size() - 1).getN() == diff) {
l.add(new Triple(mr.group(0).charAt(0), diff, mr.start()));
}
},
(left, right) -> {
throw new UnsupportedOperationException("Not for parallel");
}));
可能是这样的:
我分几步写的。
result1 :第一步 splits repeated character 然后按第一个字符分组。
char c
的示例:
{'c',["ccc","ccccc"]}
Map<Character,List<String>> result1 = Stream.of(charSequence.split("(?<=(.))(?!\1)"))
.collect(Collectors.groupingBy(s->s.charAt(0)));
result2 :在此步骤中,结果是具有先前结果的最大长度的字符串列表。如您所见,我们有 ["ccc","ccccc"]
,所以这里我们只使用 ccccc
序列。
List<String> result2 = result1.entrySet()
.stream()
.map(entry->entry.getValue()
.stream().max(Comparator.comparingInt(String::length)).get())
.collect(Collectors.toList());
结果:最后一步是您的预期结果。
List<Triple> result = result2
.stream()
.map(str1->new Triple(str1.charAt(0),str1.length(),charSequence.indexOf(str1)))
.collect(Collectors.toList());
Stream.of(charSequence.split("(?<=(.))(?!\1)"))
.collect(groupingBy(s -> s.charAt(0),
collectingAndThen(maxBy(comparingInt(String::length)), Optional::get)))
.entrySet().stream()
.map(m1 -> new Triple(m1.getKey(), m1.getValue().length(), charSequence.indexOf(m1.getValue())))
.collect(Collectors.toList());
I am trying to understand why maintaining index is difficult...
Stream API 的目的是通过专注于元素而非索引的管道执行操作。每个元素的索引都需要对 Stream 进行顺序处理,这与并行流的点冲突,并行流必须同步才能与索引一起工作 - 它扼杀了这个想法。
or why it can not be done if that is the case.
然而,仍然有一种方法可以同时迭代两个或多个源(集合、数组...),使用 IntStream::range
来迭代被迭代的索引本身:
IntStream.range(0, 10).map(i -> list.get(i) + array[i])...
... trying figure out how can I using Java 8 Stream API perform a reduction and at the same time maintain an index
... 但是上面的解决方案和任何其他解决方案都不关心前面的 n
元素。处理后的元素应该独立于其他元素。
在这种情况下忘记 Stream API。回到传统和程序for-loop
。单循环即可得到结果。
换位思考,向外看。这是 StreamEx 的替代解决方案之一,您可能不接受您的声明:
String str = "ddaaaacccjcccccjjj";
IntStreamEx.range(0, str.length()).boxed()
.collapse((i, j) -> str.charAt(i) == str.charAt(j), Collectors.toList())
.maxBy(l -> l.size())
.map(l -> Triple.of(l.get(0), l.size(), str.charAt(l.get(0))))
.ifPresent(System.out::println);
// output: [10, 5, c]
并获得全部:
String str = "ddaaacccaaa";
IntStreamEx.range(0, str.length()).boxed()
.collapse((i, j) -> str.charAt(i) == str.charAt(j), Collectors.toList())
.collect(MoreCollectors.maxAll(Comparators.comparingBy(l -> l.size())))
.stream().map(l -> Triple.of(l.get(0), l.size(), str.charAt(l.get(0))))
.forEach(System.out::println);
// output
// [2, 3, a]
// [5, 3, c]
// [8, 3, a]
按字符区分结果:
Collector<List<Integer>, ?, StreamEx<List<Integer>>> collector = Collectors.collectingAndThen(
MoreCollectors.maxAll(Comparators.comparingBy(l -> l.size())), StreamEx::of);
IntStreamEx.range(0, str.length()).boxed()
.collapse((i, j) -> str.charAt(i) == str.charAt(j), Collectors.toList())
.collect(collector)
.distinct(l -> str.charAt(l.get(0)))
.map(l -> Triple.of(l.get(0), l.size(), str.charAt(l.get(0))))
.forEach(System.out::println);
// output
// [2, 3, a]
// [5, 3, c]
更新:
够好了吗?实际上不是,因为它会创建不必要的临时 List
。我认为 intervalMap
.
IntStreamEx.range(0, str.length()).boxed()
.intervalMap((i, j) -> str.charAt(i) == str.charAt(j), Pair::of)
.maxBy(p -> p.right - p.left)
.map(p -> Triple.of(p.left, p.right - p.left + 1, str.charAt(p.left)))
.ifPresent(System.out::println);