Soni
June 14, 2021, 3:31am
1
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
Soni
June 14, 2021, 11:14am
5
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
Soni
June 14, 2021, 6:07pm
8
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.)
system
Closed
September 13, 2021, 3:40pm
10
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.