Better rust starting code

Currently rust starter code for processing n lines goes:

use std::io;

macro_rules! parse_input {
    ($x:expr, $t:ident) => ($x.trim().parse::<$t>().unwrap())
}

fn main() {
    // ...
    let mut input_line = String::new();
    io::stdin().read_line(&mut input_line).unwrap();
    let n = parse_input!(input_line, i32);
    for i in 0..n as usize {
        let mut input_line = String::new();
        io::stdin().read_line(&mut input_line).unwrap();
        let line = input_line.trim_matches('\n').to_string();
    }
    // ...
}

Iā€™m pretty sure the trim and trim_matches are both just to remove the newline at the end, in which case, Iā€™m changing both to trim_end_matches. And why not refactor and use ty for type in the macro to allow more complex types while weā€™re at it?

use std::io;

macro_rules! read_line {
    ($t:ty) => {{
        let mut input_line = String::new();
        io::stdin().read_line(&mut input_line).unwrap();
        input_line.trim_end_matches('\n').parse::<$t>().unwrap()
    }};
}

fn main() {
    // ...
    let n = read_line!(i32);
    for i in 0..n as usize {
        let line = read_line!(String);
    }
    // ...
}
2 Likes

Oh, actually this might happen after a while which would basically do the same thing except without parse

1 Like

This is a great idea.
Starting code should be the most understandable, and simplifying it is a great idea!

Looking at Create Starter.rs by rb5014 Ā· Pull Request #7 Ā· CodinGame/FallChallenge2022-KeepOffTheGrass Ā· GitHub I realize thereā€™s also the situation of multiple inputs in one line like 13 4 106 239

EDIT: The previous code I wrote errors, hereā€™s a fix (full code)

use std::io;

fn read_line() -> String {
    let mut input_line = String::new();
    io::stdin().read_line(&mut input_line).unwrap();
    input_line.trim_end_matches('\n').to_owned()
}

macro_rules! parse_line {
    ($x:expr, $($i:ident: $t:ty),*) => {
        let mut inputs = $x.split(' ');
        $(
            let $i = inputs.next().unwrap().parse::<$t>().unwrap();
        )*
    }
}


fn main() {
    let inputs = read_line();
    parse_line!{inputs, width: i32, height: i32};

    // game loop
    loop {
        let inputs = read_line();
        parse_line!{inputs, my_matter: i32, opp_matter: i32};
        for i in 0..height as usize {
            for j in 0..width as usize {
                let inputs = read_line();
                parse_line!{inputs,
                    scrap_amount: i32,
                    owner: i32, // 1 = me, 0 = foe, -1 = neutral
                    units: i32,
                    recycler: i32,
                    can_build: i32,
                    can_spawn: i32,
                    in_range_of_recycler: i32
                };
            }
        }

        println!("WAIT");
    }
}

Wow, Rustā€™s hygienic macro system (tagging inputs as in a separate context from $x and $i) is so convenient.

Thereā€™s still some limitations, for example thereā€™s probably some way to allow trailing commas, and parsing a single value is inefficient because of an unnecessary split call (well that might be optimized away idk). parse_line!{inputs, something: i32}

Though looks like codingame handles non-split lines different so there could be custom code like
let something: i32 = inputs.parse().unwrap();

CodinGameā€™s stub generator (which generates the starting code) has to be flexible enough to be working for ALL programming languages that it supports. If your suggested code can fit into their generator well, I think they may consider adopting it :slight_smile:
https://www.codingame.com/playgrounds/40701/help-center/stub-generator

I think I gave everything except loop_line which for some reason doesnā€™t have an example in the link you gave. I think itā€™s just loop <amount> read <variable_sequence> in which case ez.

The macros have changed to read_line and parse_line see above for code.

The only other difference is

  • read <variable sequence>
    corresponds to let inputs = read_line(); and then parse_line!{inputs, scrap_amount: i32, owner: i32, units: i32, /* etc no trailing comma */}; An example is shown in my previous comment.
    This also works for cases where only one variable is read, since split doesnā€™t do anything. (parse_line!{inputs, one_var: i32}). (The types are as before). (Optimization below).
    The exception is read v:string(256) (the only input that can have spaces, and must be by itself), it corresponds to let v = read_line();

These are the same as before (I realize thereā€™s not really a way to remove that as usize based on the spec)

  • write <text>
    = println!(<text>);
  • gameloop
    = put everything afterwards in loop { <stuff> }
  • loop <amount> <command>
    = for let i in 0..<amount> as usize { <command> }

