Day six

On day six we start racing with boats. This is done by pressing a button and releasing it, making the boat travel at a certain speed.

Parsing

Parsing was quite easy for this day. You just have two lines you need to parse. We start by getting the lines and parsing per line. We do this as follows:

    let (_, times) = lines[0].split_once(": ").unwrap();
    let (_, records) = lines[1].split_once(": ").unwrap();
    
    let times: Vec<i64> = times.split_whitespace().map(|num| num.parse().expect("Couldn't parse times!")).collect();
    let records: Vec<i64> = records.split_whitespace().map(|num| num.parse().expect("Couldn't parse records!")).collect();

In this, we start by dropping the first part of the line, which is separated by a colon. After this, we end up with a string of numbers separated by spaces.

We do this for both lines and parse the numbers into a vec of i64. Finally, we create the races as a vec of tuples.

    for (index, time) in times.iter().enumerate() {
        races.push((*time, records[index]));
    }

This iterates over the times and keeps track of the index with .enumerate(). After that, we just push the time and the record at the same index as a tuple to the races vector.

Part one

For part one we need to multiply all the possibilities per race. The fastest way to calculate the possibilities to break the records is to go up to the first broken record and multiplying this by 2. We also need to add one because otherwise every answer will fall 1 short.

This full calculation is done in the following loop:

    for (time, record) in races {
        for press in 0..time {
            let distance = (time - press) * press;
            
            if distance > record {
                let possibility = (time - press * 2) + 1;
                answer *= possibility;
                break;
            }
        }
    }

Here we check every time from 0 to the max time. After this we calculate the distance traveled with distance = (time - press) * press. We check if the distance traveled is larger than the record, and if that is so we calculate the possibilities with (time - press * 2) + 1. After that, we multiply our current answer with the amount of possibilities and break out of the loop.

Part two

The next part is mostly a parsing challenge. We need to combine all the time numbers and the record numbers for one big race.

We can do this by iterating over evry number, converting it to a string and pushing it to a single string. This is done in the following snippet:

    let mut time: String = String::new();
    let mut record: String = String::new();
    
    for (times, records) in races {
        time.push_str(&times.to_string());
        record.push_str(&records.to_string());
    }

After that, we parse the time and record and just use the same algorithm as in the first part.

Conclusion

Today wasn't a really difficult challenge. The algorithm I created where you stop the calculations once you break a record was about 60% faster than testing every possible outcome, so that was a nice optimization.

Parsing also wasn't that difficult, so all things considered it was a somewhat easy day. If you want to play around with the code from day 6, take a look at this code block:

fn main() {
    let input_file = "\
Time:      7  15   30
Distance:  9  40  200";

    let answer_one = part_one(input_file);
    let answer_two = part_two(input_file);

    println!("Part one: {}\nPart two: {}", answer_one, answer_two);
}

fn part_one(input: &str) -> i64 {
    let races = parse(input);
    let mut answer = 1;
    
    for (time, record) in races {
        for press in 0..time {
            let distance = (time - press) * press;
            
            if distance > record {
                let possibility = (time - press * 2) + 1;
                answer *= possibility;
                break;
            }
        }
    }
    
    answer
}

fn part_two(input: &str) -> i64 {
    let races = parse(input);
    
    let mut time: String = String::new();
    let mut record: String = String::new();
    
    for (times, records) in races {
        time.push_str(&times.to_string());
        record.push_str(&records.to_string());
    }
    
    let mut possibility = 0;
    
    let time: i64 = time.parse().expect("Couldn't parse time!");
    let record: i64 = record.parse().expect("Couldn't parse time!");
    
    for press in 0..time {
        let distance = (time - press) * press;
        
        if distance > record {
            possibility = (time - press * 2) + 1;
            break;
        }
    }
    
    possibility
}

fn parse(file: &str) -> Vec<(i64, i64)> {
    let lines: Vec<&str> = file.lines().collect();
    let (_, times) = lines[0].split_once(": ").unwrap();
    let (_, records) = lines[1].split_once(": ").unwrap();
    
    let times: Vec<i64> = times.split_whitespace().map(|num| num.parse().expect("Couldn't parse times!")).collect();
    let records: Vec<i64> = records.split_whitespace().map(|num| num.parse().expect("Couldn't parse records!")).collect();
    
    let mut races: Vec<(i64, i64)> = vec![];
    
    for (index, time) in times.iter().enumerate() {
        races.push((*time, records[index]));
    }
    
    races
}