Skip to content

Commit e84b085

Browse files
committed
Faster numeric parsing
1 parent 019f48d commit e84b085

22 files changed

+164
-75
lines changed

src/util/parse.rs

+126-41
Original file line numberDiff line numberDiff line change
@@ -12,60 +12,145 @@
1212
//!
1313
//! [`iter_unsigned`]: ParseUnsigned::iter_unsigned
1414
//! [`iter_signed`]: ParseSigned::iter_signed
15-
use std::iter::{Filter, Map};
16-
use std::str::{FromStr, Split};
17-
18-
/// Much shorter alias for the trait return type.
19-
type Wrapper<'a, T> = Map<Filter<Split<'a, fn(char) -> bool>, fn(&&str) -> bool>, fn(&str) -> T>;
20-
21-
/// This convenience method does the same thing as `s.parse().unwrap()` but without the
22-
/// extra `<F as FromStr>::Err` type bound so that we can have shorter type signatures.
23-
pub fn from<T: FromStr>(s: &str) -> T {
24-
match s.parse() {
25-
Ok(t) => t,
26-
Err(_) => panic!("Unable to parse \"{s}\""),
27-
}
28-
}
15+
use std::marker::PhantomData;
16+
use std::ops::{Add, Neg, Shl};
17+
use std::str::Bytes;
18+
19+
/// Traits allow us to keep type safety, restricting the possiblities to only integer types.
20+
pub trait Common: Copy + From<u8> + Add<Output = Self> + Shl<u8, Output = Self> {}
21+
impl Common for u8 {}
22+
impl Common for u16 {}
23+
impl Common for u32 {}
24+
impl Common for u64 {}
25+
impl Common for usize {}
26+
impl Common for i16 {}
27+
impl Common for i32 {}
28+
impl Common for i64 {}
2929

30-
/// This trait allows us to keep type safety, restricting the possiblities to only
31-
/// `u32`, `u64` and `usize`.
32-
pub trait Unsigned: FromStr {}
30+
pub trait Unsigned: Common {}
3331
impl Unsigned for u8 {}
32+
impl Unsigned for u16 {}
3433
impl Unsigned for u32 {}
3534
impl Unsigned for u64 {}
3635
impl Unsigned for usize {}
3736

38-
/// Rust closures have an unique type that only the compiler knows and that us mere
39-
/// mortals are not allowed to ascertain. Fortunately we can coerce the type to a known
40-
/// function signature by using intermediate variables.
41-
pub trait ParseUnsigned {
42-
fn iter_unsigned<T: Unsigned>(&self) -> Wrapper<'_, T>;
37+
pub trait Signed: Common + Neg<Output = Self> {}
38+
impl Signed for i16 {}
39+
impl Signed for i32 {}
40+
impl Signed for i64 {}
41+
42+
pub struct ParseUnsigned<'a, T> {
43+
bytes: Bytes<'a>,
44+
phantom: PhantomData<T>,
45+
}
46+
47+
pub struct ParseSigned<'a, T> {
48+
bytes: Bytes<'a>,
49+
phantom: PhantomData<T>,
50+
}
51+
52+
pub trait ParseOps {
53+
fn unsigned<T: Unsigned>(&self) -> T;
54+
fn signed<T: Signed>(&self) -> T;
55+
fn iter_unsigned<T: Unsigned>(&self) -> ParseUnsigned<'_, T>;
56+
fn iter_signed<T: Signed>(&self) -> ParseSigned<'_, T>;
4357
}
4458

45-
impl ParseUnsigned for &str {
46-
fn iter_unsigned<T: Unsigned>(&self) -> Wrapper<'_, T> {
47-
let not_numeric: fn(char) -> bool = |c| !c.is_ascii_digit();
48-
let not_empty: fn(&&str) -> bool = |s| !s.is_empty();
49-
self.split(not_numeric).filter(not_empty).map(from)
59+
impl ParseOps for &str {
60+
fn unsigned<T: Unsigned>(&self) -> T {
61+
match try_unsigned(&mut self.bytes()) {
62+
Some(t) => t,
63+
None => panic!("Unable to parse \"{self}\""),
64+
}
65+
}
66+
67+
fn signed<T: Signed>(&self) -> T {
68+
match try_signed(&mut self.bytes()) {
69+
Some(t) => t,
70+
None => panic!("Unable to parse \"{self}\""),
71+
}
72+
}
73+
74+
fn iter_unsigned<T: Unsigned>(&self) -> ParseUnsigned<'_, T> {
75+
ParseUnsigned { bytes: self.bytes(), phantom: PhantomData }
76+
}
77+
78+
fn iter_signed<T: Signed>(&self) -> ParseSigned<'_, T> {
79+
ParseSigned { bytes: self.bytes(), phantom: PhantomData }
5080
}
5181
}
5282

