Mapping over a `const` array of strings, reversing each one

Hey all,

While working on day 1 of advent of code my approach required me to create an array of string values and then create another version of that array where each string is reversed. I managed to get it working but I feel like there must be a better way to create the reversed version of the array (NUM_STRS below):

use std::fs;

const NUM_STRS: [&str; 9] = [
    "one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
];

const CHAR_DIGIT_RADIX: u32 = 10;

fn main() {
    let contents = fs::read_to_string("1.txt").unwrap();

    let mut sum = 0;

    for line in contents.lines() {
        sum += calibration_value(line);
    }

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

fn calibration_value(line: &str) -> u32 {
    let num_strs: Vec<String> = NUM_STRS.into_iter().map(|s| String::from(s)).collect();

    let first_int = get_first_int(line, &num_strs);

    let line_reversed: String = line.chars().rev().collect();

    let num_strs_rev: Vec<String> = num_strs.iter().map(|s| s.chars().rev().collect()).collect();

    let last_int = get_first_int(&line_reversed, &num_strs_rev);

    last_int + first_int * 10
}

fn get_first_int(s: &str, num_strs: &Vec<String>) -> u32 {
    let mut checked_chars = String::from("");

    for c in s.chars() {
        if c >= '0' && c <= '9' {
            return c.to_digit(RADIX).unwrap();
        }

        checked_chars.push(c);

        for (i, num_str) in num_strs.iter().enumerate() {
            if checked_chars.contains(num_str) {
                return (1 + i).try_into().unwrap();
            }
        }
    }

    panic!("No int found in {}", s);
}

Obviously the reversed version can also be a constant, so I could manually type it out and make it a const too. But I'm curious to know if there's a cleaner way to dynamically reverse it as above.

I don't like the approach I've taken because I have to create a non-reversed copy of NUM_STRS just to make it a Vec<String> so that it matches the type of the reversed version and so satisfies the parameter type for get_first_int()'.

I've tried the following but can't get the collect() call to satisfy the compiler:

    let mut num_strs_rev: [&str; 9] = [""; 9];

    for (i, s) in NUM_STRS.iter().enumerate() {
        let s_rev: &str = s.chars().rev().collect();
        num_strs_rev[i] = s_rev;
    }

That gives

value of type `&str` cannot be built from `std::iter::Iterator<Item=char>`

Also tried this:

    for (i, s) in NUM_STRS.iter().enumerate() {
        let s_rev: String = s.chars().rev().collect();
        num_strs_rev[i] = s_rev.as_str();
    }

But that gives the following error:

error[E0597]: `s_rev` does not live long enough

You can abstract over String and &str with AsRef:

fn get_first_int(s: &str, num_strs: &[impl AsRef<str>]) -> u32 {
    let mut checked_chars = String::from("");

    for c in s.chars() {
        if c >= '0' && c <= '9' {
            return c.to_digit(CHAR_DIGIT_RADIX).unwrap();
        }

        checked_chars.push(c);

        for (i, num_str) in num_strs.iter().enumerate() {
            if checked_chars.contains(num_str.as_ref()) { // THIS LINE HAS CHANGED
                return (1 + i).try_into().unwrap();
            }
        }
    }

    panic!("No int found in {}", s);
}

To answer you other question: I don't think it's possible, as you'd need either allocating or a const fn reverse<T>(&mut [T]), but mutable references aren't allowed in const. array::map is also not const.

1 Like

Thanks, that's so much nicer :bowing_man: