In this article, I'm going to introduce a general pattern named Lazy Iterator for converting recursive traversal to iterator. Let's start with a familiar example of inorder traversal of binary tree.

It is straightforward to write a recursive function which returns the nodes as List:

// Java
List<Node> traverseInOrder(Node node) {
if (node == null) {
return emptyList();
}
Return concat(traverseInOrder(node.left), List.of(node), traverseInOrder(node.right));
} List<T> concat(List<T>... lists) {
... // List concatenation.
}

Of course, we can simply return the iterator of the result list, but the drawback is twofold: 1) it is not lazily evaluated, which means even if we only care about the first a few items, we must traverse the whole tree. 2) the space complexity is O(N), which is not really necessary. We'd like to have an iterator which is lazily evaluated and takes memory only as needed. The function signature is given as below:

// Java
Iterator<Node> traverseInOrder(Node node) {
// TODO
}

One idea most people could easily come up with (but not necessarily be able to correctly implement) is to use a stack to keep track of the state of traversal. That works, but it is relatively complex and not general, there is a neat, simple and general way. This is the code which demonstrates the pattern:

// Java
Iterator<Node> traverseInOrder(Node node) {
if (node == null) {
return emtpyIterator();
}
Supplier<Iterator<Node>> leftIterator = () -> traverseInOrder(node.left);
Supplier<Iterator<Node>> currentIterator = () -> singleNodeIterator(node);
Supplier<Iterator<Node>> rightIterator = () -> traverseInOrder(node.right);
return concat(leftIterator, currentIterator, rightIterator);
} Iterator<T> concat(Supplier<Iterator<T>>... iteratorSupplier) {
// TODO
}

If you are not familiar with Java, Supplier<T> is a function which takes no argument and returns a value of type T, so basically you can think of it as a lazily evaluated T.

Note the structural correspondence between the code above and the original recursive traversal function. Instead of traverse the left and right branches before returning, it creates a lambda expression for each which when evaluated will return an iterator. So the iterators for the subproblems are lazily created.

Finally and the most importantly, it needs the help of a general function concat which creates an iterator backed by multiple iterator suppliers. The type of this function is (2 suppliers):

Supplier<Iterator<T>> -> Supplier<Iterator<T>> -> Iterator<T>

The key is that this function must keep things lazy as well, evaluate only when necessary. Here is how I implement it:

// Java
Iterator<T> concat(Supplier<Iterator<T>>... iteratorSuppliers) {
return new LazyIterator(iteratorSuppliers);
} class LazyIterator<T> {
private final Supplier<Iterator<T>>[] iteratorSuppliers;
private int i = 0;
private Iterator<T> currentIterator = null; public LazyIterator(Supplier<Iterator<T>>[] iteratorSuppliers) {
this.iteratorSuppliers = iteratorSuppliers;
} // When returning true, hasNext() always sets currentIterator to the correct iterator
// which has more elements.
@Override
public boolean hasNext() {
while (i < iteratorSuppliers.length) {
if (currentIterator == null) {
currentIterator = iteratorSuppliers[i].apply();
}
if (currentIterator.hasNext()) {
return true;
}
currentIterator = null;
i++;
}
return false;
} @Override
public T next() {
return currentIterator.next();
}
}

The LazyIterator class works only on Iterator<T>, which means it is orthogonal to specific recursive functions, hence it serves as a general way of converting recursive traversal into iterator.

If you are familiar with languages with generator, e.g., Python, the idiomatic way of implementing iterator for recursive traversal is like this:

# Python
def traverse_inorder(node):
if node.left:
for child in traverse_inorder(node.left):
yield child
yield node
if node.right:
for child in traverse_inorder(node.right):
yield child

yield will save the current stack state for the generator function, and resume the computation later when being called again. You can think of the lazy iterator pattern introduced in this article as a way of capturing the computation which can be resumed, hence simulate the generator feature in other languages.

