在Java中,如何有效地完成我和优雅的流树节点的后代? [英] In Java, how do I efficiently and elegantly stream a tree node's descendants?
问题描述
假设我们有对象,由唯一的字符串
S来标识的集合,连同类树
的定义了他们的层次结构。这个类是使用地图
从节点(再由他们的ID psented $ P $),以集合
是他们的执行各个孩子的标识。
Assume we have a collection of objects that are identified by unique String
s, along with a class Tree
that defines a hierarchy on them. That class is implemented using a Map
from nodes (represented by their IDs) to Collection
s of their respective children's IDs.
class Tree {
private Map<String, Collection<String>> edges;
// ...
public Stream<String> descendants(String node) {
// To be defined.
}
}
我想使流节点的后代的一个简单的解决办法是这样的:
I would like to enable streaming a node's descendants. A simple solution is this:
private Stream<String> children(String node) {
return edges.getOrDefault(node, Collections.emptyList()).stream();
}
public Stream<String> descendants(String node) {
return Stream.concat(
Stream.of(node),
children(node).flatMap(this::descendants)
);
}
在继续,我想提出这个解决方案下断言。 (我是正确了解这些?)
Before continuing, I would like to make the following assertions about this solution. (Am I correct about these?)
-
走在从
的后裔
流
返回占用的资源(时间和内存) - 相对于尺寸树 - 复杂的手工编码的递归会的顺序相同。特别是,中间对象重新presenting迭代状态(流
S,Spliterator
S,... )形成堆叠,因此所需的存储在任何给定时间是在复杂度作为树的深度的顺序相同。
Walking the
Stream
returned fromdescendants
consumes resources (time and memory) – relative to the size of the tree – in the same order of complexity as hand-coding the recursion would. In particular, the intermediate objects representing the iteration state (Stream
s,Spliterator
s, ...) form a stack and therefore the memory requirement at any given time is in the same order of complexity as the tree's depth.
据我了解这,只要我在流
从的后裔返回
,根级呼叫 flatMap
将导致所有载流
秒 - 一个用于每个(递归)调用的后裔
- 要立即实现。因此,得到的流
是只有懒递归的第一级,但从未被超越。 (根据 Tagir Valeevs答案编辑。)的
As I understand this, as soon as I perform a terminating operation on the Stream
returned from descendants
, the root-level call to flatMap
will cause all contained Stream
s – one for each (recursive) call to descendants
– to be realized immediately. Thus, the resulting Stream
is only lazy on the first level of recursion, but not beyond. (Edited according to Tagir Valeevs answer.)
如果我理解正确的这几点,我的问题是这样的:如何定义的后裔
使生成的流
是懒惰?
If I understood these points correctly, my question is this: How can I define descendants
so that the resulting Stream
is lazy?
我想的解决方案是优雅地,在我preFER一个解决方案,这让迭代状态隐含的意义。 (要澄清我的意思是说:我知道,我可以写一个 Spliterator
行走的树,同时保持一个明确的堆栈 Spliterator
每个级别s。我想避免的。)
I would like the solution to be as elegant as possible, in the sense that I prefer a solution which leaves the iteration state implicit. (To clarify what I mean by that: I know that I could write a Spliterator
that walks the tree while maintaining an explicit stack of Spliterator
s on each level. I would like to avoid that.)
(有可能的方式在Java中,制订这是一个生产者 - 消费者的工作流程,就像人们可以像朱莉娅语言使用和出路在哪里?)
(Is there possibly a way in Java to formulate this as a producer-consumer workflow, like one could use in languages like Julia and Go?)
推荐答案
对我来说,你的解决方案已经优雅地和它不是你的错有限的懒惰。最简单的办法是等待,直到它被固定在JRE的开发。
To me, your solution is already as elegant as possible and the limited laziness of it not your fault. The simplest solution is to wait until it gets fixed by the JRE developers.
不过,如果今日实施本次有限懒惰确实是一个问题,它也许时间来解决这个在一般的方式。那么,它的是的有关实施 Spliterator
,而不是具体到你的任务。相反,它是一个重新实施 flatmap
运行服务于所有情况下的原执行事宜有限的懒惰:
However, if this limited laziness of today’s implementation really is a concern, it’s perhaps time to solve this in a general way. Well, it is about implementing a Spliterator
, but not specific to your task. Instead, it’s a re-implementation of the flatmap
operation serving all cases where the limited laziness of the original implementation matters:
public class FlatMappingSpliterator<E,S> extends Spliterators.AbstractSpliterator<E>
implements Consumer<S> {
static final boolean USE_ORIGINAL_IMPL
= Boolean.getBoolean("stream.flatmap.usestandard");
public static <T,R> Stream<R> flatMap(
Stream<T> in, Function<? super T,? extends Stream<? extends R>> mapper) {
if(USE_ORIGINAL_IMPL)
return in.flatMap(mapper);
Objects.requireNonNull(in);
Objects.requireNonNull(mapper);
return StreamSupport.stream(
new FlatMappingSpliterator<>(sp(in), mapper), in.isParallel()
).onClose(in::close);
}
final Spliterator<S> src;
final Function<? super S, ? extends Stream<? extends E>> f;
Stream<? extends E> currStream;
Spliterator<E> curr;
private FlatMappingSpliterator(
Spliterator<S> src, Function<? super S, ? extends Stream<? extends E>> f) {
// actually, the mapping function can change the size to anything,
// but it seems, with the current stream implementation, we are
// better off with an estimate being wrong by magnitudes than with
// reporting unknown size
super(src.estimateSize(), src.characteristics()&ORDERED);
this.src = src;
this.f = f;
}
private void closeCurr() {
try { currStream.close(); } finally { currStream=null; curr=null; }
}
public void accept(S s) {
curr=sp(currStream=f.apply(s));
}
@Override
public boolean tryAdvance(Consumer<? super E> action) {
do {
if(curr!=null) {
if(curr.tryAdvance(action))
return true;
closeCurr();
}
} while(src.tryAdvance(this));
return false;
}
@Override
public void forEachRemaining(Consumer<? super E> action) {
if(curr!=null) {
curr.forEachRemaining(action);
closeCurr();
}
src.forEachRemaining(s->{
try(Stream<? extends E> str=f.apply(s)) {
if(str!=null) str.spliterator().forEachRemaining(action);
}
});
}
@SuppressWarnings("unchecked")
private static <X> Spliterator<X> sp(Stream<? extends X> str) {
return str!=null? ((Stream<X>)str).spliterator(): null;
}
@Override
public Spliterator<E> trySplit() {
Spliterator<S> split = src.trySplit();
if(split==null) {
Spliterator<E> prefix = curr;
while(prefix==null && src.tryAdvance(s->curr=sp(f.apply(s))))
prefix=curr;
curr=null;
return prefix;
}
FlatMappingSpliterator<E,S> prefix=new FlatMappingSpliterator<>(split, f);
if(curr!=null) {
prefix.curr=curr;
curr=null;
}
return prefix;
}
}
您只需要使用它,是一个进口静态添加
的 flatMap
方法到您的code和变化前$ P $形式pssions stream.flatmap(功能)
到 flatmap(流功能)
。
All you need for using it, is to add a import static
of the flatMap
method to your code and change expressions of the form stream.flatmap(function)
to flatmap(stream, function)
.
即。在code
public Stream<String> descendants(String node) {
return Stream.concat(
Stream.of(node),
flatMap(children(node), this::descendants)
);
}
那么,你有充分的偷懒行为。我甚至有无限流测试它...
then you have full lazy behavior. I tested it even with infinite streams…
请注意,我添加了一个切换,使转回到原来的执行,如指定当 -Dstream.flatmap.usestandard = TRUE
在命令行上。
Note that I added a toggle to allow turning back to the original implementation, e.g. when specifying -Dstream.flatmap.usestandard=true
on the command line.
这篇关于在Java中,如何有效地完成我和优雅的流树节点的后代?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!