如何从rust迭代器中获取最后一个和第一个元素

vx6bjr1n  于 2024-01-08  发布在  其他
关注(0)|答案(2)|浏览(119)

这是Day 1 of Advent of Code 2023的一部分。我想提取数字,获得第一个和最后一个字符,并转换回整数。这是工作版本。

fn main() {
    let line = "treb7uchet";

    // Find all the digit
    let v: String = line
        .chars()
        // Filter the numeric character
        .filter(|c| c.is_numeric() )
        // Only filter the first element
        // .enumerate()
        // .filter(|&(i, _)| i == 0)
        // .map(|(_, e)| e)
        .collect::<String>();

    // println!("{}", v);

    // Get the first and the last digit, convert into number
    let my_int: i32 = format!("{}{}", v.chars().nth(0).unwrap(), v.chars().last().unwrap())
        .parse()
        .unwrap();

    println!("{}", my_int);
}

Ans: 77

字符串
这段代码已经可以工作了,但是我想知道我是否可以在1行代码而不是2行代码上运行所有内容。我尝试了enumerate(注解代码),但是它只能filter第一项(通过知道它的索引)。我想知道如何过滤最后一项。或者有什么方法可以提取迭代器中的最后一个和第一个元素?
先谢了。
请随意在Rust playground中测试它。

hjzp0vay

hjzp0vay1#

有一个简单得多的零拷贝解决方案。
char::to_digit返回一个Option,如果字符是一个有效的数字,它将是Some,数字将是u32。我们可以利用filter_map来生成一个字符串中的字符的迭代器,这些字符是数字,转换为u32

let mut digits = line.chars().filter_map(|c| c.to_digit(10));

字符串
因为Chars是一个DoubleEndedIterator,所以我们可以从两端取。但是,由于字符串中的第一个和最后一个字符可以是同一个字符,所以我们不能只调用next,然后再调用next_back,因为当字符串包含个位数时,next_back将返回None
我们可以通过存储第一个数字来解决这个问题,然后在next_back返回None的情况下将其用作默认值。

let first = digits.next().unwrap();
let last = digits.next_back().unwrap_or(first);


为了生成一行的数字,我们只需将第一位数字乘以10,然后将其添加到最后一位数字:

first * 10 + last


请注意,在这段代码中,我们没有分配String然后解析它!我们只是在操作数字;没有堆分配。
我们可以将此合并与多行迭代相结合,以产生非常快速且易于理解的解决方案:

lines
    .lines()
    .map(|line| {
        let mut digits = line.chars().filter_map(|c| c.to_digit(10));

        let first = digits.next().unwrap();
        let last = digits.next_back().unwrap_or(first);

        first * 10 + last
    })
    .sum();


IMO这比单行Map函数更具可读性。
现在,如果你 * 真的想要一行 *,即使它很难阅读,那么你可以实现这一点,尽管注意它重复了创建迭代器的代码:

line.chars().filter_map(|c| c.to_digit(10)).next().unwrap() * 10 + line.chars().filter_map(|c| c.to_digit(10)).next_back().unwrap()


这行代码足够长,rustfmt希望将其 Package 到多行代码中--甚至比我给予的先前方法还要多。
从技术上讲,您可以将任何程序编写为一行程序,但我们有一个很好的理由不这样做,所以我强烈建议您重新考虑您的一行程序解决方案的目标。

vdgimpew

vdgimpew2#

由于任务只有短字符串和更少的数字,我们可以负担O(n)运行所有数字。
这里的技巧是使用一个迭代器方法来维护一个状态,并使用这个状态来保存第一个数字。初始状态由一个特定的值编码,并在单个数字的情况下触发加倍。
第一个想法保持了数字的char性质。

fn main() {
    let lines = ["1abc2", "pqr3stu8vwx", "a1b2c3d4e5f", "treb7uchet"];
    for line in lines {
        println!(
            "{}",
            String::from_iter(
                line.chars()
                    .filter(|c| c.is_numeric())
                    .fold(['_', '_'], |acc, c| if acc[0] == '_' {
                        [c, c]
                    } else {
                        [acc[0], c]
                    })
                    .iter()
            )
        );
    }
}

字符串
第二个想法利用了to_digit()的优点,甚至更简单。

fn main() {
    let lines = ["1abc2", "pqr3stu8vwx", "a1b2c3d4e5f", "treb7uchet"];
    for line in lines {
        println!(
            "{}",
            line.chars()
                .filter_map(|c| c.to_digit(10))
                .fold(100, |acc, d| if acc > 99 {
                    11 * d
                } else {
                    acc - acc % 10 + d
                })
        );
    }
}


最后一个简单的步骤来总结所有两位数的数字是作为一个练习,并不难。

相关问题