我有一个类型的树层次结构,可以识别他们的孩子,但不知道他们的父母。现在我正在创建一个外部注册表,从外部提供相反的路径:
public interface Registry<X>{
Optional<X> parent(X node);
}
现在我想在该接口中实现一个方法,该方法从任何给定节点获取此层次结构的根音(根节点可以是传入的节点或任何祖先)。
我到目前为止:
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;
}
虽然这有效,但感觉有点笨拙。 Optional.isPresent()
被调用两次,变量current
被重新分配。有什么方法可以让它更优雅和实用吗?
答案 0 :(得分:2)
我在考虑
default X root(X node) {
X root = node;
for (Optional<X> parentOpt = parent(root); parentOpt.isPresent(); root = parentOpt.get())
;
return root;
}
我不喜欢处理null
个参数。因此,我们遵循parent
的实现,您可能会将其记录为在Optional
参数上返回空null
。
如果参数为null
,我们也会返回null
。
如果参数不是null
,我们将其值保存在root
中并开始循环。我们得到了潜在的父母。如果它存在,我们更新root
,然后重试。否则,我们会中断,并返回root
中最后保存的值,因为这是我们得到的。
我认为笨拙的感觉来自于Optional
左右node
。我认为你不需要那个。
答案 1 :(得分:0)
下面是Guava和JDK流版本,但绝对的赢家是JavaSlang。我喜欢它!
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()
方法正是我在其他两个版本中所缺少的。
<强> Guava 强>
首先,我们将创建一个名为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;
}
};
}
}
这当然是更多的样板,但是它可以重复使用,现在我们可以用简洁的方式编写接口方法:
default X root(final X node) {
return Iterables.getLast(Optionals.stream(node, this::parent));
}
显然,没有番石榴就可以实现这一点,但是它会更加混乱。
Java 8 Stream版本:
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(final X node) {
return Streams.stream(node, this::parent)
.reduce((left, right) -> right)
.orElseThrow(NoSuchElementException::new);
}
答案 2 :(得分:0)
@Sotirios的答案简洁而且非常好。
您在尝试使用解决方案时实施了Iterator,我的解决方案是将其作为Spliterator / Stream实施。对于您的特定问题,它可能过于复杂,但它可能对您的图书馆的其他地方有用,或者对于浏览堆栈溢出的其他人可能有用。
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);
}
}
您需要此实用程序类才能使示例正常工作:
public interface Registry<X> {
Optional<X> parent(X node);
public static class CountDownRegistry implements Registry<Integer> {
@Override
public Optional<Integer> parent(Integer node) {
if (node > 0) {
return Optional.of(node - 1);
} else {
return Optional.empty();
}
}
}
}
答案 3 :(得分:0)
我认为你已经非常接近你尝试过的循环了。它可以简化一点,因为只有在循环条件为真时才执行循环体,因此您不必在循环内重新测试opt.isPresent
。如果重复使用node
参数,则可以保存变量。 (我知道,它是一种风格的东西。)增量部分中的flatMap
调用并不会给你带来太大的影响,因为你知道opt
存在于此点;您也可以在parent
结果opt.get
时调用node
,同时指定default X root(X node) {
for (Optional<X> opt = Optional.of(node); opt.isPresent(); opt = parent(node = opt.get()))
;
return node;
}
。node
这给出了:
parent
你几乎不得不假设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);
}
Java 9将有一个新的three-arg Stream.iterate
factory,可以让您非常方便地创建从叶到根的节点流。这三个参数就像for循环的三个语句一样,除了它们不会产生副作用。您可以按如下方式将原始for循环音译为流:
Optional
最后,有这样的:
Optional
关于这项技术有几点需要注意。
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuV7OdmPSutrlOE9lF3YdW4ymGn+qselCOycMk95Tobw1PcqCeAWkrnxUECpAdnHGrUKmFbEDHs3wnwzLTbfa3GvE5dvvmluug78X3RYEFQiMh1QpfS5fBfvs4WQKw7oigko3G0UwZLZFnZ4E4WKTQi4wbCgjwQJFMnMGJfFYNcoSJluVg/q8z3bVxfDOV0ZPWccmvA3bTf9YFHKCC3clscQrGf1NPnBGcBGm+s06t3EljoSmpjtyTgSiGrqBZ8TSCQxoyXxS+RkhNTigg6mqW9hIisxYYqlbzvRnCDhuqgZfmP7t65QG5raELVE7d+Ia+dgh024luZ9+vSk4Qb65DQIDAQAB
风格规则之一,尤其是这一规则:&#34;#4:创建{一般不是一个好主意{1}}用于链接方法以获取值的特定目的。&#34; (link)我不建议写这样的代码,但我想在其他人做之前把它放在那里。 : - )