是否可以在 DFS 的递归解决问题中来回移动迭代器?

问题描述

假设我有迭代器 let itt = some_string.chars().iter(); 并且我有解析 json 的函数(或某种树搜索)是否可以在此函数的另一个递归调用之间来回移动迭代器?

更简单的例子:


fn f(it: Iter<i32>,level: i32) -> Iter<i32>{
    let mut mit = it;
    for num in mit {
        if level > 2 {
            println!("{} {}",level,num);
            break
        }
        println!("{} {}",num);
        mit = f(&mit,level+1);
    }
    mit
}

fn main() {
    let tab = [1,2,3,4,5];
    let mut itr = tab.iter();
    f(itr,1);
}

预期输出

1 1
2 2 
3 3
2 4 
3 5

顺便说一句,这个例子不起作用,让它起作用(或者说这不是要走的路)会解决这个问题,非常欢迎指出相关阅读材料

解决方法

由于两个原因,您编写的代码根本无法编译。首先,考虑表达式f(&mit,level + 1)。这不会进行类型检查,因为 f 期望它的第一个参数是 Iter<i32> 类型,而是 &mit: &Iter<i32>。所以你需要用 f(mit,level + 1) 替换它。我会解决这个问题。

第二个问题是 for 循环在 into_iter 上隐式调用 mit,从而移动 mitfor 只是语法糖,所以我将手动对 for 循环进行脱糖以向您展示真正发生的事情:

fn f(it: Iter<i32>,level: i32) -> Iter<i32>{
    let mut mit = it;
    let mut iterator = mit.into_iter();
    while let Some(num) = iterator.next() {
        if level > 2 {
            println!("{} {}",level,num);
            break
        }
        println!("{} {}",num);
        mit = f(mit,level+1);
    }
    mit
}

因为 Iter<i32> 没有实现 Copy,这是一个使用后移动错误。我们将 mit 移到 into_iter 方法中,但我们再次尝试在 f(mit,level + 1) 的计算中使用它。

请注意,这实际上非常重要。通过以这种方式设置 for 循环,Rust 可以阻止您在迭代集合时以错误的方式改变集合,这可能导致意外结果(在某些情况下甚至可能导致未定义的行为)。

幸运的是,现在我们知道脱糖后的代码是什么样的,修复这个错误很容易。我们只是在脱糖代码中完全去掉变量 iterator 并得到以下内容:

fn f(it: Iter<i32>,level: i32) -> Iter<i32>{
    let mut mit = it;
    while let Some(num) = mit.next() {
        if level > 2 {
            println!("{} {}",level+1);
    }
    mit
}

这是因为对于类型 Iter<T>,方法 into_iter() 只返回 Iter<T> 本身。

这将打印预期的输出。

1 1
2 2
3 3
2 4
3 5

根据需要。

编辑:

在您的特定情况下,我们可以按如下方式重写您的代码:

fn f(it: Iter<i32>,mut level: i32) {
    for num in it {
        println!("{} {}",num);
        level = if level > 2 { level - 1 } else { level + 1 };
    }
}

fn main() {
    let tab = [1,2,3,4,5];
    let itr = tab.iter();
    f(itr,1);
}

完全消除了递归。