53-
/// This trait allows us to keep type safety, restricting the possiblities to only
54-
/// `i32` and `i64`.
55-
pub trait Signed: FromStr {}
56-
impl Signed for i32 {}
57-
impl Signed for i64 {}
83+
impl<T: Unsigned> Iterator for ParseUnsigned<'_, T> {
84+
type Item = T;
5885

59-
/// Essentially the same as `ParseUnsigned` but also considers the `-` character as part
60-
/// of a number.
61-
pub trait ParseSigned {
62-
fn iter_signed<T: Signed>(&self) -> Wrapper<'_, T>;
86+
fn size_hint(&self) -> (usize, Option<usize>) {
87+
let (lower, upper) = self.bytes.size_hint();
88+
(lower / 3, upper.map(|u| u / 3))
89+
}
90+
91+
fn next(&mut self) -> Option<Self::Item> {
92+
try_unsigned(&mut self.bytes)
93+
}
94+
}
95+
96+
impl<T: Signed> Iterator for ParseSigned<'_, T> {
97+
type Item = T;
98+
99+
fn size_hint(&self) -> (usize, Option<usize>) {
100+
let (lower, upper) = self.bytes.size_hint();
101+
(lower / 3, upper.map(|u| u / 3))
102+
}
103+
104+
fn next(&mut self) -> Option<Self::Item> {
105+
try_signed(&mut self.bytes)
106+
}
63107
}
64108

65-
impl ParseSigned for &str {
66-
fn iter_signed<T: Signed>(&self) -> Wrapper<'_, T> {
67-
let not_numeric: fn(char) -> bool = |c| !c.is_ascii_digit() && c != '-';
68-
let not_empty: fn(&&str) -> bool = |s| !s.is_empty();
69-
self.split(not_numeric).filter(not_empty).map(from)
109+
fn try_unsigned<T: Unsigned>(bytes: &mut Bytes) -> Option<T> {
110+
let mut n = loop {
111+
let b = bytes.next()?;
112+
let d = b.wrapping_sub(b'0');
113+
114+
if d < 10 {
115+
break T::from(d);
116+
}
117+
};
118+
119+
loop {
120+
let Some(b) = bytes.next() else { break Some(n) };
121+
let d = b.wrapping_sub(b'0');
122+
123+
if d < 10 {
124+
n = (n << 3) + (n << 1) + T::from(d);
125+
} else {
126+
break Some(n);
127+
}
128+
}
129+
}
130+
131+
fn try_signed<T: Signed>(bytes: &mut Bytes) -> Option<T> {
132+
let (mut n, negative) = loop {
133+
let b = bytes.next()?;
134+
let d = b.wrapping_sub(b'0');
135+
136+
if d == 253 {
137+
break (T::from(0), true);
138+
}
139+
if d < 10 {
140+
break (T::from(d), false);
141+
}
142+
};
143+
144+
loop {
145+
let Some(b) = bytes.next() else {
146+
break Some(if negative { -n } else { n });
147+
};
148+
let d = b.wrapping_sub(b'0');
149+
150+
if d < 10 {
151+
n = (n << 3) + (n << 1) + T::from(d);
152+
} else {
153+
break Some(if negative { -n } else { n });
154+
}
70155
}
71156
}

src/year2015/day07.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ pub fn parse(input: &str) -> Result {
3737
match second {
3838
"AND" => Gate::And(first, third),
3939
"OR" => Gate::Or(first, third),
40-
"LSHIFT" => Gate::LeftShift(first, from(third)),
41-
"RSHIFT" => Gate::RightShift(first, from(third)),
40+
"LSHIFT" => Gate::LeftShift(first, third.unsigned()),
41+
"RSHIFT" => Gate::RightShift(first, third.unsigned()),
4242
_ => unreachable!(),
4343
}
4444
};
@@ -67,7 +67,7 @@ fn signal<'a>(
6767
}
6868

