HiveBrain v1.2.0
Get Started
← Back to all entries
patternrustMinor

Spelling a word with chemical elements in Rust

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
elementsspellingwithwordchemicalrust

Problem

I tried solving this /r/dailyprogrammer challenge in Rust.

I'm a complete Rust beginner, and I'm sure I've made a lot of mistakes. I would be very thankful for any tips or help you might be able to provide.

Here are a couple of points that I found awkward/confusing:

-
In show_result, what I basically wanted to do was something like

symbols = spelling.map {|e| e.symbol}.join("")
elements = spelling.map {|e| e.name}.join(", ")


but that didn't seem to work.

  • I'm not exactly sure why I have to clone the elements to put them into the result vector. Can I/should I avoid this?



  • Coming from a dynamically typed garbage collected language, I had a hard time deciding when and where to use references.



Thank you very much!

```
extern crate csv;

use std::env;
use std::path::Path;
use std::collections::HashMap;
use std::option::Option;
use std::vec::Vec;

#[derive(Clone)]
struct Element {
number: u16,
symbol: String,
name: String
}

impl Element {
fn new (number: u16, symbol: &str, name: &str) -> Element {
Element {
number: number,
symbol: symbol.to_string(),
name: name.to_string()
}
}
}

// Read all elements from CSV file into a hash map
fn read_elements() -> HashMap {
let mut elem_map = HashMap::new();

let path = Path::new("./elements.csv");
let mut csv_reader = csv::Reader::from_file(path).unwrap();
for record in csv_reader.decode() {
let (number, symbol, name): (u16, String, String) = record.unwrap();
let symbol = symbol.trim();
let name = name.trim();
elem_map.insert(symbol.to_lowercase().to_string(), Element::new(number, symbol, name));
};

return elem_map;
}

// Try to spell target_word using elements from elem_map
fn spell_word(target_word: &str, elem_map: HashMap) -> Option> {
let mut result: Vec = Vec::new();
let target_word = target_word.to_lowercase().to_string();

// How far we've matched so far
let m

Solution

Overall

  • Don't explicitly import Vec or Option, they are part of the prelude.



  • Basically always want to implement Debug on any type.



  • I see two types that aren't there: something that holds the elements, something that contains results.



  • This allows you to implement Display for Spelling. This is more flexible and participates in the ecosystem better.



read_elements

  • No need to use Path explicitly, &str implements AsRef.



  • Can collect iterator of tuples into a HashMap, no need to muck about with a mutable variable.



  • Don't use unwrap. Prefer expect or return a Result. expect has words that will help you or the user find the problem. Note that we can collect into a Result.



  • Don't hardcode the filename inside the function.



spell_word

  • Should not take elem_map by value. Take a reference as we aren't making use of the allocated data.



  • I'd split the string and just track the string pieces, not the distance into the string.



  • No explicit return at the end of function.



  • Return references to the element instead of cloning.



show_result

  • Don't specify the types on a variable binding; let inference take care of it.



  • Don't take ownership of Vec, instead take a slice.



  • I'd avoid allocating in the printing function. I used Itertools to help with the comma separating.



main

  • Don't specify the type of a collection, let inference take care of it (let foo: Vec = bar.collect()).



  • Don't check for the argument and then implicitly check again (via indexing). A match or unwrap would be better.



  • No need to collect into a vector. Instead, skip the program name and just iterate over all the arguments - bonus feature!



  • No need to explicitly convert String to &str, a &String can be coerced to a &str.



  • Use raw strings to avoid escaping quotes



extern crate csv;
extern crate itertools;

use std::{env, fmt};
use std::collections::HashMap;

use itertools::Itertools;

type Error = Box;

#[derive(Debug, Clone)]
struct Element {
    number: u16,
    symbol: String,
    name: String
}

impl Element {
    fn new (number: u16, symbol: &str, name: &str) -> Element {
        Element {
            number: number,
            symbol: symbol.to_string(),
            name: name.to_string()
        }
    }
}

struct Elements {
    elem_map: HashMap
}

impl Elements {
    fn read_from_file(name: &str) -> Result {
        let mut csv_reader = csv::Reader::from_file(name)?;

        let elem_map: Result = csv_reader.decode().map(|record| {
            let (number, symbol, name): (u16, String, String) = record?;
            let symbol = symbol.trim();
            let name = name.trim();
            Ok((symbol.to_lowercase().to_string(), Element::new(number, symbol, name)))
        }).collect();

        Ok(Elements { elem_map: elem_map? })
    }

    // Try to spell target_word using elements from elem_map
    fn spell_word(&self, target_word: &str) -> Option {
        let mut result = Vec::new();
        let target_word = target_word.to_lowercase().to_string();
        let mut target_word = target_word.as_str();

        while !target_word.is_empty() {
            // Try matching the next two letters first
            if target_word.len() >= 2 {
                let (head, tail) = target_word.split_at(2);

                if let Some(elem) = self.elem_map.get(head) {
                    result.push(elem);
                    target_word = tail;
                    continue;
                }
            }

            // Try matching one letter if two letters failed
            let (head, tail) = target_word.split_at(1);

            if let Some(elem) = self.elem_map.get(head) {
                result.push(elem);
                target_word = tail;
                continue
            }

            // If no one letter element could be found,
            // this word cannot be spelled using elements
            return None;
        }

        Some(Spelling(result))
    }
}

struct Spelling(Vec);

impl fmt::Display for Spelling {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        for elem in &self.0 {
            write!(f, "{}", elem.symbol)?;
        }

        write!(f, " (")?;

        for elem in self.0.iter().map(|e| e.name.as_str()).intersperse(", ") {
            write!(f, "{}", elem)?;
        }

        write!(f, ")")
    }
}

fn main() {
    let elements = Elements::read_from_file("./elements.csv").expect("Unable to read elements");

    for target_word in env::args().skip(1) {
        match elements.spell_word(&target_word) {
            Some(spelling) => println!("{}", spelling),
            None => println!(r#"Could not find a match for word "{}"."#, target_word)
        }
    }
}

Code Snippets

extern crate csv;
extern crate itertools;

use std::{env, fmt};
use std::collections::HashMap;

use itertools::Itertools;

type Error = Box<std::error::Error>;

#[derive(Debug, Clone)]
struct Element {
    number: u16,
    symbol: String,
    name: String
}

impl Element {
    fn new (number: u16, symbol: &str, name: &str) -> Element {
        Element {
            number: number,
            symbol: symbol.to_string(),
            name: name.to_string()
        }
    }
}

struct Elements {
    elem_map: HashMap<String, Element>
}

impl Elements {
    fn read_from_file(name: &str) -> Result<Self, Error> {
        let mut csv_reader = csv::Reader::from_file(name)?;

        let elem_map: Result<_, Error> = csv_reader.decode().map(|record| {
            let (number, symbol, name): (u16, String, String) = record?;
            let symbol = symbol.trim();
            let name = name.trim();
            Ok((symbol.to_lowercase().to_string(), Element::new(number, symbol, name)))
        }).collect();

        Ok(Elements { elem_map: elem_map? })
    }

    // Try to spell target_word using elements from elem_map
    fn spell_word(&self, target_word: &str) -> Option<Spelling> {
        let mut result = Vec::new();
        let target_word = target_word.to_lowercase().to_string();
        let mut target_word = target_word.as_str();

        while !target_word.is_empty() {
            // Try matching the next two letters first
            if target_word.len() >= 2 {
                let (head, tail) = target_word.split_at(2);

                if let Some(elem) = self.elem_map.get(head) {
                    result.push(elem);
                    target_word = tail;
                    continue;
                }
            }

            // Try matching one letter if two letters failed
            let (head, tail) = target_word.split_at(1);

            if let Some(elem) = self.elem_map.get(head) {
                result.push(elem);
                target_word = tail;
                continue
            }

            // If no one letter element could be found,
            // this word cannot be spelled using elements
            return None;
        }

        Some(Spelling(result))
    }
}

struct Spelling<'a>(Vec<&'a Element>);

impl<'a> fmt::Display for Spelling<'a> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        for elem in &self.0 {
            write!(f, "{}", elem.symbol)?;
        }

        write!(f, " (")?;

        for elem in self.0.iter().map(|e| e.name.as_str()).intersperse(", ") {
            write!(f, "{}", elem)?;
        }

        write!(f, ")")
    }
}

fn main() {
    let elements = Elements::read_from_file("./elements.csv").expect("Unable to read elements");

    for target_word in env::args().skip(1) {
        match elements.spell_word(&target_word) {
            Some(spelling) => println!("{}", spelling),
            None => println!(r#"Could not find a match for word "{}"."#, target_word)
        }
    }
}

Context

StackExchange Code Review Q#154700, answer score: 3

Revisions (0)

No revisions yet.