Java 8 流式处理并在执行归约时维护索引

Java 8 Stream and maintaining index while performing reduction

这几天我一直在想怎么用Java8流API进行归约,同时维护一个索引。这是一个例子:

我有以下字符串:

String charSequence = "kjsfjsfajdsfjsaaaaaasssddfddddbbbdddaaa";

作为流操作的结果,我想 return 一个三元组 (I, N, C)

其中:

示例:

不要认为流在这里很有用。这是一个使用正则表达式和结果列表的解决方案:

    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);