Better way to parse two integers?

This is... a way to parse two integers. Specifically, it parses "0\t1\n" into (0,1).

        let stdout = String::from_utf8(stdout);
        match stdout.as_ref().ok().map(|x| {
            x.trim()
        }).map(|x| {
            (
                x,
                &x[..x.len()-x.trim_start_matches(|x| {
                    char::is_ascii_digit(&x)
                }).len()],
                &x[x.trim_end_matches(|x| {
                    char::is_ascii_digit(&x)
                }).len()..],
            )
        }).and_then(|(x, y, z)| {
            Some(x).and_then(|x| {
                x.strip_prefix(y)
            }).and_then(|x| {
                x.strip_suffix(z)
            }).filter(|&x| {
                x == "\t"
            }).and(Some((y, z)))
        }).map(|(y, z)| {
            (y.parse::<u64>().ok(), z.parse::<u64>().ok())
        }).and_then(|(y, z)| { 
            y.map(|y| (y, z))
        }).and_then(|(y, z)| {
            z.map(|z| (y, z))
        }) {
            Some(v) => {
                return Ok(v)
            },
            _ => (),
        }

But, it's probably slightly questionable. Is there a better way we could do this? Maybe with less lines? Without regex tho.

1 Like

I would probably write something along these general lines:

fn parse_two_ints(x: &str) -> Option<(u64, u64)> {
    let (a, x) = parse_int(x)?;
    let (b, _) = parse_int(x)?;
    Some((a, b))
}

fn parse_int(x: &str) -> Option<(u64, &str)> {
    let x = x.trim_start();
    let len = x.find(|c: char| c.is_whitespace()).unwrap_or(x.len());
    Some((x[..len].parse().ok()?, &x[len..]))
}

Note that this allows arbitrary whitespace between the characters (not just tabs), and it doesn't check that the end of the string is reached. But it should be easy to tweak depending on the exact behavior you want for malformed input.

Here's a version that I think more closely matches the behavior of your original code:

fn parse_two_ints(x: &str) -> Option<(u64, u64)> {
    let x = x.trim();
    let (a, x) = parse_int(x)?;
    let x = x.strip_prefix("\t")?;
    let (b, x) = parse_int(x)?;
    x.is_empty().then(|| (a, b))
}

fn parse_int(x: &str) -> Option<(u64, &str)> {
    let len = x.find(|c: char| !c.is_ascii_digit()).unwrap_or(x.len());
    let (num, x) = x.split_at(len);
    Some((num.parse().ok()?, x))
}
1 Like

There's also this two-line version:

fn parse_two_ints(x: &str) -> Option<(u64, u64)> {
    let mut i = x.trim().splitn(2, "\t");
    Some((i.next()?.parse().ok()?, i.next()?.parse().ok()?))
}
1 Like

This accepts "+0\t+0\n" which we don't want. We're not happy with any of these solutions tbh. If only we had try {} :‌p

I guess you can adjust @mbrubeck's solution. You just have to check the first char of each part.

pub fn parse_two_ints(x: &str) -> Option<(u64, u64)> {
    let (left, right) = x.trim().split_once('\t')?;
    match (left.starts_with(|c| char::is_ascii_digit(&c)),
          right.starts_with(|c| char::is_ascii_digit(&c)))
    {
        (true, true) => Some((left.parse().ok()?, right.parse().ok()?)),
        _ => None, 
    }
}

fn main() {
    assert_eq!(parse_two_ints("0\t1\n"), Some((0,1)));
    assert_eq!(parse_two_ints("0\t\t1\n"), None);
    assert_eq!(parse_two_ints("+0\t+1\n"), None);
    assert_eq!(parse_two_ints("0\t+1\n"), None);
    assert_eq!(parse_two_ints("0\t1+\n"), None);
}
2 Likes

Nice! A cute way to golf this is:

fn parse_two_ints(x: &str) -> Option<(u64, u64)> {
    let (a, b) = x.trim().split_once('\t')?;
    a.as_bytes().first()?.is_ascii_digit().then(nop)?;
    b.as_bytes().first()?.is_ascii_digit().then(nop)?;
    Some((a.parse().ok()?, b.parse().ok()?))
}

fn nop() {}
2 Likes

Hmm oh this seems cleaner

        let stdout = String::from_utf8(stdout);
        match stdout.as_ref().ok().map(|x| x.trim()).filter(|x| {
            x.trim_start_matches(|x| {
                char::is_ascii_digit(&x)
            }).trim_end_matches(|x| {
                char::is_ascii_digit(&x)
            }) == "\t"
        }).and_then(|x| {
            x.split_once("\t")
        }).and_then(|(y, z)| {
            Some((y.parse::<u64>().ok()?, z.parse::<u64>().ok()?))
        }) {
            Some(v) => {
                return Ok(v)
            },
            _ => (),
        }
        // [error handling]

(Also, if we had an ?-for-success operator, the match would be unnecessary.)

std::option::Option - Rust (rust-lang.org) maybe?

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.