To learn more about Rust, I implemented Project Euler #11 "Numbers in a grid". Contrary to some people, I prefer not having the actual data grid in my code, so I put it in a file, which I read in the beginning.

I'm wondering how 'Rustic' my code is. Specially I wonder about the following points:

  • How to most concisely read a table of integers into a data structure? Is it possible to create an array of a certain size? Do I need all this spliting and from_str mapping?
  • Have I handled the array indices in the right way? I'm not used to needing all this casting.
  • I my max function sufficiently generic?

use std::io;
fn max(a:int, b:int) -> int {if a > b {a} else {b}}
fn main() {
    let n = 20;
    let mut ar:Vec<Vec<int>> = vec![];
    for line in io::stdin().lines() {
        let s = line.unwrap();
        let ss = s.as_slice().trim().split(' ');
        let xs:Vec<int> = ss.map(|s|from_str::<int>(s).unwrap()).collect();
    let mut best = 0;
    for i in range(0i,n) {
        for j in range(0i,n) {
            for &(di, dj) in [(0,1),(1,1),(1,0),(1,-1i)].iter() {
                if i+di*3 < n && j+dj*3 < n && j+dj*3 >= 0 {
                    let mut p = 1;
                    for k in range(0i,4) {
                        p *= ar[(i+di*k) as uint][(j+dj*k) as uint]
                    best = max(best, p);
    println!("{}", best);

Just like most people not actively involved in the language design, I'm still finding my feet with Rust as well, so take what I have to say with a large grain of salt.

You don't need to define your own max function: there is one already in the standard library, in std::cmp to be exact:

pub fn max<T: Ord>(v1: T, v2: T) -> T

This will work for anything that has an impl Ord, which int certainly does.

You can avoid the casts if you simply read everything in as a uint in the first place. In fact, it's probably a good idea to split up the code reading and parsing the file from the code that does the calculation. I'd write it something like:

use std::io;

fn read_grid(path: &str) -> Vec<Vec<uint>> {
    let path = Path::new(path);
    let mut file = io::BufferedReader::new(io::File::open(&path));
    let mut lines = file.lines().map(|x| x.unwrap());
    let mut grid: Vec<Vec<uint>> = vec![];

    for line in lines {
        let option = line.as_slice().split(' ').map(|x| -> Option<uint> from_str(x.trim()));
        grid.push(option.filter(|x| x.is_some()).map(|x| x.unwrap()).collect());

This will also skip over any non-convertible values (that is, anything that from_str(x.trim()) gives None from.

I don't doubt there are still much better ways of writing this, but that's about where I'm at for now.

|improve this answer|||||
  • \$\begingroup\$ I don't think I can use uint, as my dj can be negative. At least, if j is uint and dj is int, I'll have to write j+dj*3 someway different. I think. \$\endgroup\$ – Thomas Ahle Sep 29 '14 at 16:01
  • \$\begingroup\$ .map(|x| -> Option<uint> from_str(x.trim())).filter(|x| x.is_some()).map(|x| x.unwrap()) would be better as .filter_map(|x| from_str::<uint>(x.trim()). And once it’s done that way, the ::<uint> part can be inferred, so it can be dropped too. \$\endgroup\$ – Chris Morgan Oct 6 '14 at 2:56
  • \$\begingroup\$ The recommended style would also have use std::io::{BufferedReader, File};. \$\endgroup\$ – Chris Morgan Oct 6 '14 at 3:03
  • \$\begingroup\$ If you really wanted to mess with people, you could write the entire body of the function in one expression, BufferedReader::new(File::open(&Path::new(path))).lines().map(|x| x.unwrap()[].split(' ').filter_map(|x| from_str(x.trim())).collect()).collect()… (oh yeah, you can use [] where you used to have .as_slice()) \$\endgroup\$ – Chris Morgan Oct 6 '14 at 3:07
  • \$\begingroup\$ @ChrisMorgan You should write all of this up as an answer. \$\endgroup\$ – Yuushi Oct 6 '14 at 3:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.