< 返回版块

Neutron3529 发表于 2020-06-18 19:22

在rust里面,我们可以对iter使用fold或者scan

如果使用fold,我们可以返回计算的最终结果,也只能返回最终结果

如果使用scan,我们可以在保留状态变量的同,对iter中的每一个元素进行计算并返回某个结果

我想问的是,是否存在一个介于这两者之间的函数,使我们可以针对部分iter元素返回计算结果而忽略其他元素

比如,现在有一个向量a,我希望获得一个向量b,满足:

(1)向量b的元素个数与向量a中等于0的元素个数相等

(2)向量b的第i个元素是a中第i-1个0到第i个0这些元素的和

差不多就是这个意思(不考虑数据溢出之类的情况)

/*input:*/ a=vec![1,2,0,3,4,0,5];
/*output:*/ assert_eq!(b,vec![3,7]);

我能想到的只有手写一个vec,然后调用for循环去一个个vec.push()

想问一下,能否用rust的迭代器,写一个更优美的解答

就比如

a.into_iter().???(0,|state,x|if(x==0){let ans=*state;*state=0;Some(ans)}else{*state+=x;None}).collect()

想知道有没有这样一个名叫???的函数(语法是scan的语法)

我试了一下,如果这里用scan,collect会失败。

    let a:Vec<i32>=vec![1,2,-3,4,5];
    let b:Vec<i32>=a.into_iter().scan(0,|state,x|{*state+=x;if(x>0){None}else{Some(*state)}}).collect();//才发现这里写错了……
    println!("{:?}",b);//只会打印出[]
    let a:Vec<i32>=vec![1,2,-3,4,5];
    let mut c=a.into_iter().scan(0,|state,x|{*state+=x;if(x>0){None}else{Some(*state)}});
    println!("{:?}",c.next());//None
    println!("{:?}",c.next());//None
    println!("{:?}",c.next());//Some(0)
    println!("{:?}",c.next());//None
    println!("{:?}",c.next());//None, iter在这一步用完
    println!("{:?}",c.next());//None, iter在上一步其实已经用完了,但程序不知道
}

想知道有没有什么办法优雅地消费rust的迭代器

(如果最优雅的方法是手写for循环然后vec.push的话……就这样吧)

(现在我只会.into_iter().scan()然后.collect()/.for_each(),连手写迭代器trait都不会)

评论区

写评论
vkill 2020-06-19 10:14
fn main() {
    let a = vec![1, 2, 0, 3, 4, 0, 5];
    let (b, _) = a.into_iter().fold(
        (Vec::<i32>::default(), Vec::<i32>::default()),
        |mut out, item| {
            if item != 0 {
                out.1.push(item)
            } else {
                out.0.push(out.1.iter().sum());
                out.1.clear();
            }

            out
        },
    );
    assert_eq!(b, vec![3, 7]);
}

作者 Neutron3529 2020-06-19 01:22

原来还有两层Some这样神奇的操作

学到了!

至于我用iter的原因很简单,用iter的话rust不会进行边界检查,而用下标的话rust有可能进行边界检查

虽然我不确定检查发生在编译还是运行阶段

但官方的ZCA都说了,iter“甚至略快一点”……

对以下内容的回复:

whfuyn 2020-06-19 00:36

我写了一个,然而并不优雅,我以前也喜欢把循环用迭代器的方法写,但现在觉得还是应该挑选最合适的写法,有时候一堆迭代器搞来搞去,反而在花里胡哨中显得笨拙……

fn main() {
    let xs = vec![1,2,0,3,4,0,5];
    let ys = xs.iter().scan(0, |acc, &x|{
        if x == 0 {
            let tmp = *acc;
            *acc = 0;
            Some(Some(tmp))
        }
        else {
            *acc += x;
            Some(None)
        }
    })
    .filter_map(|x| x)
    .collect::<Vec<i32>>();
    dbg!(ys); // 3 7
}

solarsail 2020-06-19 00:12

不过用迭代器看上去也不错,至少可读性挺好

对以下内容的回复:

solarsail 2020-06-19 00:07

我觉得这种直接用 for c in a.chars() {...} 更顺些,不用强求迭代器吧。实际上是个 parse 的动作。

对以下内容的回复:

作者 Neutron3529 2020-06-18 23:42

题解如下

use std::rc::Rc;
use std::cell::RefCell;
impl Solution {
    pub fn recover_from_preorder(s: String) -> Option<Rc<RefCell<TreeNode>>> {
        let a = s.split('-').filter(|x|!x.is_empty()).map(|x|x.parse::<i32>().unwrap()).collect::<Vec<i32>>();
        let mut a=a.into_iter();
        let mut stack=Vec::with_capacity(100);
        stack.push(Rc::new(RefCell::new(TreeNode::new(a.next().unwrap()))));
        s.split(|x|x!='-').filter(|x|!x.is_empty()).map(|x|x.len()).zip(a).for_each(|(depth,val)|{
            let tree=Rc::new(RefCell::new(TreeNode::new(val)));
            if stack.len()==depth{
                (*stack.last().unwrap()).borrow_mut().left=Some(tree.clone());
                stack.push(tree)
            }else{
                stack.truncate(depth);
                (*stack.last().unwrap()).borrow_mut().right=Some(tree.clone());
                stack.push(tree);
            }
        });
        Some((*stack.first().unwrap()).clone())
    }
}

对以下内容的回复:

作者 Neutron3529 2020-06-18 22:38

仔细读了读官方的某些代码

好像可以直接map然后filter的……

对以下内容的回复:

作者 Neutron3529 2020-06-18 22:19

看了一下str的split,好像可以直接判断字符串是否为空然后写进同一个函数了……

有点脏

先这么写吧……

对以下内容的回复:

作者 Neutron3529 2020-06-18 22:16

学到了

以后或许会有用。

不过……目前我遇到的问题跟这个关系不太大

可能是我抽象的时候弄错了什么。

提出这个问题的原因是今天Leetcode的每日一题。

我们从二叉树的根节点 root 开始进行深度优先搜索。

在遍历中的每个节点处,我们输出 D 条短划线(其中 D 是该节点的深度),然后输出该节点的值。(如果节点的深度为 D,则其直接子节点的深度为 D + 1。根节点的深度为 0)。

如果节点只有一个子节点,那么保证该子节点为左子节点。

给出遍历输出 S,还原树并返回其根节点 root。

输入:"1-2--3--4-5--6--7"
输出:[1,2,5,3,4,6,7]


来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/recover-a-tree-from-preorder-traversal
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

我在想,如果开一个栈存节点,然后把字符串处理成(减号个数,数值)这种格式的迭代器

之后我们只需要比较减号个数与栈的大小。

若减号个数等于栈的大小则新节点为原节点的左叶子节点,并将新节点入栈。

否则在栈中pop元素直到栈大小等于减号个数,此时将新节点设为栈顶元素右叶子节点,并将新节点入栈。

(减号个数大于栈的大小可以panic!)


这个想法直接死在了第一步……

不过经你提醒,我至少可以用split两次,一次切数字,一次切减号

但总觉得这样多遍历了一次字符串……虽然有点强迫症地不太喜欢,但至少题目能做了……

多谢~

对以下内容的回复:

solarsail 2020-06-18 20:10

针对你的需求可以用 slice::split,不过不是你期望的那种实现方式。

fn main() {
    let a = vec![1, 2, 0, 3, 4, 0, 5];
    let mut b: Vec<i32> = a
        .as_slice()
        .split(|&n| n == 0)
        .map(|v| v.iter().sum())
        .collect();
    b.pop();
    println!("{:?}", b);
}
1 共 10 条评论, 1 页