【问题标题】:Retaining the stack position of a recursive function between calls在调用之间保留递归函数的堆栈位置
【发布时间】:2016-12-06 15:44:45
【问题描述】:

这个问题很笼统,但我觉得最好用一个具体的例子来解释。假设我有一个包含许多嵌套子目录的目录,并且在其中一些子目录中有以“.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

挑战来了:不能将所有文本文件的内部列表保存在爬虫对象中。那是微不足道的。在这种情况下,您只需在初始化中构建一个递归构建文件列表的方法。

是否有一种暂停递归方法的通用方法,以便在再次调用它时返回到堆栈中它离开的特定点?或者我们是否必须针对每种情况编写一些特定的东西,并且解决方案必须因文件爬虫、组织结构图搜索、递归素数查找器等而有所不同?

【问题讨论】:

  • 所以你希望这个 `nextFile()' 方法在没有状态的情况下有状态?
  • 递归函数通常具有引用透明性。你所要做的就是给它同样的参数,它就会做同样的操作。
  • @tirpitz.verus 我认为他想要一个对象crawler 能够保存通用状态信息以便在进入递归搜索时重复使用。
  • 或者可能编写一个递归方法,接收 java.util.function.Consumer 并为结构的每个元素执行它。

标签: java algorithm recursion


【解决方案1】:

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

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);

根据需要进行调整。

【讨论】:

    【解决方案2】:

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

    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&lt;T&gt; 是一种将函数传递给类以用作条件检查器的方法。递归检查说“Is T a folder”,返回检查说“Is T a *.txt”,“getAllSubclassElements”也应该属于这里。 for each 循环是因为缺乏关于如何在 Java 中使用可修改迭代器的知识,请适应实际代码。

    【讨论】:

      【解决方案3】:

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

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

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

      【讨论】:

      • 有趣。我会看看我是否可以一起获得一些示例代码。
      猜你喜欢
      • 1970-01-01
      • 2019-09-30
      • 2019-08-07
      • 2018-12-19
      • 2018-02-25
      • 1970-01-01
      • 2023-03-27
      • 2015-11-01
      • 2017-09-12
      相关资源
      最近更新 更多