14 votes

Day 6: Wait For It

Today's problem description: https://adventofcode.com/2023/day/6

Please post your solutions in your own top-level comment. Here's a template you can copy-paste into your comment to format it nicely, with the code collapsed by default inside an expandable section with syntax highlighting (you can replace python with any of the "short names" listed in this page of supported languages):

<details>
<summary>Part 1</summary>

```python
Your code here.
```

</details>

18 comments

  1. RheingoldRiver
    (edited )
    Link
    Well, that was a problem I guess Part 1 import json from copy import copy class Solver: def __init__(self): with open('info2.json', 'r', encoding='utf-8') as f: self.data = json.load(f) def...

    Well, that was a problem I guess

    Part 1
    import json
    from copy import copy
    
    
    class Solver:
    
        def __init__(self):
            with open('info2.json', 'r', encoding='utf-8') as f:
                self.data = json.load(f)
    
        def run(self):
            product = 1
            for race in self.data['races']:
                ways = 0
                for i in range(race['time']):
                    if i * (race['time'] - i) > race['distance']:
                        ways += 1
                product *= max(ways, 1)
            return product
    
    
    if __name__ == '__main__':
        print(Solver().run())
    
    Part 2 Surprise, same as part 1 but I changed info.json
    3 votes
  2. whs
    Link
    Glad today was easy. Today's language of the day is SQL, specifically PL/PGSQL. I never wrote a single function in SQL before, but I saw people use WITH and I thought it should be possible to...

    Glad today was easy. Today's language of the day is SQL, specifically PL/PGSQL. I never wrote a single function in SQL before, but I saw people use WITH and I thought it should be possible to solve AoC in one.

    By the way, this is my first solution this year that use a Regex - I did day 1-5 without a single regex.

    DROP FUNCTION IF EXISTS parse;
    DROP FUNCTION IF EXISTS solve1;
    DROP FUNCTION IF EXISTS solve1_arr;
    DROP TYPE IF EXISTS problem;
    
    CREATE TYPE problem AS
    (
        time     BIGINT,
        distance BIGINT
    );
    
    CREATE FUNCTION parse(input TEXT) RETURNS problem[] AS
    $$
    DECLARE
        inputTrim text   := trim(both E'\r\n' from input);
        lines     text[] := regexp_split_to_array(inputTrim, '\n');
        times     text[] := array(SELECT regexp_matches(lines[1], '\d+', 'g'));
        distances text[] := array(SELECT regexp_matches(lines[2], '\d+', 'g'));
        out       problem[];
        i         BIGINT;
    BEGIN
        FOR i IN 1..array_length(times, 1)
            LOOP
                out = array_append(out, (times[i][1], distances[i][1])::problem);
            END LOOP;
        RETURN out;
    END
    $$ LANGUAGE plpgsql;
    
    CREATE FUNCTION solve1(problem) RETURNS BIGINT AS
    $$
    DECLARE
        hold_duration           BIGINT;
        remaining_duration BIGINT;
        travel_distance BIGINT;
        winning_ways BIGINT := 0;
    BEGIN
        FOR hold_duration IN 1..($1.time-1)
        LOOP
            remaining_duration = $1.time - hold_duration;
            travel_distance = remaining_duration * hold_duration;
            IF travel_distance > $1.distance THEN
                winning_ways = winning_ways + 1;
            end if;
        END LOOP;
        RETURN winning_ways;
    END
    $$ LANGUAGE plpgsql;
    
    CREATE FUNCTION solve1_arr(problem[]) RETURNS BIGINT AS
    $$
    DECLARE
        acc BIGINT := 1;
        problem problem;
    BEGIN
        FOREACH problem IN ARRAY $1
        LOOP
            -- RAISE NOTICE '%', to_json(times);
            acc = acc * solve1(problem);
        END LOOP;
        RETURN acc;
    END
    $$ LANGUAGE plpgsql;
    
    -- Part 1
    -- SELECT solve1_arr((parse($input$
    -- Time:      7  15   30
    -- Distance:  9  40  200
    -- $input$)));
    -- SELECT solve1_arr((parse($input$
    -- Time:        46     85     75     82
    -- Distance:   208   1412   1257   1410
    -- $input$)));
    
    -- Part 2
    SELECT solve1_arr((parse($input$
    Time:        46857582
    Distance:   208141212571410
    $input$)));
    

    Apparently SQL array starts from 1. That wasted me an hour.

    For part 2 I'm too lazy to write a parser, so I just strip the number by hand. Takes 3s769ms to finish on my machine.

    3 votes
  3. updawg
    Link
    I'm a little surprised only one person seems to have even mentioned doing the math. I did Part 1 the code-heavy way to get some practice in with Counters, and I did Part 2 the math way because it...

    I'm a little surprised only one person seems to have even mentioned doing the math. I did Part 1 the code-heavy way to get some practice in with Counters, and I did Part 2 the math way because it would have just been copy+paste otherwise. I also parsed because I am learning so I'm trying to teach myself different things. I considered hard coding, but chose to do it the "hard" way. Although I'm now realizing that hard coding values would have let me use CONSTANTS instead of variables. How fun.

    Part 1
    """Advent of Code 2023 Day 6"""
    from collections import Counter
    
    with open("input.txt", 'r') as file:
        data = file.read().strip().split('\n')
    
    times, distances = ([int(j) for j in i.split() if j.isdigit()] for i in data)
    
    # Part 1
    t_C = Counter()
    
    for i in range(len(times)): # Iterating through each index in list of record times
        for j in range(times[i]): # Trying each amount of time holding the button
            time_held = speed = j # j is time held in ms and speed in units/ms
            time_left =  times[i] - time_held # Time left when releasing button
            distance_travelled = speed * time_left
            if distance_travelled > distances[i]:
                t_C[times[i]] += 1 # Add to counter for the given time if distance record is set
    part_1 = 1 # Multiply all counter values together...gotta initialize and gotta multiply so =1
    for i in t_C.values():
        part_1 *= i
    print(f"Part 1: {part_1}")
    
    #Part 2
    t = int("".join(str(x) for x in times))
    d = int("".join(str(y) for y in distances))
    
    Part 2
    # Answer is really x^2 - tx + d
    import math
    part_2 = math.floor(t + math.sqrt(t**2 - 4*d)/2) - math.floor(t - math.sqrt(t**2 - 4*d)/2)
    print(f"Part 2: {part_2}")
    
    3 votes
  4. tjf
    Link
    Suspiciously easy day, but I'm not complaining. Also I do like days where the input is short enough to enter manually into my source code while first solving. Anyway here are my Python solutions:...

    Suspiciously easy day, but I'm not complaining. Also I do like days where the input is short enough to enter manually into my source code while first solving. Anyway here are my Python solutions:

    Part 1
    #!/usr/bin/env pypy3
    
    product = 1
    for t, d in zip(*(map(int, line.split(':')[1].split()) for line in open(0))):
        product *= sum(x * t - x * x > d for x in range(1, t))
    print(product)
    
    Part 2 No change in computation for part two, only parsing the input:
    #!/usr/bin/env pypy3
    
    t, d = map(int, (line.split(':')[1].replace(' ', '') for line in open(0)))
    print(sum(x * t - x * x > d for x in range(1, t)))
    

    Are we in for something nasty tomorrow?

    2 votes
  5. asciipip
    Link
    My solution in Common Lisp. I almost had a sub-five-minute turnaround from part one to part two. But I implemented the closed form of the problem using sqrt and the single-float it was returning...

    My solution in Common Lisp.

    I almost had a sub-five-minute turnaround from part one to part two. But I implemented the closed form of the problem using sqrt and the single-float it was returning wasn't precise enough to get the right answer for part two. I spent a couple minutes double-checking the syntax to force it to return a double. (Solution: Pass it a double-float and it'll return one, too. To force its argument to a double, replace the 4 in one part of the inner calculation with 4d0 and let contagion do the rest.)

    2 votes
  6. [2]
    first-must-burn
    Link
    That seemed ... super easy? After yesterday I was expecting something much harder. I finished both parts in 20 minutes and was still in the 4ks on the leaderboard. I should have just copy-pasted...

    That seemed ... super easy? After yesterday I was expecting something much harder. I finished both parts in 20 minutes and was still in the 4ks on the leaderboard.

    I should have just copy-pasted the inputs into my code instead of messing around with parsing.

    Spoiler comments

    I suppose if you naively implemented the race as an actual simulation, instead of just computing the distance, maybe it would be harder? But even then, part 2 just scales linearly, so it was just ... change the input and rerun it?

    Github link

    Both Parts
    package main
    
    import (
    	h "aoc2023/internal/helpers"
    	"fmt"
    	"os"
    	"strconv"
    	"strings"
    )
    
    func main() {
    
    	d := &Day6{}
    
    	err := h.Dispatch(os.Args, d)
    	if err != nil {
    		fmt.Println("Error:", err)
    		os.Exit(1)
    	}
    
    }
    
    func RaceDistance(raceLength int, holdTime int) int {
    	return holdTime * (raceLength - holdTime)
    }
    
    func WaysToWin(raceLength int, record int) int {
    	winWays := 0
    	for i := 0; i < raceLength; i++ {
    		dist := RaceDistance(raceLength, i)
    		if dist > record {
    			winWays += 1
    		}
    	}
    	return winWays
    }
    
    type Day6 struct {
    	lines     []string
    	times     []int
    	distances []int
    }
    
    func (d *Day6) Setup(filename string) {
    	fmt.Println("Setup")
    	d.lines = h.ReadFileToLines(filename)
    
    	d.times = h.ParseIntList(strings.Split(d.lines[0], ":")[1], " ")
    	d.distances = h.ParseIntList(strings.Split(d.lines[1], ":")[1], " ")
    
    	fmt.Println("Times:", d.times)
    	fmt.Println("DIstances:", d.distances)
    }
    
    func (d *Day6) Part1() {
    	fmt.Println("Part 1")
    	waysProd := 1
    	for index := 0; index < len(d.times); index++ {
    		raceLength := d.times[index]
    		record := d.distances[index]
    		winWays := WaysToWin(raceLength, record)
    		fmt.Println("WinWays", winWays)
    		waysProd *= winWays
    	}
    	fmt.Println("WinWays product", waysProd)
    }
    
    func CombineVals(input string) int {
    	output := ""
    	for _, c := range input {
    		if c >= '0' && c <= '9' {
    			output += string(c)
    		}
    	}
    	intVal, err := strconv.Atoi(output)
    	h.Assert(err == nil, "error not nil")
    	return intVal
    }
    
    func (d *Day6) Part2() {
    	fmt.Println("Part 2")
    	raceLength := CombineVals(d.lines[0])
    	record := CombineVals(d.lines[1])
    	fmt.Println("raceLength", raceLength)
    	fmt.Println("record", record)
    
    	winWays := WaysToWin(raceLength, record)
    	fmt.Println("WinWays", winWays)
    }
    
    1 vote
    1. RheingoldRiver
      Link Parent
      yep that's what I did haha

      I should have just copy-pasted the inputs into my code instead of messing around with parsing.

      yep that's what I did haha

      2 votes
  7. lily
    Link
    I wasted time on parsing or I would've done better. Should've just copied the values straight into my code and I probably could've gotten a leaderboard spot. But oh well, can't win 'em all....

    I wasted time on parsing or I would've done better. Should've just copied the values straight into my code and I probably could've gotten a leaderboard spot. But oh well, can't win 'em all.

    Solution
    # Advent of Code 2023
    # Day 6: Wait For It
    
    from functools import reduce
    from operator import mul
    
    with open("inputs/day_6.txt") as file:
        line_1 = next(file)
        line_2 = next(file)
    
        races_part_1 = list(zip(
            (int(n) for n in line_1.split()[1:]),
            (int(n) for n in line_2.split()[1:])
        ))
    
        race_part_2 = (
            int("".join(line_1.split()[1:])),
            int("".join(line_2.split()[1:]))
        )
    
    def find_ways(race):
        ways = 0
    
        for hold_duration in range(1, race[0]):
            distance = (race[0] - hold_duration) * hold_duration
    
            if distance > race[1]:
                ways += 1
    
        return ways
    
    print(f"Part 1: {reduce(mul, (find_ways(race) for race in races_part_1))}")
    print(f"Part 2: {find_ways(race_part_2)}")
    
    1 vote
  8. tomf
    (edited )
    Link
    Google Sheets with the inputs in A2:A3. For part 1 I split it out into H Part 1 =ARRAYFORMULA( IF(F2=FALSE,, PRODUCT( BYCOL( H2:K2, LAMBDA( x, COUNTIFS(...

    Google Sheets

    with the inputs in A2:A3. For part 1 I split it out into H

    Part 1
    =ARRAYFORMULA(
      IF(F2=FALSE,,
       PRODUCT(
        BYCOL(
         H2:K2,
         LAMBDA(
          x,
          COUNTIFS(
           (x-SEQUENCE(100))*SEQUENCE(100),">"&OFFSET(x,1,0),
           SEQUENCE(100),"<="&x))))))
    
    Part 2
    =INT(
      SQRT(
       REGEXREPLACE(A2,"\D",)^2-
       4*REGEXREPLACE(A3,"\D",)))
    
    1 vote
  9. infinitesimal
    Link
    Nthing comments that this really would've been faster if you just manually typed the inputs and the loop in a REPL. Kotlin package aoc2023 object Day6 { fun parse(input: String): Pair<List<Int>,...

    Nthing comments that this really would've been faster if you just manually typed the inputs and the loop in a REPL.

    Kotlin
    package aoc2023
    
    object Day6 {
        fun parse(input: String): Pair<List<Int>, List<Int>> {
            fun intsOf(s: String) = Regex("""\d+""").findAll(s).map { it.value.toInt() }
            val (times, dists) = input.trim().lines().map { intsOf(it).toList() }.toList()
            return Pair(times, dists)
        }
    
        fun part1(input: String): Int {
            val (times, dists) = parse(input)
            fun wins(time0: Int, dist0: Int) = (0..time0).fold(0) { acc, time ->
                if (time * (time0 - time) > dist0) acc + 1 else acc
            }
            return times.indices.map { wins(times[it], dists[it]) }.reduce { x, y -> x * y }
        }
    
        fun part2(input: String): Long {
            val (times, dists) = parse(input)
            val time0 = times.joinToString("") { it.toString() }.toLong()
            val dist0 = dists.joinToString("") { it.toString() }.toLong()
            fun wins(time0: Long, dist0: Long) = (0..time0).fold(0L) { acc, time ->
                if (time * (time0 - time) > dist0) acc + 1 else acc
            }
            return wins(time0, dist0)
        }
    }
    
    1 vote
  10. pnutzh4x0r
    Link
    Language: Python Part 1 Not much to say... this was pretty straightforward. def race(charge_time: int, total_time: int) -> int: return charge_time * (total_time - charge_time) def...

    Language: Python

    Part 1

    Not much to say... this was pretty straightforward.

    def race(charge_time: int, total_time: int) -> int:
        return charge_time * (total_time - charge_time)
    
    def main(stream=sys.stdin) -> None:
        times     = [int(t) for t in stream.readline().split(':')[-1].split()]
        distances = [int(d) for d in stream.readline().split(':')[-1].split()]
        product   = 1
    
        for time, distance in zip(times, distances):
            ways     = [c for c in range(1, time + 1) if race(c, time) > distance]
            product *= len(ways)
    
        print(product)
    
    Part 2

    Probably could have done some math, but didn't need to :]

    def race(charge_time: int, total_time: int):
        return charge_time * (total_time - charge_time)
    
    def main(stream=sys.stdin) -> None:
        time     = int(''.join(stream.readline().split(':')[-1].split()))
        distance = int(''.join(stream.readline().split(':')[-1].split()))
        ways     = [c for c in range(1, time + 1) if race(c, time) > distance]
        print(len(ways))
    

    GitHub Repo

    1 vote
  11. DataWraith
    Link
    (Rust) Parser use nom::{ bytes::complete::tag, character::complete::{newline, space0, space1}, combinator::eof, multi::separated_list1, IResult, }; #[derive(Debug)] pub struct Race { pub time:...

    (Rust)

    Parser
    use nom::{
        bytes::complete::tag,
        character::complete::{newline, space0, space1},
        combinator::eof,
        multi::separated_list1,
        IResult,
    };
    
    #[derive(Debug)]
    pub struct Race {
        pub time: usize,
        pub distance: usize,
    }
    
    pub fn parse(input: &str) -> IResult<&str, Vec<Race>> {
        let (input, times) = parse_times(input)?;
        let (input, distances) = parse_distances(input)?;
        let (input, _) = eof(input)?;
    
        Ok((
            input,
            times
                .into_iter()
                .zip(distances)
                .map(|(t, d)| Race {
                    time: t,
                    distance: d,
                })
                .collect::<Vec<_>>(),
        ))
    }
    
    fn parse_times(input: &str) -> IResult<&str, Vec<usize>> {
        let (input, _) = tag("Time:")(input)?;
        let (input, _) = space1(input)?;
        let (input, times) = separated_list1(space1, parse_usize)(input)?;
        let (input, _) = newline(input)?;
    
        Ok((input, times))
    }
    
    fn parse_distances(input: &str) -> IResult<&str, Vec<usize>> {
        let (input, _) = tag("Distance:")(input)?;
        let (input, _) = space1(input)?;
        let (input, distances) = separated_list1(space1, parse_usize)(input)?;
        let (input, _) = newline(input)?;
        let (input, _) = space0(input)?;
    
        Ok((input, distances))
    }
    
    fn parse_usize(input: &str) -> IResult<&str, usize> {
        let (input, num) = nom::character::complete::digit1(input)?;
        let num = num.parse::<usize>().unwrap();
    
        Ok((input, num))
    }
    
    Parts 1 & 2
    mod parser;
    
    use parser::Race;
    
    fn main() {
        println!("Part 1: {}", part1(include_str!("../input.txt")));
        println!(
            "Part 2: {}",
            // I was lazy and just manually edited the input
            part2(
                "Time:        58996469
    Distance:   478223210191071
    "
            )
        );
    }
    
    fn part1(input: &str) -> usize {
        let races = parser::parse(input).unwrap().1;
    
        races
            .iter()
            .map(winning_race_strategies)
            .map(|(min, max)| max - min + 1)
            .product()
    }
    
    fn part2(input: &str) -> usize {
        part1(input)
    }
    
    fn boat_distance(race: &Race, button_held_for: usize) -> usize {
        let speed = button_held_for;
        let remaining_time = race.time - button_held_for;
    
        speed * remaining_time
    }
    
    fn bisect(race: &Race, lower: usize, upper: usize, win: bool) -> usize {
        if lower >= upper {
            return lower;
        }
    
        let lower_dist = boat_distance(race, lower);
        let upper_dist = boat_distance(race, upper);
        let lower_wins = lower_dist > race.distance;
        let upper_wins = upper_dist > race.distance;
    
        if upper_wins == win && lower_wins == win {
            return lower;
        }
    
        let mid = (lower + upper) / 2;
        let mid_dist = boat_distance(race, mid);
        let mid_wins = mid_dist > race.distance;
    
        if mid_wins == win {
            if lower_wins == win {
                return bisect(race, mid + 1, upper, win);
            } else {
                return bisect(race, lower + 1, mid, win);
            }
        }
    
        bisect(race, mid + 1, upper, win)
    }
    
    fn winning_race_strategies(race: &Race) -> (usize, usize) {
        let min_hold = bisect(race, 0, race.time, true);
        let max_hold = bisect(race, min_hold, race.time, false) - 1;
    
        (min_hold, max_hold)
    }
    

    This took an embarassingly long time to write though. Off-by-one errors in a bisection search are nasty...

    Performance

    Huh. After yesterday's problem it didn't even occur to me that I could just brute force this, so I wrote a recursive bisection search. 478223210191071 seemed like a scary-large number to brute-force, but I guess computers are fast...

    Benchmark 1: target/release/day-06
      Time (mean ± σ):       1.1 ms ±   0.1 ms    [User: 0.3 ms, System: 0.7 ms]
      Range (min … max):     0.4 ms …   1.9 ms    4090 runs
    
    1 vote
  12. Eabryt
    Link
    After all my issues with yesterdays code, today's was incredibly easy. I made it more complicated than it needed to be as I was anticipating some more issues. Part 2 was definitely a bit slow from...

    After all my issues with yesterdays code, today's was incredibly easy.

    I made it more complicated than it needed to be as I was anticipating some more issues. Part 2 was definitely a bit slow from a performance issue, but I'm talking 5-10s slow, not minutes.

    All my code
    import re
    
    
    class Race:
        def __init__(self, time, dist):
            self.time = time
            self.dist = dist
    
        def __repr__(self, options):
            return f'To win, hold the button for {[x for x in options]} second(s)'
    
        def best_options(self):
            options = []
            for x in range(self.time):
                if x * (self.time - x) > self.dist:
                    options.append(x)
            return options
    
    def part1(lines):
        print(f"Part 1!")
        lines = lines.strip().split('\n')
        times, dist = [re.findall(r'(\d+)', s) for s in lines]
        times = list(map(int, times))
        dist = list(map(int, dist))
        races = []
        for x in range(len(times)):
            races.append(Race(times[x], dist[x]))
        winners = 1
        for race in races:
            winners *= len(race.best_options())
        print(f"Result: {winners}")
    
    
    def part2(lines):
        print(f"Part 2!")
        lines = lines.strip().split('\n')
        times, dist = [re.findall(r'(\d+)', s) for s in lines]
        times = int(''.join(times))
        dist = int(''.join(dist))
        race = Race(times, dist)
        winners = len(race.best_options())
        print(f"Result: {winners}")
    
    
    def openFile():
        return open("input.txt", "r").read()
    
    
    def main():
        f = openFile()
        part1(f)
        part2(f)
    
    
    if __name__ == '__main__':
        main()
    
    1 vote
  13. jonah
    Link
    We have been blessed with an easy day. My code is a little more verbose than necessary, but I have some brain fog today so it was helpful to lay everything out more explicitly. Part 1 import {...

    We have been blessed with an easy day. My code is a little more verbose than necessary, but I have some brain fog today so it was helpful to lay everything out more explicitly.

    Part 1
    import { getInput } from "./input";
    import * as utils from "./utils";
    
    (async () => {
      const input = getInput();
      const lines = input.split("\n");
    
      const times = utils.ints(lines[0]);
      const distance = utils.ints(lines[1]);
    
      let total = 1;
    
      for (let i = 0; i < times.length; i++) {
        const time = times[i];
        const dist = distance[i];
        let wins = 0;
    
        for (let charge = 1; charge < time; charge++) {
          const rem = time - charge;
          if (charge * rem > dist) {
            wins++;
          }
        }
    
        total *= wins;
      }
    
      console.log(total);
    })();
    

    I kinda cheesed part two by doing a little bit of dumb processing to keep my previous solution exactly the same, so here's what that looks like:

    Part 2
    import { getInput } from "./input";
    import * as utils from "./utils";
    
    (async () => {
      const input = getInput();
      const lines = input.split("\n");
    
      const times = [
        parseInt(
          utils
            .ints(lines[0])
            .map((x) => x.toString())
            .join(""),
        ),
      ];
      const distance = [
        parseInt(
          utils
            .ints(lines[1])
            .map((x) => x.toString())
            .join(""),
        ),
      ];
    
      let total = 1;
    
      for (let i = 0; i < times.length; i++) {
        const time = times[i];
        const dist = distance[i];
        let wins = 0;
    
        for (let charge = 1; charge < time; charge++) {
          const rem = time - charge;
          if (charge * rem > dist) {
            wins++;
          }
        }
    
        total *= wins;
      }
    
      console.log(total);
    })();
    
    1 vote
  14. wycy
    Link
    Rust Day 6 use std::env; use std::io::{self, prelude::*}; use std::fs::File; fn solve(input: &str) -> io::Result<()> { // File handling let input_str = std::fs::read_to_string(input).unwrap(); let...

    Rust

    Day 6
    use std::env;
    use std::io::{self, prelude::*};
    use std::fs::File;
    
    fn solve(input: &str) -> io::Result<()> {
        // File handling
        let input_str = std::fs::read_to_string(input).unwrap();
        let input_str = input_str.trim();
        let input: Vec<_> = input_str.lines().collect();
    
        // Part 1 Inputs
        let times: Vec<_> = input[0]
            .split_whitespace()
            .skip(1)
            .map(|x| x.parse::<usize>().unwrap())
            .collect();
        let distances: Vec<_> = input[1]
            .split_whitespace()
            .skip(1)
            .map(|x| x.parse::<usize>().unwrap())
            .collect();
    
        // Part 1
        let mut part1 = 1;
        for (time,dist) in times.iter().zip(distances.iter()) {
            let ways = (1..*time)
                .into_iter()
                .filter(|hold| hold * (time - hold) > *dist)
                .count();
            if ways > 0 { part1 *= ways; }
        }
        println!("Part 1: {part1}"); // 800280
    
        // Part 2 Inputs
        let time_p2 = times
            .iter()
            .map(|x| format!("{x}"))
            .collect::<Vec<_>>()
            .concat()
            .parse::<usize>()
            .unwrap();
        let dist_p2 = distances
            .iter()
            .map(|x| format!("{x}"))
            .collect::<Vec<_>>()
            .concat()
            .parse::<usize>()
            .unwrap();
    
        // Part 2 Solve
        let part2 = (1..time_p2)
            .into_iter()
            .filter(|hold| hold * (time_p2 - hold) > dist_p2)
            .count();
        println!("Part 2: {part2}"); // 45128024
    
        Ok(())
    }
    
    fn main() {
        let args: Vec<String> = env::args().collect();
        let filename = &args[1];
        solve(&filename).unwrap();
    }
    
    1 vote
  15. Toric
    Link
    Originally 'brute forced' it (still only a couple thousand calculations even for part 2, under 20 ms in rust), but later remembered high school algebra, and basically the entire runtime of the...

    Originally 'brute forced' it (still only a couple thousand calculations even for part 2, under 20 ms in rust), but later remembered high school algebra, and basically the entire runtime of the solution is taken up by parsing the input.

    https://git.venberg.xyz/Gabe/advent_of_code_2023/src/branch/main/days/day06

    1 vote
  16. jzimbel
    Link
    Elixir Still playing catch-up, but this one was a nice break. I still did more work than necessary, though—I assumed part 2 would require optimization so I implemented a binary search with no...

    Elixir

    Still playing catch-up, but this one was a nice break. I still did more work than necessary, though—I assumed part 2 would require optimization so I implemented a binary search with no guardrails (since it's guaranteed to find a value for each race in the puzzle input) and used math to compute the total number of winners from the first winning button-hold value. Yay for me, I guess?

    Part 1 runs in ~12μs, part 2 in ~5.7μs. It takes longer to run multiple small searches than to run one big one! Thanks, O(log n)!.

    Parts 1 and 2
    defmodule AdventOfCode.Solution.Year2023.Day06 do
      import Integer, only: [is_even: 1]
    
      def part1(input) do
        input
        |> parse_p1()
        |> Enum.map(&count_wins/1)
        |> Enum.product()
      end
    
      def part2(input) do
        input
        |> parse_p2()
        |> count_wins()
      end
    
      defp parse_p1(input) do
        input
        |> String.split("\n", trim: true)
        |> Enum.map(fn line ->
          line
          |> String.split()
          |> Enum.drop(1)
          |> Enum.map(&String.to_integer/1)
        end)
        |> Enum.zip()
      end
    
      defp parse_p2(input) do
        input
        |> String.split("\n", trim: true)
        |> Enum.map(fn line ->
          line
          |> String.split(":")
          |> Enum.at(1)
          |> String.replace(" ", "")
          |> String.to_integer()
        end)
        |> List.to_tuple()
      end
    
      defp count_wins({t, d}) do
        half_t = div(t, 2)
        n = binary_search_first_win(1, half_t, t, d)
    
        if is_even(t), do: 2 * (half_t - n) + 1, else: 2 * (half_t - n + 1)
      end
    
      defp binary_search_first_win(n_min, n_max, t, d) do
        n = div(n_min + n_max, 2)
    
        with {:n_wins_race?, true} <- {:n_wins_race?, n * (t - n) > d},
             n_sub1 = n - 1,
             {:n_sub1_loses_race?, true} <- {:n_sub1_loses_race?, n_sub1 * (t - n_sub1) <= d} do
          n
        else
          {:n_wins_race?, false} -> binary_search_first_win(n + 1, n_max, t, d)
          {:n_sub1_loses_race?, false} -> binary_search_first_win(n_min, n - 1, t, d)
        end
      end
    end
    
    1 vote
  17. whispersilk
    Link
    Well, after day 5 this was refreshingly easy. Brute-force ran in ~4 ms even for part 2, but I wound up implementing a binary search that skips to linear searching when <10 elements are left...

    Well, after day 5 this was refreshingly easy. Brute-force ran in ~4 ms even for part 2, but I wound up implementing a binary search that skips to linear searching when <10 elements are left anyway. With that, runtime for parts 1 and 2 combined comes out around 12 μs, making today the fastest day by roughly an order of magnitude.

    Parser and data structure
    #[derive(Debug)]
    struct Race {
        time: usize,
        record: usize,
    }
    
    impl Race {
        fn num_solutions(&self) -> usize {
            let min = binary_search(0, self.time / 2 - 1, |val| {
                match (self.is_solution(val), self.is_solution(val + 1)) {
                    (true, true) => Ordering::Greater,
                    (false, false) => Ordering::Less,
                    (false, true) => Ordering::Equal,
                    (true, false) => unreachable!(),
                }
            });
            let max = binary_search(self.time / 2, self.time, |val| {
                match (self.is_solution(val), self.is_solution(val + 1)) {
                    (true, true) => Ordering::Less,
                    (false, false) => Ordering::Greater,
                    (true, false) => Ordering::Equal,
                    (false, true) => unreachable!(),
                }
            });
            let (max, min) = (max.expect("Max exists"), min.expect("Min exists"));
            max - min + 1
        }
    
        fn is_solution(&self, val: usize) -> bool {
            val * (self.time - val) > self.record
        }
    }
    
    impl From<(usize, usize)> for Race {
        fn from(val: (usize, usize)) -> Self {
            Self {
                time: val.0,
                record: val.1,
            }
        }
    }
    
    #[inline]
    fn binary_search<F>(mut bottom: usize, mut top: usize, cond: F) -> Option<usize>
    where
        F: Fn(usize) -> Ordering,
    {
        while bottom < top {
            if top - bottom < 10 {
                return (bottom..=top).find(|i| cond(*i) == Ordering::Equal);
            } else {
                let mid = (top + bottom) / 2;
                match cond(mid) {
                    Ordering::Less => bottom = mid - 1,
                    Ordering::Greater => top = mid + 1,
                    Ordering::Equal => return Some(mid),
                }
            }
        }
        None
    }
    
    fn parse_input(part: Puzzle) -> Result<Vec<Race>> {
        let input = load_input(6)?;
        let mut lines = input.lines();
        let times = lines
            .next()
            .and_then(|l| l.strip_prefix("Time:"))
            .ok_or("No line 1")?;
        let records = lines
            .next()
            .and_then(|l| l.strip_prefix("Distance:"))
            .ok_or("No line 2")?;
        match part {
            Puzzle::First => {
                let times = times
                    .split_whitespace()
                    .filter(|n| !n.is_empty())
                    .map(parse_as::<usize>)
                    .collect::<Result<Vec<usize>>>()?;
                let records = records
                    .split_whitespace()
                    .filter(|n| !n.is_empty())
                    .map(parse_as::<usize>)
                    .collect::<Result<Vec<usize>>>()?;
                Ok(times.into_iter().zip(records).map(Race::from).collect())
            }
            Puzzle::Second => {
                let time = parse_as::<usize>(&times.replace(' ', ""))?;
                let record = parse_as::<usize>(&records.replace(' ', ""))?;
                Ok(vec![(time, record).into()])
            }
        }
    }
    
    Part 1
    fn first() -> Result<String> {
        let races = parse_input(Puzzle::First)?;
        let total = races
            .into_iter()
            .map(|race| race.num_solutions())
            .product::<usize>()
            .to_string();
        Ok(total)
    }
    
    Part 2
    fn second() -> Result<String> {
        let race = parse_input(Puzzle::Second)?.remove(0);
        Ok(race.num_solutions().to_string())
    }
    
    1 vote