Converting Recursive Traversal to Iterator的更多相关文章

  1. Java性能提示(全)

    http://www.onjava.com/pub/a/onjava/2001/05/30/optimization.htmlComparing the performance of LinkedLi ...

  2. HashMap与TreeMap源码分析

    1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Ja ...

  3. java.util.Map源码分析

    /** * An object that maps keys to values. A map cannot contain duplicate keys; * each key can map to ...

  4. [leetcode]428. Serialize and Deserialize N-ary Tree序列化与反序列化N叉树

    Serialization is the process of converting a data structure or object into a sequence of bits so tha ...

  5. jdk1.8新特性之接口default方法

    众所周知,default是java的关键字之一,使用场景是配合switch关键字用于条件分支的默认项.但自从java的jdk1.8横空出世以后,它就被赋予了另一项很酷的能力——在接口中定义非抽象方法. ...

  6. java面试基础篇(一)

    最近想深入的理解一下java 的工作机制,也是便于后期的面试. 1.A:HashMap和Hashtable有什么区别? Q:HashMap和Hashtable都实现了Map接口,因此很多特性非常相似. ...

  7. Java-Class-I:java.util.Map

    ylbtech-Java-Class-I:java.util.Map 1.返回顶部 1.1. import java.util.HashMap; import java.util.Map; 1.2. ...

  8. Java的集合(一)

    转载:https://blog.csdn.net/hacker_zhidian/article/details/80590428 Java集合概况就三个:List.set和map list(Array ...

  9. Tree 使用方式

    Traditional Ways of Tree Traversal This page contains examples of some “standard” traversal algorith ...

随机推荐

  1. JVM学习02:GC垃圾回收和内存分配

    JVM学习02:GC垃圾回收和内存分配 写在前面:本系列分享主要参考资料是  周志明老师的<深入理解Java虚拟机>第二版. GC垃圾回收和内存分配知识要点Xmind梳理 案例分析1-(G ...

  2. Linux(centos)下安装JDK

    安装 JDK是运行java程序必不可少的环境,服务器上跑程序也不例外.首先在安装之前,要知道Linux下安装软件有两种,一种是使用yum等命令直接下载,一种是使用上传下载工具,上传至Linux下使用, ...

  3. Vue仿string.format

    Vue.prototype.$stringFormat = function stringFormat (formatted, args) { for (let i = 0; i < args. ...

  4. filter 全局和局部过滤器

    1,局部过滤器 2,全局过滤器 使用方法相同,在花括号中使用过滤器名或者v-bind中使用

  5. docker镜像运行错误排查

    docker做服务时,如果客户端无法连接,错误排查: 1.先使用 docker ps 查看镜像是否都在运行中,如果没有就进入镜像查看日志 2.如果确定代码及配置文件没有问题,就需要检查镜像的替换是否正 ...

  6. [DBNETLIB][ConnectionOpen(Invalid Instance())] 无效的连接 的解决办法

    Provider=SQLOLEDB.1;Persist Security Info=False;User ID=sa;Data Source=192.168.1.28,1433 连接SQL serve ...

  7. 如何为shell安装有道及更新pip.

    今天尝试安装shell下的有道翻译,提示需要安装pip. [root@mestery ~]# yum install python-pip [root@mestery ~]# sudo pip ins ...

  8. Desktop Central 的移动设备管理功能

    Desktop Central 的移动设备管理功能1.移动应用程序管理设备管理不会仅仅只是配置策略.检索资产信息和保护设备.应用程序管理与设置员工的移动设备一样重要.使用 Desktop Centre ...

  9. 人力资源项目中 add_account.php

    add_account.php ( 文件浏览 ) <?phpinclude('db_con.php');   if(isset($_POST['save'])) {    $employee_i ...

  10. Java: 集合类详解

    0.参考文献 http://blog.csdn.net/liulin_good/article/details/6213815 1.java集合类图 1.1 1.2 上述类图中,实线边框的是实现类,比 ...