Предположим, что у нас есть набор объектов, которые идентифицируются уникальным String
s, а также класс Tree
, который определяет иерархию на них. Этот класс реализуется с помощью Map
от узлов (представленных их идентификаторами) до Collection
их соответствующих идентификаторов детей.
class Tree {
private Map<String, Collection<String>> edges;
// ...
public Stream<String> descendants(String node) {
// To be defined.
}
}
Я хотел бы включить потоковое воспроизведение потомков node. Простым решением является следующее:
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)
);
}
Прежде чем продолжить, я хотел бы сделать следующие утверждения об этом решении. (Я прав об этом?)
-
Прогулка
Stream
, возвращаемая изdescendants
, потребляет ресурсы (время и память) - относительно размера дерева - в том же порядке сложности, что и ручное кодирование рекурсии. В частности, промежуточные объекты, представляющие состояние итерации (Stream
s,Spliterator
s,...), образуют стек, и поэтому требование памяти в любой момент времени находится в том же порядке сложности, что и глубина дерева. -
Как я понимаю this, как только я выполняю операцию завершения на
Stream
, возвращенном изdescendants
, вызов корневого уровняflatMap
приведет к тому, что все содержащиесяStream
- по одному для каждого (рекурсивного) вызоваdescendants
- будут реализованы немедленно. Таким образом, результатStream
ленив только на первом уровне рекурсии, но не выше. (Отредактировано согласно Ответ Тагира Валеева.)
Если я правильно понял эти пункты, мой вопрос таков: Как я могу определить descendants
, чтобы результирующий Stream
был ленивым?
Я хотел бы, чтобы решение было настолько элегантным, насколько возможно, в том смысле, что я предпочитаю решение, которое оставляет итерационное состояние неявным. (Чтобы пояснить, что я имею в виду: я знаю, что я мог бы написать Spliterator
, который ходит по дереву, сохраняя явный стек Spliterator
на каждом уровне. Я бы хотел этого избежать.)
(Возможно ли, что в Java можно сформулировать это как рабочий процесс-производитель-потребитель, как можно использовать на таких языках, как Julia и Go?)