在调用之间保留递归函数的堆栈位置

时间:2016-08-01 14:03:55

标签: java algorithm recursion

这个问题很笼统,但我觉得最好用一个具体的例子来解释。假设我有一个包含许多嵌套子目录的目录,在某些子目录中有一些以“.txt”结尾的文本文件。样本结构可以是:

dir1
    dir2
        file1.txt
    dir3
        file2.txt
    file3.txt

我感兴趣的是,如果在Java中有一种方法可以构建一个可以调用以返回连续文本文件的方法:

TextCrawler crawler = new TextCrawler(new File("dir1"));
File textFile;
textFile = crawler.nextFile(); // value is file1.txt
textFile = crawler.nextFile(); // value is file2.txt
textFile = crawler.nextFile(); // value is file3.txt

以下是挑战:所有文本文件的内部列表都不能保存在抓取工具对象中。这是微不足道的。在这种情况下,您只需在初始化中构建一个递归构建文件列表的方法。

是否存在暂停递归方法的一般方法,以便在再次调用它时返回到它离开的堆栈中的特定点?或者我们是否必须编写针对每种情况的特定内容,并且解决方案必须针对文件爬虫,组织图搜索,递归主要查找器等进行改变?

3 个答案:

答案 0 :(得分:0)

如果您想要一个适用于任何递归函数的解决方案,您可以接受Railscast对象。它可能看起来像这样:

public void recursiveMethod(Consumer<TreeNode> func, TreeNode node){
  if(node.isLeafNode()){
      func.accept(node);
  } else{
    //Perform recursive call
  }
}

对于一堆文件,它可能如下所示:

public void recursiveMethod(Consumer<File> func, File curFile){
  if(curFile.isFile()){
      func.accept(curFile);
  } else{
    for(File f : curFile.listFiles()){
      recursiveMethod(func, f);
    }
  }
}

然后你可以用:

来调用它
File startingFile;
//Initialize f as pointing to a directory
recursiveMethod((File file)->{
  //Do something with file
}, startingFile);

根据需要进行调整。

答案 1 :(得分:0)

我认为在从递归函数返回时应该保存状态,然后在再次调用递归函数时需要恢复状态。没有通用的方法来保存这样的状态,但是可以创建模板。像这样:

class Crawler<T> {
    LinkedList<T> innerState;
    Callback<T> callback;
    constructor Crawler(T base,Callback<T> callback) {
        innerState=new LinkedList<T>();
        innerState.push(base);
        this.callback=callback; // I want functions passed here
    }
    T recursiveFunction() {
        T base=innerState.pop();
        T result=return recursiveInner(base);
        if (!result) innerState.push(base); // full recursion complete
        return result;
    }
    private T recursiveInner(T element) {
       ArrayList<T> c=callback.getAllSubElements(element);
           T d;
           for each (T el in c) {
               if (innerState.length()>0) {
                   d=innerState.pop();
                   c.skipTo(d); 
                   el=d;
                   if (innerState.length()==0) el=c.getNext(); 
                   // we have already processed "d", if full inner state is restored
               }
               T result=null;
               if (callback.testFunction(el)) result=el;
               if ((!result) && (callback.recursiveFunction(el))) result=recursiveInner(el); // if we can recurse on this element, go for it
               if (result) {
                   // returning true, go save state
                   innerState.push(el); // push current local state to "stack"
                   return result;
               }
           } // end foreach
           return null;
      }
}
interface Callback<T> {
    bool testFunction(T element);
    bool recursiveFunction(T element);
    ArrayList<t> getAllSubElements(T element);
}

此处,skipTo()是一种修改c上的迭代器以指向提供的元素的方法。 Callback<T>是一种将函数传递给类以用作条件检查器的方法。说“是一个文件夹”用于递归检查,“是T a * .txt”用于返回检查,“getAllSubclassElements”也应该属于此处。 for each循环缺乏关于如何在Java中使用可修改迭代器的知识,请适应实际代码。

答案 2 :(得分:0)

我能想到的唯一可以满足您的确切要求的方法是在单独的线程中执行递归树遍历,并让该线程一次一个地将结果传递回主线程。 (为简单起见,您可以使用有界队列进行传递,但也可以使用wait / notify,一个锁对象和一个共享引用变量来实现。)

例如,在Python中,这非常适合coroutines。不幸的是,Java没有直接的等价物。

我应该补充一点,使用线程可能会在同步和线程上下文切换中产生很大的开销。如果“生产”和“消费”的比率很好地匹配,使用队列会将它们减少到一定程度。