6969
let result = if key.chars().next().unwrap().is_ascii_digit() {
70-
from(key)
70+
key.unsigned()
7171
} else {
7272
match circuit[key] {
7373
Gate::Wire(w) => signal(w, circuit, cache),

src/year2015/day09.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ pub fn parse(input: &str) -> Result {
4343
for [start, _, end, _, distance] in tokens.iter() {
4444
let start = indices[start];
4545
let end = indices[end];
46-
let distance = from(distance);
46+
let distance = distance.unsigned();
4747
distances[stride * start + end] = distance;
4848
distances[stride * end + start] = distance;
4949
}

src/year2020/day01.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
use crate::util::parse::*;
1717

1818
pub fn parse(input: &str) -> Vec<usize> {
19-
input.lines().map(from).collect()
19+
input.lines().map(|line| line.unsigned()).collect()
2020
}
2121

2222
pub fn part1(input: &[usize]) -> usize {

src/year2020/day02.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ pub struct Rule<'a> {
2121

2222
impl Rule<'_> {
2323
fn from([a, b, c, d]: [&str; 4]) -> Rule {
24-
let start = from(a);
25-
let end = from(b);
24+
let start = a.unsigned();
25+
let end = b.unsigned();
2626
let letter = c.as_bytes()[0];
2727
let password = d.as_bytes();
2828
Rule { start, end, letter, password }

src/year2020/day07.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ pub fn parse(input: &str) -> Haversack {
4343

4444
for (index, chunk) in tokens {
4545
let [amount, first_name, second_name, _] = chunk;
46-
let amount = from(amount);
46+
let amount = amount.unsigned();
4747
let next = indices[&[first_name, second_name]];
4848
bag[index] = Some(Rule { amount, next });
4949
}

src/year2020/day08.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@ pub enum Instruction {
2222

2323
impl Instruction {
2424
fn from([a, b]: [&str; 2]) -> Instruction {
25+
let amount = b.signed();
2526
match a {
26-
"acc" => Instruction::Acc(from(b)),
27-
"jmp" => Instruction::Jmp(from(b)),
28-
"nop" => Instruction::Nop(from(b)),
27+
"acc" => Instruction::Acc(amount),
28+
"jmp" => Instruction::Jmp(amount),
29+
"nop" => Instruction::Nop(amount),
2930
_ => unreachable!(),
3031
}
3132
}

src/year2020/day12.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::util::point::*;
99
type Command = (u8, i32);
1010

1111
pub fn parse(input: &str) -> Vec<Command> {
12-
input.lines().map(|line| (line.as_bytes()[0], from(&line[1..]))).collect()
12+
input.lines().map(|line| (line.as_bytes()[0], (&line[1..]).signed())).collect()
1313
}
1414

1515
pub fn part1(input: &[Command]) -> i32 {

src/year2020/day13.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ pub struct Input {
1717

1818
pub fn parse(input: &str) -> Input {
1919
let lines: Vec<_> = input.lines().collect();
20-
let timestamp = from(lines[0]);
20+
let timestamp = lines[0].unsigned();
2121
let buses: Vec<(usize, usize)> = lines[1]
2222
.split(',')
2323
.enumerate()
2424
.filter(|&(_, id)| id != "x")
25-
.map(|(offset, id)| (offset, from(id)))
25+
.map(|(offset, id)| (offset, id.unsigned()))
2626
.collect();
2727
Input { timestamp, buses }
2828
}

src/year2020/day14.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,8 @@ pub fn parse(input: &str) -> Vec<Instruction> {
133133
Instruction::mask(&line[7..])
134134
} else {
135135
let (address, value) = line[4..].split_once("] = ").unwrap();
136-
let address = from(address);
137-
let value = from(value);
136+
let address = address.unsigned();
137+
let value = value.unsigned();
138138
Instruction::Mem { address, value }
139139
};
140140
instructions.push(instruction);

src/year2020/day20.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ impl Tile {
7171
];
7272

7373
fn from(chunk: &[&str]) -> Tile {
74-
let id = from(&chunk[0][5..9]);
74+
let id = (&chunk[0][5..9]).unsigned();
7575

7676
let pixels: [[u8; 10]; 10] =
7777
std::array::from_fn(|i| chunk[i + 1].as_bytes().try_into().unwrap());

src/year2021/day01.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
use crate::util::parse::*;
2020

2121
pub fn parse(input: &str) -> Vec<u32> {
22-
input.lines().map(from).collect()
22+
input.iter_unsigned().collect()
2323
}
2424

2525
pub fn part1(input: &[u32]) -> usize {

src/year2021/day02.rs

+8-5
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,14 @@ pub enum Sub {
1616
}
1717

1818
pub fn parse(input: &str) -> Vec<Sub> {
19-
let helper = |[a, b]: [&str; 2]| match a {
20-
"up" => Sub::Up(from(b)),
21-
"down" => Sub::Down(from(b)),
22-
"forward" => Sub::Forward(from(b)),
23-
_ => unreachable!(),
19+
let helper = |[a, b]: [&str; 2]| {
20+
let amount = b.signed();
21+
match a {
22+
"up" => Sub::Up(amount),
23+
"down" => Sub::Down(amount),
24+
"forward" => Sub::Forward(amount),
25+
_ => unreachable!(),
26+
}
2427
};
2528
input.split_ascii_whitespace().chunk::<2>().map(helper).collect()
2629
}

src/year2021/day13.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ pub fn parse(input: &str) -> Input {
3434
let folds: Vec<_> = suffix
3535
.lines()
3636
.map(|line| match line.split_once('=').unwrap() {
37-
("fold along x", x) => Fold::Horizontal(from(x)),
38-
("fold along y", y) => Fold::Vertical(from(y)),
37+
("fold along x", x) => Fold::Horizontal(x.signed()),
38+
("fold along y", y) => Fold::Vertical(y.signed()),
3939
_ => unreachable!(),
4040
})
4141
.collect();

src/year2021/day24.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ pub fn parse(input: &str) -> Vec<Constraint> {
2828
.map(|chunk| {
2929
let helper = |i: usize| {
3030
let token = chunk[i].split_ascii_whitespace().last().unwrap();
31-
from(token)
31+
token.signed()
3232
};
3333
if helper(4) == 1 {
3434
Push(helper(15))

src/year2022/day01.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use crate::util::parse::*;
1414
/// Parse and group lines.
1515
pub fn parse(input: &str) -> Vec<u32> {
1616
let mut elves: Vec<u32> =
17-
input.split("\n\n").map(|s| s.lines().map(from::<u32>).sum()).collect();
17+
input.split("\n\n").map(|s| s.lines().map(|line| line.unsigned::<u32>()).sum()).collect();
1818
elves.sort_unstable();
1919
elves
2020
}

src/year2022/day07.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ pub fn parse(input: &str) -> Vec<u32> {
5656
} else if token == "cd" {
5757
cd = true;
5858
} else if token.as_bytes()[0].is_ascii_digit() {
59-
total += from::<u32>(token);
59+
total += token.unsigned::<u32>();
6060
}
6161
}
6262

src/year2022/day09.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ pub fn parse(input: &str) -> Vec<Input> {
2626
.chunk::<2>()
2727
.map(|[d, n]| {
2828
let point = Point::from_string(d);
29-
let amount = from(n);
29+
let amount = n.unsigned();
3030
(point, amount)
3131
})
3232
.collect()

src/year2022/day10.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ pub fn parse(input: &str) -> Vec<i32> {
1111
match token {
1212
"noop" => (),
1313
"addx" => (),
14-
delta => x += from::<i32>(delta),
14+
delta => x += delta.signed::<i32>(),
1515
}
1616
xs.push(x);
1717
}

src/year2022/day11.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ pub fn parse(input: &str) -> Vec<Monkey> {
6565
let tokens: Vec<&str> = chunk[2].split(' ').rev().take(2).collect();
6666
let operation = match tokens[..] {
6767
["old", _] => Operation::Square,
68-
[y, "*"] => Operation::Multiply(from(y)),
69-
[y, "+"] => Operation::Add(from(y)),
68+
[y, "*"] => Operation::Multiply(y.unsigned()),
69+
[y, "+"] => Operation::Add(y.unsigned()),
7070
_ => unreachable!(),
7171
};
7272
let test = chunk[3].iter_unsigned().next().unwrap();

0 commit comments

Comments
 (0)