Java的Stream.flatMap()的(种类)逆操作是什么?

时间:2018-06-15 13:50:24

标签: java java-stream

Stream.flatMap()操作转换

的流
a, b, c

到包含每个输入元素的零个或多个元素的流中,例如,

a1, a2, c1, c2, c3

是否有相反的操作将一些元素分成一个新元素?

  • 不是.reduce(),因为这只产生一个结果
  • 不是collect(),因为这只填充容器(afaiu)
  • 它不是forEach(),因为它只返回void并使用副作用

它存在吗?我可以用任何方式模拟它吗?

5 个答案:

答案 0 :(得分:1)

你可以黑客。请参阅以下示例:

Stream<List<String>> stream = Stream.of("Cat", "Dog", "Whale", "Mouse")
   .collect(Collectors.collectingAndThen(
       Collectors.partitioningBy(a -> a.length() > 3),
       map -> Stream.of(map.get(true), map.get(false))
    ));

答案 1 :(得分:1)

    IntStream.range(0, 10)
            .mapToObj(n -> IntStream.of(n, n / 2, n / 3))
            .reduce(IntStream.empty(), IntStream::concat)
            .forEach(System.out::println);

如您所见,元素也映射到Streams,然后连接成一个大流。

答案 2 :(得分:1)

看看StreamEx中的collapse

StreamEx.of("a1", "a2", "c1", "c2", "c3").collapse((a, b) -> a.charAt(0) == b.charAt(0))
    .map(e -> e.substring(0, 1)).forEach(System.out::println);

或具有更多功能的my forkgroupBysplitsliding ...

StreamEx.of("a1", "a2", "c1", "c2", "c3").collapse((a, b) -> a.charAt(0) == b.charAt(0))
.map(e -> e.substring(0, 1)).forEach(System.out::println);
// a
// c

StreamEx.of("a1", "a2", "c1", "c2", "c3").splitToList(2).forEach(System.out::println);
// [a1, a2]
// [c1, c2]
// [c3]

StreamEx.of("a1", "a2", "c1", "c2", "c3").groupBy(e -> e.charAt(0))
.forEach(System.out::println);
// a=[a1, a2]
// c=[c1, c2, c3]

答案 3 :(得分:1)

最后,我发现flatMap是它自己的“逆”,可以这么说。我监督flatMap不一定会增加元素的数量。通过为某些元素发出空流,它也可以减少元素的数量。要实现分组操作,由flatMap调用的函数需要最小的内部状态,即最近的元素。它要么返回空流,要么在组的末尾返回简化为组的代表。

这是一个快速实现,其中groupBorder必须返回true,如果传入的两个元素不属于同一组,即它们之间是组边界。 combiner是一种分组函数,例如将(1,a),(1,a),(1,a)合并为(3,a),前提是您的分组元素是元组(int,字符串)。

public class GroupBy<X> implements Function<X, Stream<X>>{

  private final BiPredicate<X, X> groupBorder;
  private final BinaryOperator<X> combiner;
  private X latest = null;

  public GroupBy(BiPredicate <X, X> groupBorder,
                 BinaryOperator<X> combiner) {
    this.groupBorder = groupBorder;
    this.combiner = combiner;
  }

  @Override
  public Stream<X> apply(X elem) {
    // TODO: add test on end marker as additonal parameter for constructor
    if (elem==null) {
      return latest==null ? Stream.empty() : Stream.of(latest);
    }
    if (latest==null) {
      latest = elem;
      return Stream.empty();
    }
    if (groupBorder.test(latest, elem)) {
      Stream<X> result = Stream.of(latest);
      latest = elem;
      return result;
    }
    latest = combiner.apply(latest,  elem);
    return Stream.empty();
  }
}

但是有一个警告:要发送整个流的最后一组,必须将结束标记作为流中的最后一个元素粘贴。上面的代码假定它是null,但是可以添加一个附加的结束标记测试器。

我无法提出不依赖结束标记的解决方案。

此外,我也没有在传入和传出元素之间进行转换。对于唯一操作,这将起作用。对于计数操作,上一步必须将单个元素映射到计数对象。

答案 4 :(得分:0)

这就是我提出的:

interface OptionalBinaryOperator<T> extends BiFunction<T, T, Optional<T>> {
  static <T> OptionalBinaryOperator<T> of(BinaryOperator<T> binaryOperator,
          BiPredicate<T, T> biPredicate) {
    return (t1, t2) -> biPredicate.test(t1, t2)
            ? Optional.of(binaryOperator.apply(t1, t2))
            : Optional.empty();
  }
}

class StreamUtils {
  public static <T> Stream<T> reducePartially(Stream<T> stream,
          OptionalBinaryOperator<T> conditionalAccumulator) {
    Stream.Builder<T> builder = Stream.builder();
    stream.reduce((t1, t2) -> conditionalAccumulator.apply(t1, t2).orElseGet(() -> {
      builder.add(t1);
      return t2;
    })).ifPresent(builder::add);
    return builder.build();
  }
}

不幸的是,我没有时间让它变得懒惰,但可以通过编写一个自定义Spliterator委托给stream.spliterator()来完成,这将遵循上面的逻辑(而不是使用{{1}这是一个终端操作。)

PS。我刚刚意识到你想要stream.reduce()转换,我写了关于<T,U>转换的文章。如果您可以先从<T,T>映射到T,然后使用上面的功能,那就是它(即使它不是最理想的)。

如果它更复杂,那么在提出API之前需要定义减少/合并的条件(例如UPredicate<T>BiPredicate<T,T>,甚至可能BiPredicate<U,T>)。