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&lt;T&gt;,
 * return a Stream&lt;T&gt;. 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&lt;T&gt;,
     * return an Iterable&lt;T&gt;. 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&lt;T&gt;,
 * return a Stream&lt;T&gt;. 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);
}