Java 使用可选选项查找层次结构的根
我有一个类型的树层次结构,它知道他们的孩子,但不知道他们的父母。现在,我正在创建一个外部注册表,从外部提供相反的路径:Java 使用可选选项查找层次结构的根,java,optional,Java,Optional,我有一个类型的树层次结构,它知道他们的孩子,但不知道他们的父母。现在,我正在创建一个外部注册表,从外部提供相反的路径: public interface Registry<X>{ Optional<X> parent(X node); } 公共接口注册表{ 可选父节点(X节点); } 现在我想在该接口中实现一个方法,该方法从任何给定节点(根节点可以是传入的节点或任何祖先节点)获取该层次结构的根注释 我走了这么远: default X root(X node)
public interface Registry<X>{
Optional<X> parent(X node);
}
公共接口注册表{
可选父节点(X节点);
}
现在我想在该接口中实现一个方法,该方法从任何给定节点(根节点可以是传入的节点或任何祖先节点)获取该层次结构的根注释
我走了这么远:
default X root(X node) {
X current = node;
for (Optional<X> opt = Optional.of(current);
opt.isPresent();
opt = opt.flatMap(this::parent)) {
if (opt.isPresent()) {
current = opt.get();
}
}
return current;
}
默认X根(X节点){
X电流=节点;
for(可选opt=可选of(当前);
opt.isPresent();
opt=opt.flatMap(this::parent)){
if(opt.isPresent()){
current=opt.get();
}
}
回流;
}
虽然这样做有效,但感觉有点笨拙
Optional.isPresent()
被调用两次,变量current
被重新赋值。有什么方法可以让它更优雅、更实用吗?下面是Guava和JDK流版本,但绝对的赢家是。我喜欢
import javaslang.collection.Stream;
import java.util.Optional;
// ...
default X root(X node) {
return Stream.iterate(Optional.of(node),
t -> t.flatMap(this::parent)
).takeWhile(Optional::isPresent)
.last()
.orElseThrow(NoSuchElementException::new);
}
Stream.iterate()
方法正是我在其他两个版本中缺少的
:
public final class Streams {
private Streams(){}
/**
* Given an instance of type T and a function from T to Optional<T>,
* return a Stream<T>. This Stream will keep returning values by
* repeatedly applying the supplied function until the returned Optional
* is not present.
*/
public static <X> Stream<X> stream(
final X start, final Function<X, Optional<X>> increment) {
return StreamSupport.stream(
new Spliterator<X>() {
Optional<X> next = Optional.ofNullable(start);
@Override
public boolean tryAdvance(final Consumer<? super X> action) {
final boolean present = next.isPresent();
if (present) action.accept(next.get());
next = next.flatMap(increment);
return present;
}
@Override
public Spliterator<X> trySplit() { return null; }
@Override
public long estimateSize() { return Long.MAX_VALUE; }
@Override
public int characteristics() { return Spliterator.ORDERED; }
}, false
);
}
首先,我们将创建一个名为Optionals
的助手类:
// to avoid misunderstandings:
import java.util.Optional;
import java.util.function.Function;
import com.google.common.collect.AbstractIterator;
public final class Optionals {
private Optionals(){}
/**
* Given an instance of type T and a function from T to Optional<T>,
* return an Iterable<T>. This Iterable will keep returning values
* by repeatedly applying the supplied function until the returned Optional
* is not present.
*/
public static <T> Iterable<T> stream(
final T start, final Function<T, Optional<T>> increment) {
return () -> new AbstractIterator<T>() {
Optional<T> current = Optional.of(start);
@Override
protected T computeNext() {
if (!current.isPresent()) return endOfData();
final T data = current.get();
current = current.flatMap(increment);
return data;
}
};
}
}
很明显,如果没有番石榴,这是可能实现的,但这将是一个混乱得多
Java8流版本:
public final class Streams {
private Streams(){}
/**
* Given an instance of type T and a function from T to Optional<T>,
* return a Stream<T>. This Stream will keep returning values by
* repeatedly applying the supplied function until the returned Optional
* is not present.
*/
public static <X> Stream<X> stream(
final X start, final Function<X, Optional<X>> increment) {
return StreamSupport.stream(
new Spliterator<X>() {
Optional<X> next = Optional.ofNullable(start);
@Override
public boolean tryAdvance(final Consumer<? super X> action) {
final boolean present = next.isPresent();
if (present) action.accept(next.get());
next = next.flatMap(increment);
return present;
}
@Override
public Spliterator<X> trySplit() { return null; }
@Override
public long estimateSize() { return Long.MAX_VALUE; }
@Override
public int characteristics() { return Spliterator.ORDERED; }
}, false
);
}
我在想
default X root(X node) {
X root = node;
for (Optional<X> parentOpt = parent(root); parentOpt.isPresent(); root = parentOpt.get())
;
return root;
}
默认X根(X节点){
X根=节点;
for(可选parentOpt=parent(root);parentOpt.isPresent();root=parentOpt.get())
;
返回根;
}
我不喜欢处理null
参数。因此我们遵从parent
的实现,您可能会将其记录为在null
参数上返回一个空的Optional
如果参数为null
,我们还将返回null
如果参数不是null
,我们将其值保存在root
中并开始循环。我们得到了它的潜在父母。如果它存在,我们将更新根目录
,然后重试。否则,我们将中断,并返回根目录中最后保存的值,因为这是我们所能得到的
我认为这种笨拙的感觉来自于节点周围的初始可选。我认为你不需要这些。索蒂里奥斯的回答很简洁,而且非常好
您在尝试解决方案时实现了,而我的解决方案是将其实现为/。对于您的特定问题来说,它可能过于复杂,但它可能在其他地方对您的库有用,或者对其他浏览stackoverflow的人有用
public class ParentSpliterator<T> implements Spliterator<T> {
private final Registry<T> registry;
private Optional<T> currentNodeOpt;
public ParentSpliterator(Registry<T> registry, T startNode) {
this.registry = registry;
this.currentNodeOpt = Optional.of(startNode);
}
@Override
public boolean tryAdvance(Consumer<? super T> action) {
if (!currentNodeOpt.isPresent()) {
return false; // stream is empty
} else {
T currentNode = currentNodeOpt.get();
action.accept(currentNode);
currentNodeOpt = registry.parent(currentNode);
return true;
}
// // Alternative implementation (more Stream-ish):
// return currentNodeOpt.map(node -> {
// action.accept(node);
// currentNodeOpt = registry.parent(node);
// return node;
// }).isPresent();
}
@Override
public Spliterator<T> trySplit() {
return null; // Cannot be split.
}
@Override
public long estimateSize() {
return Long.MAX_VALUE; // No quick way to estimate size.
}
@Override
public int characteristics() {
return Spliterator.ORDERED; // maybe others?
}
public static void main(String[] args) {
Registry.CountDownRegistry cdr = new Registry.CountDownRegistry();
ParentSpliterator<Integer> parentSpliterator = new ParentSpliterator<>(cdr, 3);
Stream<Integer> stream = StreamSupport.stream(parentSpliterator, false);
//stream.forEach(System.out::println);
// Using a reduce to pick the last element of the Stream:
Integer root = stream.reduce((node, nextNode) -> nextNode).get();
System.out.println(root);
}
}
公共类ParentSpliterator实现Spliterator{
私人最终登记处;
私有可选currentNodeOpt;
公共ParentSpliterator(注册表,T startNode){
this.registry=注册表;
this.currentNodeOpt=可选的.of(startNode);
}
@凌驾
public boolean tryAdvance(Consumer我认为您已经非常接近您尝试的循环。它可以简化一点,因为循环体仅在循环条件为true时执行,因此您不必在循环中重新测试opt.isPresent
。如果重用节点
参数,您可以保存一个变量。(我知道,这是一种风格。)增量部分中的flatMap
调用不会给您带来太多好处,因为您知道opt
此时存在;您最好在分配node
时,根据opt.get
的结果调用parent
。这将提供:
default X root(X node) {
for (Optional<X> opt = Optional.of(node); opt.isPresent(); opt = parent(node = opt.get()))
;
return node;
}
Java 9将有一个新的功能,可以让您非常方便地创建从叶到根的节点流。这三个参数与for循环的三个语句类似,只是它们不会产生副作用。您可以将原始for循环转换为流,如下所示:
default Stream<X> stream(X node) {
return Stream.iterate(Optional.of(node),
Optional::isPresent,
op -> op.flatMap(this::parent))
.map(Optional::get);
}
关于这项技术,有几点值得注意
- 它违反了我一直支持的
Optional
的风格规则之一,特别是这条规则:“#4:创建Optional
通常不是一个好主意,因为它的特定目的是链接方法以获取值。”()
- 它是递归的,这意味着如果层次结构太深,它可能会破坏堆栈
- 如果你使用这种技巧,你的同事会避开你
我不建议编写这样的代码,但我想在其他人之前将其发布出来。:-是的,肯定比我的原始版本更干净,谢谢。null永远不会出现在这里,所以这很好。看看我的更新版本,我也切换到直接实现拆分器:-)。我不喜欢你将流功能和父级绑定在一起的方法,这是我试图避免的紧密耦合。但我非常喜欢你的“更多流”代码。你为什么要注释掉它?这很尴尬:一个映射操作会自动返回(什么都不做),但这是产生双重副作用所必需的。
default X root(X node) {
for (Optional<X> opt = Optional.of(node); opt.isPresent(); opt = parent(node = opt.get()))
;
return node;
}
default X root(X node) {
for (Optional<X> opt; (opt = parent(node)).isPresent(); node = opt.get())
;
return node;
}
default Stream<X> stream(X node) {
return Stream.iterate(Optional.of(node),
Optional::isPresent,
op -> op.flatMap(this::parent))
.map(Optional::get);
}
default X root(X node) {
return Optional.of(node).flatMap(this::parent).map(this::root).orElse(node);
}