Regular comments are preprocessed away (documentation comments canā€™t document variables) so no worry about that.


Though the single input parse_line can be optimized by adding a special case:

macro_rules! parse_line {
    /* special case: */
    ($x:expr, $i:ident: $t:ty) => {
        let $i = $x.parse::<$t>().unwrap();
    };

    ($x:expr, $($i:ident: $t:ty),*) => {
        let mut inputs = $x.split(' ');
        $(
            let $i = inputs.next().unwrap().parse::<$t>().unwrap();
        )*
    }
}

this would also make let v = read_line(); equivalent to parse_line!{inputs, v: String}; (which might simplify the implementation) because inputs.parse::<String>() optimizes into a noop.


in the future read_line!() might be replaced with io::input_ln().unwrap(), see the second comment/rfc link above.

In the stub generator,
ā€¢ loop n read x: int is reading n lines, each line containing one value of x.
ā€¢ loopline n read x: int is reading one line, which contains n values of x, separated by spaces.

If you think your code fits, you may tag _CG_ClementHammel to have a look :slight_smile:

@_CG_ClementHammel

Top level code

use std::io;

// Previously this would be repeated every time
fn read_line() -> String {
    let mut input_line = String::new();
    io::stdin().read_line(&mut input_line).unwrap();
    input_line.trim_end_matches('\n').to_owned()
}

// I have no idea how booleans were previously handled,
// so here's a somewhat hacky solution,
// the braces are necessary to make {bool} not a type
macro_rules! parse_input {
    ($x:expr, {bool}) => {
        $x == "1"
    };

    ($x:expr, $t:ty) => {
        $x.parse::<$t>().unwrap()
    }
}

// Possibly unnecessary
macro_rules! read_parse {
    ($($i:ident: $t:tt),*) => {
        let input_line = read_line();
        let mut inputs = input_line.split(' ');
        $(
            let $i = parse_input!{inputs.next().unwrap(), $t};
        )*
    }
}

read v:string

let v = read_line();

Previously it was already a special case (input_line.trim_matches('\n').to_string()) so this is an improvement.

read <variable sequence>

Two examples:

/* read a:int */
read_parse!{a:i32};

/* read b:float c:long d:word(32) e:bool */
read_parse!{b:f32, c:i64, d:String, e:{bool}};

Types are the same, except bool; by default there would a ParseBoolError. An actual implementation would use more line breaks so that comments can be added.

read_parse feels a little bit extra. It expands to

let input_line = read_line();
let mut inputs = input_line.split(' ');
let b = parse_input!(inputs.next().unwrap(), f32);
let c = parse_input!(inputs.next().unwrap(), i64);
let d = parse_input!(inputs.next().unwrap(), String);
//etc

for speed, but if read_parse isnā€™t used then really the old version (+read_line) is better

let input_line = read_line();
let inputs = input_line.split(' ').collect::<Vec<_>>();
let b = parse_input!(inputs[0], f32);
let c = parse_input!(inputs[1], i64);
let d = parse_input!(inputs[2], String);
//etc

The old solution uses split(" ") but classically, this causes a clippy::perf::single_char_pattern lint which says to use split(' ') because itā€™s faster (50% faster but by nanoseconds).

let input_line = read_line(); canā€™t be inlined ugh

gameloop

same as before

loop { /* stuff */ }

loop <amount> <command>

same as before

for i in 0..v as usize {
    /* stuff */
}

loopline <amount> <variable sequence>

let inputs = read_line();

// prev
for i in inputs.split_whitespace() {
    let state = parse_input!(i, i32);
}

// now
for i in inputs.split(' ') {
    let state = parse_input!(i, i32);
}

write

same as before

another thing (Rust debug optimizations)

Separately, really want to mention thereā€™s a way to compile in optimized debug mode, with all the debug features!

Simply

[profile.dev]
opt-level = 3

in the Cargo.toml

https://doc.rust-lang.org/cargo/reference/profiles.html

Edit: Turns out itā€™s always in (non-optimized) debug mode even when submitting.

2 Likes

Of course for some reason coding game does not use cargoā€¦ I donā€™t understand why. Cargo is an incredible feature of rust.

they could also do with rust edition=ā€œ2021ā€, the latest version of rust, target-cpu=native, an extra library or ten like they have for python

I gave up in the last competition as I had to constantly butcher my pretty code to get it to run on coding game. 2021 and all the small recent changes are such massive improvements.

2 Likes