My question has been partially answered, so I've revised it in response to things I've learned from comments and additional experiments.
In summary, I want a fast I/O routine for programming contests, in which problems are solved with a single file and no external crates. It should read in a sequence of whitespace-separated tokens from a BufRead
(either stdin or a file). The tokens may be integers, floats or ASCII words, separated by spaces and newlines, so it seems I should support FromStr
types generically. A small minority of problems are interactive, meaning not all of the input is available initially, but it always comes in complete lines.
For context, here's the discussion that led me to post here. Someone wrote very fast custom code to parse integers directly from the &[u8]
output of BufRead::fill_buf()
, but it's not generic in FromStr
.
Here is my best solution so far (emphasis on the Scanner
struct):
use std::io::{self, prelude::*};
fn solve<B: BufRead, W: Write>(mut scan: Scanner<B>, mut w: W) {
let n = scan.token();
let mut a = Vec::with_capacity(n);
let mut b = Vec::with_capacity(n);
for _ in 0..n {
a.push(scan.token::<i64>());
b.push(scan.token::<i64>());
}
let mut order: Vec<_> = (0..n).collect();
order.sort_by_key(|&i| b[i] - a[i]);
let ans: i64 = order
.into_iter()
.enumerate()
.map(|(i, x)| a[x] * i as i64 + b[x] * (n - 1 - i) as i64)
.sum();
writeln!(w, "{}", ans);
}
fn main() {
let stdin = io::stdin();
let stdout = io::stdout();
let reader = Scanner::new(stdin.lock());
let writer = io::BufWriter::new(stdout.lock());
solve(reader, writer);
}
pub struct Scanner<B> {
reader: B,
buf_str: String,
buf_iter: std::str::SplitWhitespace<'static>,
}
impl<B: BufRead> Scanner<B> {
pub fn new(reader: B) -> Self {
Self {
reader,
buf_str: String::new(),
buf_iter: "".split_whitespace(),
}
}
pub fn token<T: std::str::FromStr>(&mut self) -> T {
loop {
if let Some(token) = self.buf_iter.next() {
return token.parse().ok().expect("Failed parse");
}
self.buf_str.clear();
self.reader
.read_line(&mut self.buf_str)
.expect("Failed read");
self.buf_iter = unsafe { std::mem::transmute(self.buf_str.split_whitespace()) };
}
}
}
By avoiding unnecessary allocations, this Scanner
is quite fast. If we didn't care about unsafety, it can be made even faster by, instead of doing read_line()
into a String
, doing read_until(b'\n')
into a Vec<u8>
, followed by str::from_utf8_unchecked()
.
However, I'd also like to know what's the fastest safe solution. Is there a clever way to tell Rust that what my Scanner
implementation does is actually safe, eliminating the mem::transmute
? Intuitively, it seems we should think of the SplitWhitespace
object as owning the buffer until it's effectively dropped after it returns None
.
All else being equal, I'd like a "nice" idiomatic standard library solution, as I'm trying to present Rust to others who do programming contests.