AoC 2019 Day 01: The Tyranny of the Rocket Equation

Dated Jan 8, 2020; last modified on Wed, 08 Jan 2020

Description

Link to challenge

The objective is to write a function that calculates the amount of fuel needed to propel a given mass. The question is interesting because the fuel has mass and therefore requires fuel too, and so forth.

My Solution

use std::fs::File;
use std::io::{self, BufRead};
use std::path::Path;

/// Returns the amount of fuel required to launch a module of mass `mass`. 
fn fuel_needed_for_mass(mass: i64) -> i64 {
    // You can return early from a function by using the `return` keyword and
    // specifying a value, but most functions return the last expression
    // implicitly. Note that expressions don't have semicolons.
    //
    // https://doc.rust-lang.org/book/ch03-03-how-functions-work.html

    // To find the fuel required for a module, take its mass, divide by three,
    // round down, and subtract 2.
    // 
    // I like how Rust warned me against using an unsigned int:
    // thread 'main' panicked at 'attempt to subtract with overflow'
    let fuel_mass = (mass / 3) - 2;

    // Can't do a one-line if-statement in Rust. Good to know.
    if fuel_mass <= 0 { return 0; }

    // The fuel also requires fuel...
    return fuel_mass + fuel_needed_for_mass(fuel_mass);
}

fn main() {
    assert_eq!(fuel_needed_for_mass(14), 2);
    assert_eq!(fuel_needed_for_mass(1969), 966);
    assert_eq!(fuel_needed_for_mass(100756), 50346);

    let mut total_fuel_needed: i64 = 0;
    // Functions return `Result` whenever errors are expected and recoverable.
    // `Ok(T)` represents success and contains a value, while `Err(E)`
    // represents an error and contains an error value.
    // https://doc.rust-lang.org/std/result/
    if let Ok(lines) = read_lines("./input01.txt") {
        for line in lines {
            // So much error handling. In Python, I couldn't even consider
            // adding this check. What could go wrong at this point?
            // Update: say the line is not valid UTF-8
            if let Ok(mass_str) = line {
                let mass = mass_str.parse::<i64>().unwrap();
                total_fuel_needed += fuel_needed_for_mass(mass);
            }
        }
    }
    // Macros look like functions, except that their name ends with
    // a bang !, but instead of generating a function call, macros
    // are expanded into source code that gets compiled with the
    // rest of the program.
    // https://doc.rust-lang.org/rust-by-example/macros.html
    // 
    // The string formatting is similar to that of Python
    // https://doc.rust-lang.org/std/fmt/index.html
    println!("Total fuel needed: {}", total_fuel_needed);
}

/// Returns an `Iterator` to the `Reader` of the lines of the file. The output
/// is wrapped in an `io::Result<T>` to make the failure of all I/O operations
/// explicit.
/// 
/// Source: https://doc.rust-lang.org/rust-by-example/std_misc/file/read_lines.html#read_lines
fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
where P: AsRef<Path>, {
    let file = File::open(filename)?;
    // From the docs: `BufReader<R>` can improve the speed of programs that make
    // small and repeated read calls to the same file. It does not help when
    // reading very large amounts at once, or reading just one or a few times.
    // It also provides no advantage when reading from a source that is already
    // in memory, like a `Vec<u8>`.
    Ok(io::BufReader::new(file).lines())
}