[–] 2 points 11 months ago (1 child)

Ohh so it was possible to use floats for this! I was "worried" that it would lead to precision errors haha so I ended up "cheating" with BigInt (Golang) to make all the multiplications first and one division at the end

  • source
  • parent
  • context
  • [–] 2 points 11 months ago (2 children)

    Python

    # pairwise helps picking consecutive values easier
    #   [A,B,C,D] -> [AB, BC, CD]
    # combinations will give me combinations of elements in a sequence
    #   [A,B,C,D] -> [AB, AC, AD, BC, BD, CD]
    from itertools import pairwise, combinations
    
    
    # line will pass thorough the center if the points are on opposite ends,
    #   i.e., total points / 2 distance away
    def part1(data: str, nail_count: int = 32):
        opposite_dist = nail_count // 2
        nodes = [int(n) for n in data.split(",")]
    
        center_passes = 0
        for a, b in pairwise(nodes):
            if abs(a - b) == opposite_dist:
                center_passes += 1
        return center_passes
    
    
    assert part1("1,5,2,6,8,4,1,7,3", 8) == 4
    
    
    # BRUTEFORCE: take every two points and check if they intersect
    def part2(data: str, nail_count: int = 32):
        def intersects(s1, s2):
            # expand the points
            (x1, x2), (y1, y2) = s1, s2
            # make sure x1 is the smaller nail and x2 is the bigger one
            if x1 > x2:
                x1, x2 = x2, x1
            # there is an intersection if one point lies bwtween x1 and x2
            #  and the other lies outside it
            if x1 < y1 < x2 and (y2 > x2 or y2 < x1):
                return True
            if x1 < y2 < x2 and (y1 > x2 or y1 < x1):
                return True
            return False
    
        nodes = [int(n) for n in data.split(",")]
        knots = 0
        for s1, s2 in combinations(pairwise(nodes), 2):
            if intersects(s1, s2):
                knots += 1
    
        return knots
    
    
    assert part2("1,5,2,6,8,4,1,7,3,5,7,8,2", 8) == 21
    
    from collections import defaultdict
    
    
    # This is better than bruteforce
    def part3(data: str, nail_count: int = 256):
        connections = defaultdict(list)
        nodes = [int(n) for n in data.split(",")]
    
        # record all connections, both ways
        for a, b in pairwise(nodes):
            connections[a].append(b)
            connections[b].append(a)
    
        max_cuts = 0
        for node_a in range(1, nail_count + 1):
            cuts = 0
            for node_b in range(node_a + 2, nail_count + 1):
                # add new cuts for the node we just added between a and b
                for other_node in connections[node_b - 1]:
                    if other_node > node_b or other_node < node_a:
                        cuts += 1
                # also add any cuts for threads that go between node a and b
                cuts += connections[node_a].count(node_b)
    
                # remove cuts that were going from BETWEEN node_a and node_b-1 (prev node_b) to node_b
                for other_node in connections[node_b]:
                    if node_a < other_node < node_b:
                        cuts -= 1
                # also remove any cuts for threads that go between node a and b-1
                cuts -= connections[node_a].count(node_b - 1)
    
                max_cuts = max(max_cuts, cuts)
    
        return max_cuts
    
    
    assert part3("1,5,2,6,8,4,1,7,3,6", 8) == 7
    
  • source
  • [–] 2 points 11 months ago

    Python

    Used sliding window for part 3. The off-by-one difference between i and j tripped me up for a while.

    def part1(data: str, mentor: str = 'A'):
        mentors = 0
        pairs = 0
        for p in data:
            if p == mentor:
                mentors += 1
            elif p == mentor.lower():
                pairs += mentors
        return pairs
    
    assert part1("ABabACacBCbca") == 5
    
    def part2(data: str):
        all_pairs = 0
        for mentor in 'ABC':
            all_pairs += part1(data, mentor)
        return all_pairs    
    
    assert part2("ABabACacBCbca") == 11
    
    from collections import defaultdict
    
    def part3(data: str, distance_limit: int = 1000, repeat: int = 1000):
        n = len(data)
        N = len(data) * repeat
    
        pairs = 0
        person_count = defaultdict(int)
        curr = 0
    
        # initialize the first window excluding the right boundary
        for j in range(distance_limit):
            person_count[data[j % n]] += 1
    
        for curr in range(N):
            # move left boundary (if applicable)
            if (i := curr - distance_limit - 1) >= 0:
                person_count[data[i % n]] -= 1
            # move right boundary (if applicable)
            if (j := curr + distance_limit) < N:
                person_count[data[j % n]] += 1
            # if mentee, record pairs
            if data[curr % n].islower():
                pairs += person_count[data[curr % n].upper()]
    
        return pairs
        
    
    assert (t := part3("AABCBABCABCabcabcABCCBAACBCa", 10, 1)) == 34, f"Expected 34 but got {t}"
    assert (t := part3("AABCBABCABCabcabcABCCBAACBCa", 10, 2)) == 72, f"Expected 72 but got {t}"
    assert (t := part3("AABCBABCABCabcabcABCCBAACBCa", 1000, 1000)) == 3442321, f"Expected 3442321 but got {t}"-
    
  • source
  • [–] [S] 2 points 11 months ago

    Rust

    use std::collections::{HashMap, HashSet};
    
    use itertools::Itertools;
    
    fn explode_from_barrel(
        map: &HashMap<(isize, isize), i8>,
        mut exploded: HashSet<(isize, isize)>,
        mut front: HashSet<(isize, isize)>,
    ) -> HashSet<(isize, isize)> {
        while !front.is_empty() {
            let mut next_front = HashSet::new();
            for (i, j) in front.drain() {
                exploded.insert((i, j));
                let my_size = map[&(i, j)];
                for (di, dj) in [(-1, 0), (0, -1), (0, 1), (1, 0)] {
                    let (ni, nj) = (i + di, j + dj);
                    if exploded.contains(&(ni, nj)) {
                        continue;
                    }
                    if let Some(neighour_size) = map.get(&(ni, nj))
                        && *neighour_size <= my_size
                    {
                        next_front.insert((ni, nj));
                    }
                }
            }
            front = next_front;
        }
        exploded
    }
    
    pub fn solve_part_1(input: &str) -> String {
        let map: HashMap<(isize, isize), i8> = input
            .lines()
            .enumerate()
            .flat_map(|(i, line)| {
                line.chars()
                    .enumerate()
                    .map(move |(j, ch)| ((i as isize, j as isize), ch as i8 - '0' as i8))
            })
            .collect();
        let exploded = HashSet::<(isize, isize)>::new();
        let front: HashSet<(isize, isize)> = [(0, 0)].into_iter().collect();
        explode_from_barrel(&map, exploded, front).len().to_string()
    }
    
    pub fn solve_part_2(input: &str) -> String {
        let map: HashMap<(isize, isize), i8> = input
            .lines()
            .enumerate()
            .flat_map(|(i, line)| {
                line.chars()
                    .enumerate()
                    .map(move |(j, ch)| ((i as isize, j as isize), ch as i8 - '0' as i8))
            })
            .collect();
        let exploded = HashSet::<(isize, isize)>::new();
        let max_i = map.keys().map(|(i, _)| *i).max().unwrap();
        let max_j = map.keys().map(|(_, j)| *j).max().unwrap();
        let front: HashSet<(isize, isize)> = [(0, 0), (max_i, max_j)].into_iter().collect();
        explode_from_barrel(&map, exploded, front).len().to_string()
    }
    
    pub fn solve_part_3(input: &str) -> String {
        let map: HashMap<(isize, isize), i8> = input
            .lines()
            .enumerate()
            .flat_map(|(i, line)| {
                line.chars()
                    .enumerate()
                    .map(move |(j, ch)| ((i as isize, j as isize), ch as i8 - '0' as i8))
            })
            .collect();
        let max_i = map.keys().map(|(i, _)| *i).max().unwrap();
        let max_j = map.keys().map(|(_, j)| *j).max().unwrap();
        let best_barrel = (0..=max_i)
            .cartesian_product(0..=max_j)
            .map(|(i, j)| {
                ((i, j), {
                    let exploded = HashSet::<(isize, isize)>::new();
                    let front: HashSet<(isize, isize)> = [(i, j)].into_iter().collect();
                    explode_from_barrel(&map, exploded, front)
                })
            })
            .max_by_key(|(_, exploded)| exploded.len())
            .unwrap();
        let second_best_barrel = (0..=max_i)
            .cartesian_product(0..=max_j)
            .filter(|&(i, j)| !best_barrel.1.contains(&(i, j)))
            .map(|(i, j)| {
                ((i, j), {
                    let exploded = best_barrel.1.clone();
                    let front: HashSet<(isize, isize)> = [(i, j)].into_iter().collect();
                    explode_from_barrel(&map, exploded, front)
                })
            })
            .max_by_key(|(_, exploded)| exploded.len())
            .unwrap();
        let third_best_barrel = (0..=max_i)
            .cartesian_product(0..=max_j)
            .filter(|&(i, j)| !second_best_barrel.1.contains(&(i, j)))
            .map(|(i, j)| {
                ((i, j), {
                    let exploded = second_best_barrel.1.clone();
                    let front: HashSet<(isize, isize)> = [(i, j)].into_iter().collect();
                    explode_from_barrel(&map, exploded, front)
                })
            })
            .max_by_key(|(_, exploded)| exploded.len())
            .unwrap();
        let exploded = HashSet::<(isize, isize)>::new();
        let front: HashSet<(isize, isize)> = [best_barrel.0, second_best_barrel.0, third_best_barrel.0]
            .into_iter()
            .collect();
        explode_from_barrel(&map, exploded, front).len().to_string()
    }
    
  • source
  • [–] [S] 1 point 11 months ago

    Rust

    pub fn solve_part_1(input: &str) -> String {
        let numbers = input
            .lines()
            .map(|l| l.parse().unwrap())
            .collect::<Vec<i64>>();
        let offset = 2025 % (numbers.len() + 1);
        if offset == 0 {
            1
        } else if offset > numbers.len().div_ceil(2) {
            numbers[(numbers.len() - offset) * 2 + 1]
        } else {
            numbers[(offset - 1) * 2]
        }
        .to_string()
    }
    
    fn find_number(ranges: &[(i64, i64)], mut offset: i64, counterclockwise: bool) -> i64 {
        for (from, to) in ranges {
            let segment_size = (to - from) + 1;
            if offset >= segment_size {
                offset -= segment_size;
                continue;
            }
            return if counterclockwise {
                to - offset
            } else {
                from + offset
            };
        }
        panic!("find_number gave up and died");
    }
    
    fn solve_part_2_with_turns(input: &str, turns: i64) -> String {
        let ranges = input
            .lines()
            .map(|l| {
                let (l, r) = l.split_once("-").unwrap();
                (l.parse().unwrap(), r.parse().unwrap())
            })
            .collect::<Vec<(i64, i64)>>();
        let mut clockwise_length = 0;
        let mut clockwise_ranges = vec![];
        let mut counterclockwise_length = 0;
        let mut counterclockwise_ranges = vec![];
        for (i, (from, to)) in ranges.into_iter().enumerate() {
            if i % 2 == 0 {
                clockwise_length += to - from + 1;
                clockwise_ranges.push((from, to));
            } else {
                counterclockwise_length += to - from + 1;
                counterclockwise_ranges.push((from, to));
            }
        }
        counterclockwise_ranges.reverse();
        let offset = turns % (clockwise_length + counterclockwise_length + 1);
        if offset == 0 {
            1
        } else if offset > clockwise_length {
            find_number(
                &counterclockwise_ranges,
                offset - clockwise_length - 1,
                true,
            )
        } else {
            find_number(&clockwise_ranges, offset - 1, false)
        }
        .to_string()
    }
    
    pub fn solve_part_2(input: &str) -> String {
        solve_part_2_with_turns(input, 20252025)
    }
    
    pub fn solve_part_3(input: &str) -> String {
        solve_part_2_with_turns(input, 202520252025)
    }
    
    
  • source
  • [–] [S] 2 points 11 months ago

    Rust

    Finally caught up with the puzzles.

    use std::collections::{HashMap, HashSet};
    
    use itertools::Itertools;
    
    pub fn solve_part_1(input: &str) -> String {
        let mut barrels: HashSet<(isize, isize)> = input
            .lines()
            .enumerate()
            .flat_map(|(i, line)| {
                line.chars()
                    .enumerate()
                    .filter(|&(_, ch)| ch == '#')
                    .map(move |(j, _)| (i as isize, j as isize))
            })
            .collect();
        let max_i = barrels.iter().map(|(i, _)| *i).max().unwrap();
        let max_j = barrels.iter().map(|(_, j)| *j).max().unwrap();
        let mut total_active = 0;
        for _ in 0..10 {
            let mut next_barrels = HashSet::new();
            for i in 0..=max_i {
                for j in 0..=max_j {
                    let active_neighbours = [(-1, -1), (1, 1), (-1, 1), (1, -1)]
                        .iter()
                        .filter(|(di, dj)| barrels.contains(&(i + di, j + dj)))
                        .count();
                    let will_be_active = if barrels.contains(&(i, j)) {
                        active_neighbours % 2 == 1
                    } else {
                        active_neighbours % 2 == 0
                    };
                    if will_be_active {
                        next_barrels.insert((i, j));
                    }
                }
            }
            barrels = next_barrels;
            total_active += barrels.len();
        }
        total_active.to_string()
    }
    
    pub fn solve_part_2(input: &str) -> String {
        let mut barrels: HashSet<(isize, isize)> = input
            .lines()
            .enumerate()
            .flat_map(|(i, line)| {
                line.chars()
                    .enumerate()
                    .filter(|&(_, ch)| ch == '#')
                    .map(move |(j, _)| (i as isize, j as isize))
            })
            .collect();
        let max_i = barrels.iter().map(|(i, _)| *i).max().unwrap();
        let max_j = barrels.iter().map(|(_, j)| *j).max().unwrap();
        let mut total_active = 0;
        for _ in 0..2025 {
            let mut next_barrels = HashSet::new();
            for i in 0..=max_i {
                for j in 0..=max_j {
                    let active_neighbours = [(-1, -1), (1, 1), (-1, 1), (1, -1)]
                        .iter()
                        .filter(|(di, dj)| barrels.contains(&(i + di, j + dj)))
                        .count();
                    let will_be_active = if barrels.contains(&(i, j)) {
                        active_neighbours % 2 == 1
                    } else {
                        active_neighbours % 2 == 0
                    };
                    if will_be_active {
                        next_barrels.insert((i, j));
                    }
                }
            }
            barrels = next_barrels;
            total_active += barrels.len();
        }
        total_active.to_string()
    }
    
    pub fn solve_part_3(input: &str) -> String {
        let pattern: HashSet<(isize, isize)> = input
            .lines()
            .enumerate()
            .flat_map(|(i, line)| {
                line.chars()
                    .enumerate()
                    .filter(|&(_, ch)| ch == '#')
                    .map(move |(j, _)| (i as isize, j as isize))
            })
            .collect();
        let pattern_max_i = pattern.iter().map(|(i, _)| *i).max().unwrap();
        let pattern_max_j = pattern.iter().map(|(_, j)| *j).max().unwrap();
        assert_eq!(7, pattern_max_i);
        assert_eq!(7, pattern_max_j);
        let mut barrels: HashSet<(isize, isize)> = HashSet::new();
        let max_i = 33;
        let max_j = 33;
        let mut state_map = HashMap::<Vec<(isize, isize)>, usize>::new();
        state_map.insert(vec![], 0);
        let mut current_round = 0;
        let rounds_to_simulate = 1000000000;
        let mut active_tiles_after_rounds = vec![0];
        while current_round < rounds_to_simulate {
            let mut next_barrels = HashSet::new();
            for i in 0..=max_i {
                for j in 0..=max_j {
                    let active_neighbours = [(-1, -1), (1, 1), (-1, 1), (1, -1)]
                        .iter()
                        .filter(|(di, dj)| barrels.contains(&(i + di, j + dj)))
                        .count();
                    let will_be_active = if barrels.contains(&(i, j)) {
                        active_neighbours % 2 == 1
                    } else {
                        active_neighbours % 2 == 0
                    };
                    if will_be_active {
                        next_barrels.insert((i, j));
                    }
                }
            }
            barrels = next_barrels;
            current_round += 1;
            let state_key: Vec<_> = barrels.iter().copied().collect();
            if let Some(&seen_before_after_round) = state_map.get(&state_key) {
                let loop_length = current_round - seen_before_after_round;
                let loops_remaining = (rounds_to_simulate - current_round) / loop_length;
                let iterations_remaining =
                    rounds_to_simulate - current_round - (loops_remaining * loop_length);
                return (active_tiles_after_rounds[0..current_round]
                    .iter()
                    .sum::<usize>()
                    + active_tiles_after_rounds[seen_before_after_round..current_round]
                        .iter()
                        .sum::<usize>()
                        * loops_remaining
                    + active_tiles_after_rounds
                        [seen_before_after_round..(seen_before_after_round + iterations_remaining)]
                        .iter()
                        .sum::<usize>())
                .to_string();
            }
            state_map.insert(state_key, current_round);
            let does_pattern_match = (13..=20)
                .cartesian_product(13..=20)
                .all(|(i, j)| barrels.contains(&(i, j)) == pattern.contains(&(i - 13, j - 13)));
            active_tiles_after_rounds.push(if does_pattern_match { barrels.len() } else { 0 });
        }
        active_tiles_after_rounds.iter().sum::<usize>().to_string()
    }
    
  • source
  • [–] 2 points 11 months ago (2 children)

    Python

    # snipped read_input implementation
    data = read_input("input_p3.txt")
    
    # part 1 and 2 can be solved using a calculator 
    # with only the first and last gears
    
    def part3(data):
        shafts = data.splitlines()
    
        rotations = 100 * int(shafts[0])
        for shaft in shafts[1:-1]:
            gear1, gear2 = [int(g) for g in shaft.split('|')]
            rotations *= gear2 / gear1
        rotations /= int(shafts[-1])
        
        return int(rotations)
    
    assert part3("""5
    7|21
    18|36
    27|27
    10|50
    10|50
    11""") == 6818
    
    print(part3(data))
    
  • source
  • [–] [S] 1 point 11 months ago

    Rust

    I hate when you need to look at the data to solve a simpler subclass of the problem.

    use num::Integer;
    
    fn one_round(columns: &mut [i64], forward: bool) -> bool {
        let mut modified = false;
        for i in 1..columns.len() {
            if forward {
                if columns[i - 1] > columns[i] {
                    columns[i - 1] -= 1;
                    columns[i] += 1;
                    modified = true;
                }
            } else if columns[i - 1] < columns[i] {
                columns[i - 1] += 1;
                columns[i] -= 1;
                modified = true;
            }
        }
        modified
    }
    
    pub fn solve_part_1(input: &str) -> String {
        let mut columns = input
            .lines()
            .map(|l| l.parse().unwrap())
            .collect::<Vec<i64>>();
        let mut rounds_remaining = 10;
        while rounds_remaining > 0 {
            if one_round(&mut columns, true) {
                rounds_remaining -= 1;
            } else {
                break;
            }
        }
        while rounds_remaining > 0 {
            if one_round(&mut columns, false) {
                rounds_remaining -= 1;
            } else {
                break;
            }
        }
        columns
            .iter()
            .enumerate()
            .map(|(column_idx, count)| (column_idx + 1) as i64 * *count)
            .sum::<i64>()
            .to_string()
    }
    
    pub fn solve_part_2(input: &str) -> String {
        let mut columns = input
            .lines()
            .map(|l| l.parse().unwrap())
            .collect::<Vec<i64>>();
        let mut total_rounds = 0;
        loop {
            if one_round(&mut columns, true) {
                total_rounds += 1;
            } else {
                break;
            }
        }
        loop {
            if one_round(&mut columns, false) {
                total_rounds += 1;
            } else {
                break;
            }
        }
        total_rounds.to_string()
    }
    
    pub fn solve_part_3(input: &str) -> String {
        let mut columns = input
            .lines()
            .map(|l| l.parse().unwrap())
            .collect::<Vec<i64>>();
        let invariant_sum = columns.iter().sum::<i64>();
        let mut total_rounds = 0i64;
        loop {
            let first_gap = (0..columns.len() - 1).find(|&i| columns[i].abs_diff(columns[i + 1]) > 1);
            let last_gap = (0..columns.len() - 1)
                .filter(|&i| columns[i].abs_diff(columns[i + 1]) > 1)
                .next_back();
            if let (Some(first_gap), Some(last_gap)) = (first_gap, last_gap)
                && (last_gap > first_gap)
            {
                let amount_to_fill: i64 = columns
                    .iter()
                    .take(first_gap + 1)
                    .map(|&x| columns[first_gap + 1] - x)
                    .sum();
                let amount_available: i64 = columns
                    .iter()
                    .skip(last_gap + 1)
                    .map(|&x| x - columns[last_gap])
                    .sum();
                let amount_to_transfer = amount_to_fill.min(amount_available);
                let new_amount_left: i64 =
                    columns.iter().take(first_gap + 1).sum::<i64>() + amount_to_transfer;
                let (left_base, remainder) = new_amount_left.div_rem(&((first_gap + 1) as i64));
                for (i, new_value) in columns.iter_mut().enumerate().take(first_gap + 1) {
                    *new_value = left_base
                        + if first_gap - i < remainder as usize {
                            1
                        } else {
                            0
                        };
                }
                let new_amount_right: i64 =
                    columns.iter().skip(last_gap + 1).sum::<i64>() - amount_to_transfer;
                let (right_base, remainder) =
                    new_amount_right.div_rem(&((columns.len() - last_gap - 1) as i64));
                for i in last_gap + 1..columns.len() {
                    columns[i] = right_base
                        + if columns.len() - i <= remainder as usize {
                            1
                        } else {
                            0
                        };
                }
                assert_eq!(invariant_sum, columns.iter().sum::<i64>());
                total_rounds += amount_to_transfer;
            } else {
                break;
            }
        }
        loop {
            if one_round(&mut columns, false) {
                total_rounds += 1;
            } else {
                break;
            }
        }
        total_rounds.to_string()
    }
    
  • source
  • [–] 2 points 11 months ago

    Scheme/Guile

    I was stuck on part 3 for a while, for not taking account that a child scale that I'm evaluating may already be a parent scale in some group.

    (import (rnrs io ports (6))
            (srfi srfi-1))
    #!curly-infix
    
    (define (parse-file file-name)
      (let* ((lines (string-split (string-trim-both (call-with-input-file file-name get-string-all)) #\newline))
             (split-lines (map (lambda (l) (string-split l #\:)) lines))
             (parsed-lines (map (lambda (l) (cons (string->number (car l)) (string->list (cadr l)))) split-lines)))
        parsed-lines))
    
    (define (child-score child p1 p2 p1-sim p2-sim)
      (if (and-map null? (list child p1 p2))
          (* p1-sim p2-sim)
          (let ((matches-p1 (eq? (car child) (car p1)))
                (matches-p2 (eq? (car child) (car p2))))
            (cond
              ((not (or matches-p1 matches-p2)) #f)
              (else (child-score (cdr child) (cdr p1) (cdr p2) (+ p1-sim (if matches-p1 1 0)) (+ p2-sim (if matches-p2 1 0))))))))
    (let ((dna-lines (parse-file "notes/everybody_codes_e2025_q09_p1.txt")))
      (format #t "P1 Answer: ~a\n\n" (or
        (child-score (cdar dna-lines) (cdadr dna-lines) (cdaddr dna-lines) 0 0)
        (child-score (cdadr dna-lines) (cdar dna-lines) (cdaddr dna-lines) 0 0)
        (child-score (cdaddr dna-lines) (cdadr dna-lines) (cdar dna-lines) 0 0))))
    
    
    (let ((dna-lines (list->vector (parse-file "notes/everybody_codes_e2025_q09_p2.txt"))))
      (let loop ((child 0) (total-sim 0))
        (if {child < (vector-length dna-lines)}
            (loop (1+ child) (+ total-sim (let loop ((i 0))
              (cond
                ((eq? i child) (loop (1+ i)))
                ({i >= {(vector-length dna-lines) - 1}} 0)
                (else
                  (or
                    (let loop ((j (1+ i)))
                      (cond
                        ((eq? j child) (loop (1+ j)))
                        ({j >= (vector-length dna-lines)} #f)
                        (else (let ((res (child-score
                                   (cdr (vector-ref dna-lines child))
                                   (cdr (vector-ref dna-lines i))
                                   (cdr (vector-ref dna-lines j)) 0 0)))
                          (or res (loop (1+ j)))))))
                    (loop (1+ i))))))))
            (format #t "P2 Answer: ~a\n\n" total-sim))))
    
    
    (define (init-id-to-group dna-lines)
      (let ((table (make-hash-table)))
        (let loop ((i 0))
          (if {i < (vector-length dna-lines)}
              (let ((id (car (vector-ref dna-lines i))))
                (hash-set! table id id)
                (loop (1+ i)))
              table))))
    (define (init-group-to-ids dna-lines)
      (let ((table (make-hash-table)))
        (let loop ((i 0))
          (if {i < (vector-length dna-lines)}
              (let ((id (car (vector-ref dna-lines i))))
                (hash-set! table id (list id))
                (loop (1+ i)))
              table))))
    (let ((dna-lines (list->vector (parse-file "notes/everybody_codes_e2025_q09_p3.txt"))))
      (let ((id-to-group (init-id-to-group dna-lines)) (group-to-ids (init-group-to-ids dna-lines)))
      (let child-loop ((child 0))
        (if {child < (vector-length dna-lines)}
          (let i-loop ((i 0))
            (cond
              ((eq? i child) (i-loop (1+ i)))
              ({i >= {(vector-length dna-lines) - 1}} (child-loop (1+ child)))
              (else
                (let j-loop ((j (1+ i)))
                  (cond
                    ((eq? j child) (j-loop (1+ j)))
                    ({j >= (vector-length dna-lines)} (i-loop (1+ i)))
                    (else (let* ((cl (vector-ref dna-lines child))
                                 (pil (vector-ref dna-lines i))
                                 (pjl (vector-ref dna-lines j))
                                 (res (child-score (cdr cl) (cdr pil) (cdr pjl) 0 0)))
                      (if res
                          (let* ((i-group (hash-ref id-to-group (car pil)))
                                 (j-group (hash-ref id-to-group (car pjl)))
                                 (child-group (hash-ref id-to-group (car cl)))
                                 (i-group-ids (hash-ref group-to-ids i-group))
                                 (j-group-ids (hash-ref group-to-ids j-group))
                                 (child-group-ids (hash-ref group-to-ids child-group))
                                 (new-group-ids (delete-duplicates (append child-group-ids (or i-group-ids '()) (or j-group-ids '())))))
                            (map (lambda (id) (hash-set! id-to-group id child-group)) new-group-ids)
                            (hash-remove! group-to-ids i-group)
                            (hash-remove! group-to-ids j-group)
                            (hash-set! group-to-ids child-group new-group-ids)
                            (child-loop (1+ child)))
                          (j-loop (1+ j))))))))))
            (format #t "P3 Answer: ~a\n\n" (cdr (hash-fold
                    (lambda (_ id-list prior)
                            (let ((group-size (length id-list))
                                  (group-sum (apply + id-list)))
                              (if {group-size > (car prior)}
                                  (cons group-size group-sum)
                                  prior)))
                    (cons 0 0)
                    group-to-ids)))))))
    
  • source
  • [–] 2 points 11 months ago

    Scheme/Guile

    Takes about 5 seconds.

    (import (rnrs io ports (6))
            (srfi srfi-1))
    #!curly-infix
    
    (define (parse-file file-name)
      (let ((sequence (map string->number (string-split (string-trim-both (call-with-input-file file-name get-string-all)) #\,))))
        (zip sequence (cdr sequence))))
    
    (let loop ((sequence (parse-file "notes/everybody_codes_e2025_q08_p1.txt")) (count 0))
      (if (null? sequence)
          (format #t "P1 Answer: ~a\n\n" count)
          (loop (cdr sequence) (+ count (if (and last (eq? (modulo (- (cadar sequence) (caar sequence)) 32) 16)) 1 0)))))
    
    (define (crosses-over? a b)
      (let ((a1 (car a))
            (a2 (cadr a))
            (b1 (car b))
            (b2 (cadr b)))
        (let ((a2 (modulo {a2 - a1} 256))
              (b1 (modulo {b1 - a1} 256))
              (b2 (modulo {b2 - a1} 256)))
          (and (not (eq? b1 0)) (not (eq? b2 0))
          (or
            (and {b1 < a2} {b2 > a2})
            (and {b1 > a2} {b2 < a2}))))))
    (define (count-cross-overs sequence a)
      (let loop ((sequence sequence) (count 0))
        (if (null? sequence)
            count
            (loop (cdr sequence) (+ count (if (crosses-over? (car sequence) a) 1 0))))))
    (let loop ((sequence (parse-file "notes/everybody_codes_e2025_q08_p2.txt")) (passed '()) (count 0))
      (if (null? sequence)
          (format #t "P2 Answer: ~a\n\n" count)
          (loop (cdr sequence) (cons (car sequence) passed) (+ count (count-cross-overs passed (car sequence))))))
    
    
    (let ((sequence (parse-file "notes/everybody_codes_e2025_q08_p3.txt")))
      (let loop ((i 1) (greatest 0))
        (if {i > 256}
            (format #t "P3 Answer: ~a\n\n" greatest)
            (loop (1+ i) (max greatest (let loop ((j i) (greatest 0))
                  (if {j > 256}
                      greatest
                      (loop (1+ j) (max greatest (count-cross-overs sequence (list i j)))))))))))
    
  • source
  • [–] 2 points 1 year ago*

    Scheme/Guile

    You could probably build a (letter, length) => combination-count mapping pretty quickly for part 3, but dealing with overlap in the input elements seems like a pain if handled this way.

    (import (rnrs io ports (6)))
    #!curly-infix
    
    (define (parse-file file-name) (let*
      ((lines (string-split (string-trim-both (call-with-input-file file-name get-string-all)) #\newline))
       (names (string-split (car lines) #\,))
       (rule-lines (cddr lines))
       (rules (map (lambda (l) (let*
                           ((sides (string-split l #\space))
                            (r-left (string-ref (car sides) 0))
                            (r-right (caddr sides)))
                           (cons r-left (map (lambda (x) (string-ref x 0)) (string-split r-right #\,))))) rule-lines)))
      (cons names rules)))
    
    (define (check-name rules name)
      (if (eq? 1 (string-length name))
          #t
          (let* ((letter (string-ref name 0))
                 (right (assq-ref rules letter))
                 (next-letter (string-ref name 1)))
            (if (memq next-letter right)
                (check-name rules (substring/read-only name 1))
                #f))))
    (let* ((parsed (parse-file "notes/everybody_codes_e2025_q07_p1.txt"))
           (names (car parsed))
           (rules (cdr parsed)))
      (let loop ((names names))
        (let* ((name (car names)) (name-matches (check-name rules name)))
          (if name-matches
              (format #t "P1 Answer: ~a\n\n" name)
              (loop (cdr names))))))
    
    
    (let* ((parsed (parse-file "notes/everybody_codes_e2025_q07_p2.txt"))
           (names (car parsed))
           (rules (cdr parsed)))
      (let loop ((i 1) (names names) (name-sum 0))
        (if (null? names)
            (format #t "P2 Answer: ~a\n\n" name-sum)
            (let* ((name (car names)) (name-matches (check-name rules name)))
              (loop (1+ i) (cdr names) (+ name-sum (if name-matches i 0)))))))
    
    
    (define discovered-prefixes (make-hash-table))
    (define (count-prefixes rules name)
      (if (hash-ref discovered-prefixes name)
          0
          (begin
            (hash-set! discovered-prefixes name #t)
            (if {(string-length name) >= 11}
                1
                (+
                  (apply + (map
                         (lambda (c) (count-prefixes rules (string-append name (string c))))
                         (or (assq-ref rules (string-ref name (1- (string-length name)))) '())))
                  (if {(string-length name) >= 7} 1 0))))))
    (let* ((parsed (parse-file "notes/everybody_codes_e2025_q07_p3.txt"))
           (names (car parsed))
           (rules (cdr parsed)))
      (let ((name-count (apply + (map (lambda (name) (count-prefixes rules name)) (filter (lambda (name) (check-name rules name)) names)))))
        (format #t "P3 Answer: ~a\n\n" name-count)))
    
  • source
  • [–] [S] 1 point 1 year ago

    Rust

    use std::collections::{BTreeSet, HashMap, HashSet};
    
    use itertools::Itertools;
    
    pub fn solve_part_1(input: &str) -> String {
        let board: Vec<Vec<_>> = input.lines().map(|l| l.chars().collect()).collect();
        let mut front: HashSet<_> = (0usize..board.len())
            .cartesian_product(0usize..board[0].len())
            .filter(|&(i, j)| board[i][j] == 'D')
            .collect();
        let mut visited = HashSet::new();
        let knight_moves: [(isize, isize); 8] = [
            (2, 1),
            (2, -1),
            (-2, -1),
            (-2, 1),
            (1, 2),
            (1, -2),
            (-1, -2),
            (-1, 2),
        ];
        for _ in 0..=4 {
            let mut next_front = HashSet::new();
            for (i, j) in front.drain() {
                for (di, dj) in knight_moves {
                    let (ni, nj) = (i.wrapping_add_signed(di), j.wrapping_add_signed(dj));
                    if ni >= board.len() || nj >= board[0].len() {
                        continue;
                    }
                    if visited.contains(&(ni, nj)) {
                        continue;
                    }
                    next_front.insert((ni, nj));
                }
                visited.insert((i, j));
            }
            front = next_front;
        }
        visited
            .drain()
            .filter(|&(i, j)| board[i][j] == 'S')
            .count()
            .to_string()
    }
    
    fn solve_part_2_with_turns(input: &str, turns: usize) -> String {
        let board: Vec<Vec<_>> = input.lines().map(|l| l.chars().collect()).collect();
        let mut front: HashSet<_> = (0usize..board.len())
            .cartesian_product(0usize..board[0].len())
            .filter(|&(i, j)| board[i][j] == 'D')
            .collect();
        let knight_moves: [(isize, isize); 8] = [
            (2, 1),
            (2, -1),
            (-2, -1),
            (-2, 1),
            (1, 2),
            (1, -2),
            (-1, -2),
            (-1, 2),
        ];
        let mut eaten_sheep = HashSet::new();
        for turn in 0..=turns {
            let mut next_front = HashSet::new();
            for (i, j) in front.drain() {
                for (di, dj) in knight_moves {
                    let (ni, nj) = (i.wrapping_add_signed(di), j.wrapping_add_signed(dj));
                    if ni >= board.len() || nj >= board[0].len() {
                        continue;
                    }
                    next_front.insert((ni, nj));
                }
                if board[i][j] != '#' {
                    if let Some(sheep_i) = (i + 1).checked_sub(turn)
                        && board[sheep_i][j] == 'S'
                    {
                        eaten_sheep.insert((sheep_i, j));
                    }
                    if let Some(sheep_i) = i.checked_sub(turn)
                        && turn != 0
                        && board[sheep_i][j] == 'S'
                    {
                        eaten_sheep.insert((sheep_i, j));
                    }
                }
            }
            front = next_front;
        }
        eaten_sheep.len().to_string()
    }
    
    pub fn solve_part_2(input: &str) -> String {
        solve_part_2_with_turns(input, 20)
    }
    type VeryComplexType = HashMap<(usize, usize, usize, Vec<(usize, usize)>), usize>;
    fn count_winning_sequences(
        turn: usize,
        dragon: (usize, usize),
        hiding_places: &HashSet<(usize, usize)>,
        sheep: BTreeSet<(usize, usize)>,
        height: usize,
        width: usize,
        cache: &mut VeryComplexType,
    ) -> usize {
        if sheep.is_empty() {
            return 1;
        }
        let cache_key = (
            turn % 2,
            dragon.0,
            dragon.1,
            sheep.iter().cloned().collect(),
        );
        if let Some(result) = cache.get(&cache_key) {
            return *result;
        }
        if turn % 2 == 1 {
            let knight_moves: [(isize, isize); 8] = [
                (2, 1),
                (2, -1),
                (-2, -1),
                (-2, 1),
                (1, 2),
                (1, -2),
                (-1, -2),
                (-1, 2),
            ];
            let (i, j) = dragon;
            let mut total = 0;
            for (di, dj) in knight_moves {
                let (ni, nj) = (i.wrapping_add_signed(di), j.wrapping_add_signed(dj));
                if ni >= height || nj >= width {
                    continue;
                }
                if !hiding_places.contains(&(ni, nj)) && sheep.contains(&(ni, nj)) {
                    let mut new_sheep = sheep.clone();
                    new_sheep.remove(&(ni, nj));
                    total += count_winning_sequences(
                        turn + 1,
                        (ni, nj),
                        hiding_places,
                        new_sheep,
                        height,
                        width,
                        cache,
                    );
                } else {
                    total += count_winning_sequences(
                        turn + 1,
                        (ni, nj),
                        hiding_places,
                        sheep.clone(),
                        height,
                        width,
                        cache,
                    );
                }
            }
            cache.insert(cache_key, total);
            total
        } else {
            let mut sheep_moves_available = false;
            let mut total = 0;
            for &(i, j) in sheep.iter() {
                if dragon == (i + 1, j) && !hiding_places.contains(&(i + 1, j)) {
                    continue;
                }
                sheep_moves_available = true;
                if i == (height - 1) {
                    continue;
                }
                let mut new_sheep = sheep.clone();
                new_sheep.remove(&(i, j));
                new_sheep.insert((i + 1, j));
                total += count_winning_sequences(
                    turn + 1,
                    dragon,
                    hiding_places,
                    new_sheep,
                    height,
                    width,
                    cache,
                );
            }
            if !sheep_moves_available {
                return count_winning_sequences(
                    turn + 1,
                    dragon,
                    hiding_places,
                    sheep,
                    height,
                    width,
                    cache,
                );
            }
            cache.insert(cache_key, total);
            total
        }
    }
    
    pub fn solve_part_3(input: &str) -> String {
        let board: Vec<Vec<_>> = input.lines().map(|l| l.chars().collect()).collect();
        let dragon = (0usize..board.len())
            .cartesian_product(0usize..board[0].len())
            .filter(|&(i, j)| board[i][j] == 'D')
            .exactly_one()
            .unwrap();
        let sheep = (0usize..board.len())
            .cartesian_product(0usize..board[0].len())
            .filter(|&(i, j)| board[i][j] == 'S')
            .collect::<BTreeSet<_>>();
        let hiding_places = (0usize..board.len())
            .cartesian_product(0usize..board[0].len())
            .filter(|&(i, j)| board[i][j] == '#')
            .collect::<HashSet<_>>();
        let mut cache = HashMap::new();
        count_winning_sequences(
            0,
            dragon,
            &hiding_places,
            sheep,
            board.len(),
            board[0].len(),
            &mut cache,
        )
        .to_string()
    }
    
  • source
  • [–] [S] 1 point 1 year ago*

    Rust

    use std::collections::HashMap;
    
    use bit_set::BitSet;
    use itertools::{Itertools, izip};
    
    type BitSetBase = u32;
    
    fn is_child(child: &str, parent1: &str, parent2: &str) -> bool {
        izip!(child.chars(), parent1.chars(), parent2.chars()).all(|(c, a, b)| c == a || c == b)
    }
    
    fn similarity(a: &str, b: &str) -> usize {
        izip!(a.chars(), b.chars()).filter(|(a, b)| a == b).count()
    }
    
    fn unequality_bitset(a: &str, b: &str) -> BitSet<BitSetBase> {
        izip!(a.chars(), b.chars())
            .enumerate()
            .filter_map(|(i, (a_ch, b_ch))| if a_ch == b_ch { None } else { Some(i) })
            .collect()
    }
    
    pub fn solve_part_1(input: &str) -> String {
        let dnas = input
            .lines()
            .map(|l| {
                let (_, dna) = l.split_once(":").unwrap();
                dna
            })
            .collect::<Vec<_>>();
        let (child, parenta, parentb) = if is_child(dnas[0], dnas[1], dnas[2]) {
            (dnas[0], dnas[1], dnas[2])
        } else if is_child(dnas[1], dnas[0], dnas[2]) {
            (dnas[1], dnas[0], dnas[2])
        } else {
            (dnas[2], dnas[0], dnas[1])
        };
        (similarity(child, parenta) * similarity(child, parentb)).to_string()
    }
    
    pub fn solve_part_2(input: &str) -> String {
        let dnas = input
            .lines()
            .map(|l| {
                let (_, dna) = l.split_once(":").unwrap();
                dna
            })
            .collect::<Vec<_>>();
        let unequalities = dnas
            .iter()
            .enumerate()
            .cartesian_product(dnas.iter().enumerate())
            .map(|((a_id, a), (b_id, b))| ((a_id, b_id), unequality_bitset(a, b)))
            .collect::<HashMap<_, _>>();
        let mut total_similarities = 0;
        'outer: for (child_id, &child_string) in dnas.iter().enumerate() {
            for (parent_a_id, &parent_a_string) in dnas.iter().enumerate() {
                if parent_a_id == child_id {
                    continue;
                }
                for (parent_b_id, &parent_b_string) in dnas.iter().enumerate() {
                    if parent_b_id == child_id || parent_b_id == parent_a_id {
                        continue;
                    }
                    if unequalities[&(child_id, parent_a_id)]
                        .intersection(&unequalities[&(child_id, parent_b_id)])
                        .count()
                        == 0
                    {
                        total_similarities += similarity(child_string, parent_a_string)
                            * similarity(child_string, parent_b_string);
                        continue 'outer;
                    }
                }
            }
        }
        total_similarities.to_string()
    }
    
    pub fn solve_part_3(input: &str) -> String {
        let dnas = input
            .lines()
            .map(|l| {
                let (_, dna) = l.split_once(":").unwrap();
                dna
            })
            .collect::<Vec<_>>();
        let unequalities = dnas
            .iter()
            .enumerate()
            .cartesian_product(dnas.iter().enumerate())
            .map(|((a_id, a), (b_id, b))| ((a_id, b_id), unequality_bitset(a, b)))
            .collect::<HashMap<_, _>>();
        let mut parents: HashMap<usize, (usize, usize)> = HashMap::new();
        'outer: for (child_id, _) in dnas.iter().enumerate() {
            for (parent_a_id, _) in dnas.iter().enumerate() {
                if parent_a_id == child_id {
                    continue;
                }
                for (parent_b_id, _) in dnas.iter().enumerate() {
                    if parent_b_id == child_id || parent_b_id == parent_a_id {
                        continue;
                    }
                    if unequalities[&(child_id, parent_a_id)]
                        .intersection(&unequalities[&(child_id, parent_b_id)])
                        .count()
                        == 0
                    {
                        parents.insert(child_id, (parent_a_id, parent_b_id));
                        continue 'outer;
                    }
                }
            }
        }
        let mut family_id = (0usize..dnas.len()).collect::<Vec<_>>();
        for (child, (parent_a, parent_b)) in parents.drain() {
            let destination_family_id = family_id[child];
            let merge_families = (family_id[parent_a], family_id[parent_b]);
            for candidate_family in family_id.iter_mut() {
                if *candidate_family == merge_families.0 || *candidate_family == merge_families.1 {
                    *candidate_family = destination_family_id;
                }
            }
        }
        let families = family_id
            .iter()
            .enumerate()
            .map(|(member_idx, &family_idx)| (family_idx, member_idx))
            .into_group_map();
        families
            .values()
            .max_by_key(|f| f.len())
            .unwrap()
            .iter()
            .map(|member_id| member_id + 1)
            .sum::<usize>()
            .to_string()
    }
    
  • source
  • [–] 2 points 1 year ago

    Scheme/Guile

    Part 3 was a fun little challenge.

    (import (rnrs io ports (6)))
    #!curly-infix
    
    (define (parse-file file-name) (string-trim-both (call-with-input-file file-name get-string-all)))
    
    (let* ((line (parse-file "notes/everybody_codes_e2025_q06_p1.txt"))
           (line-length (string-length line)))
      (let loop ((i 0) (knight-count 0) (mentor-count 0))
        (if {i < line-length}
            (let ((letter (string-ref line i)))
              (loop (1+ i) (+ knight-count (if (eq? letter #\A) 1 0)) (+ mentor-count (if (eq? letter #\a) knight-count 0))))
            (format #t "P1 Answer: ~a\n\n" mentor-count))))
    
    (let* ((line (parse-file "notes/everybody_codes_e2025_q06_p2.txt"))
           (line-length (string-length line)))
      (let loop ((i 0) (knight-counts '()) (mentor-count 0))
        (if {i < line-length}
            (let ((letter (string-ref line i)))
              (loop
                (1+ i)
                (if (char-upper-case? letter) (assq-set! knight-counts letter (1+ (or (assq-ref knight-counts letter) 0))) knight-counts)
                (+ mentor-count (if (char-lower-case? letter) (or (assq-ref knight-counts (char-upcase letter)) 0) 0))))
            (format #t "P2 Answer: ~a\n\n" mentor-count))))
    
    
    (let* ((line (parse-file "notes/everybody_codes_e2025_q06_p3.txt"))
           (line-length (string-length line)))
      (let loop ((i 0) (mentor-count 0))
        (if {i < line-length}
            (let ((letter (string-ref line i)))
              (loop
                (1+ i)
                (+ mentor-count
                   (if (char-lower-case? letter)
                       (let loop ((j (- i 1000)) (mentors-here 0))
                         (if {j <= (+ i 1000)}
                             (loop
                               (1+ j)
                               (+ mentors-here
                                  (if {(string-ref line {j modulo line-length}) eq? (char-upcase letter)}
                                      (if (and {0 <= j} {j < line-length}) 1000 999)
                                      0)))
                             mentors-here))
                       0))))
            (format #t "P3 Answer: ~a\n\n" mentor-count))))
    
  • source
  • [–] 2 points 1 year ago* (last edited 1 year ago)

    Haskell

    Hmm. I'm still not very happy with part 3: it's a bit slow and messy. Doing state over the list monad for memoization doesn't work well, so I'm enumerating all possible configurations first and taking advantage of laziness.

    import Control.Monad  
    import Data.Bifunctor  
    import Data.Ix  
    import Data.List  
    import Data.Map (Map)  
    import Data.Map qualified as Map  
    import Data.Maybe  
    import Data.Set.Monad (Set)  
    import Data.Set.Monad qualified as Set  
    import Data.Tuple  
    
    type Pos = (Int, Int)  
    
    readInput :: String -> ((Pos, Pos), Pos, Set Pos, Set Pos)  
    readInput s =  
      let grid =  
            Map.fromList  
              [ ((i, j), c)  
                | (i, cs) <- zip [0 ..] $ lines s,  
                  (j, c) <- zip [0 ..] cs  
              ]  
       in ( ((0, 0), fst $ Map.findMax grid),  
            fst $ fromJust $ find ((== 'D') . snd) $ Map.assocs grid,  
            Set.fromList $ Map.keys (Map.filter (== 'S') grid),  
            Set.fromList $ Map.keys (Map.filter (== '#') grid)  
          )  
    
    moveDragon (i, j) = Set.mapMonotonic (bimap (+ i) (+ j)) offsets  
      where  
        offsets = Set.fromList ([id, swap] <*> ((,) <$> [-1, 1] <*> [-2, 2]))  
    
    dragonMoves bounds =  
      iterate (Set.filter (inRange bounds) . (>>= moveDragon)) . Set.singleton  
    
    part1 n (bounds, start, sheep, _) =  
      (!! n)  
        . map (Set.size . Set.intersection sheep)  
        . scanl1 Set.union  
        $ dragonMoves bounds start  
    
    part2 n (bounds, dragonStart, sheepStart, hideouts) =  
      (!! n)  
        . map ((Set.size sheepStart -) . Set.size)  
        . scanl'  
          ( \sheep eaten ->  
              (Set.\\ eaten)  
                . Set.mapMonotonic (first (+ 1))  
                . (Set.\\ eaten)  
                $ sheep  
          )  
          sheepStart  
        . map (Set.\\ hideouts)  
        $ (tail $ dragonMoves bounds dragonStart)  
    
    part3 (bounds, dragonStart, sheepStart, hideouts) =  
      count (dragonStart, sheepStart)  
      where  
        sheepStartByColumn = Map.fromList $ map swap $ Set.elems sheepStart  
        sheepConfigs =  
          map  
            ( (Set.fromList . catMaybes)  
                . zipWith (\j -> fmap (,j)) (Map.keys sheepStartByColumn)  
            )  
            . mapM  
              ( ((Nothing :) . map Just)  
                  . (`enumFromTo` (fst $ snd bounds))  
              )  
            $ Map.elems sheepStartByColumn  
        count =  
          ((Map.!) . Map.fromList . map ((,) <*> go))  
            ((,) <$> range bounds <*> sheepConfigs)  
        go (dragon, sheep)  
          | null sheep = 1  
          | otherwise =  
              (sum . map count) $ do  
                let movableSheep =  
                      filter (\(_, p) -> p /= dragon || Set.member p hideouts) $  
                        map (\(i, j) -> ((i, j), (i + 1, j))) $  
                          Set.elems sheep  
                    sheepMoves =  
                      if null movableSheep  
                        then [sheep]  
                        else do  
                          (p1, p2) <- movableSheep  
                          return $ Set.insert p2 $ Set.delete p1 sheep  
                sheep' <- sheepMoves  
                guard $ all (inRange bounds) sheep'  
                dragon' <- Set.elems $ moveDragon dragon  
                guard $ inRange bounds dragon'  
                let eaten = Set.singleton dragon' Set.\\ hideouts  
                return (dragon', sheep' Set.\\ eaten)  
    
    main = do  
      readFile "everybody_codes_e2025_q10_p1.txt" >>= print . part1 4 . readInput  
      readFile "everybody_codes_e2025_q10_p2.txt" >>= print . part2 20 . readInput  
      readFile "everybody_codes_e2025_q10_p3.txt" >>= print . part3 . readInput  
    
  • source
  • [–] 2 points 1 year ago*

    Uiua

    Just a dirty great hack and a few minutes of toasty CPU for part3 with live data.

    "1,5,2,6,8,4,1,7,3"
    βŠœβ‹•βŠΈβ‰ @,
    &p /+=βŠƒ(β§ˆβ‚‚(⌡/-)|Γ·2/β†₯) # Part1 --> 4
    
    "1,5,2,6,8,4,1,7,3,5,7,8,2"
    βŠœβ‹•βŠΈβ‰ @,
    Knot  ← (βŠƒ(=Β―|=)∩⌞(Β±-)βŠ™Β°βŠŸ)
    Knots ← /↧/β†₯β†―βˆž_2[∩⌟Knot]°⊟
    β§ˆβ‚‚β†
    &p /+≑(/+≑KnotsΒ€Β°βŠ‚β†™Β―)βŠ™Β€+1β†˜2β‡‘βŠΈβ§» # Part 2 --> 21
    
    "1,5,2,6,8,4,1,7,3,6"
    βŠœβ‹•βŠΈβ‰ @,
    βŠƒ(β§…<2+1⇑⧻◴|β§ˆβ‚‚β†)            # Possible cuts, existing strings.
    /β†₯+βŠƒ(β‰‘ΛœβˆŠβŠ™Β€|≑(/+≑KnotsΒ€)βŠ™Β€) # Part3 --> 7
    
  • source
  • [–] 2 points 1 year ago (1 child)

    I'm sure there are 17 different graph libraries I could have used for the graph representation and connected components, but it seemed to be in the spirit of the question to write it myself. Nothing interesting about the parent search though -- it's just brute-force comparison.

    (ql:quickload :str)
    
    (defun parse-line (line)
      (let ((index-and-codes (str:split ":" line)))
        (cons (parse-integer (car index-and-codes)) (cadr index-and-codes))))
    
    (defun read-inputs (filename)
      (let ((input-lines (uiop:read-file-lines filename)))
        (mapcar #'parse-line input-lines)))
    
    (defun can-be-child-of? (parent1 parent2 child)
      (loop for i from 0 to (1- (length child))
            unless (or (eql (char child i) (char parent1 i))
                       (eql (char child i) (char parent2 i)))
              return nil
            finally (return t)))
    
    (defun similarity (genome1 genome2)
      (loop for i from 0 to (1- (length genome1))
            sum (if (eql (char genome1 i) (char genome2 i)) 1 0)))
    
    (defun main-1 (filename)
      (let ((genomes (read-inputs filename)))
        (loop for arrangement in '((1 2 3) (2 3 1) (3 1 2))
              maximize
              (destructuring-bind (parent1-index parent2-index child-index) arrangement
                (let ((parent1 (cdr (assoc parent1-index genomes)))
                      (parent2 (cdr (assoc parent2-index genomes)))
                      (child (cdr (assoc child-index genomes))))
                  (if (can-be-child-of? parent1 parent2 child)
                      (* (similarity parent1 child) (similarity parent2 child))
                      0))))))
    
    (defun find-parents (genomes child-pair)
      (loop named loop1
            for tail1 on genomes
            for parent1-pair = (car tail1)
            do (loop for parent2-pair in (cdr tail1)
                     when (and
                           (/= (car parent1-pair) (car child-pair))
                           (/= (car parent2-pair) (car child-pair))
                           (can-be-child-of? (cdr parent1-pair) (cdr parent2-pair) (cdr child-pair)))
                       do (return-from loop1 (cons (car parent1-pair) (car parent2-pair))))
            finally (return-from loop1 nil)))
    
    (defun child-relationships (genomes)
      (mapcar #'(lambda (child-pair)
                  (cons (car child-pair) (find-parents genomes child-pair)))
              genomes))
    
    (defun main-2 (filename)
      (let* ((genomes (read-inputs filename))
             (child-relationships (child-relationships genomes)))
        (loop for child-rel in child-relationships
              sum (destructuring-bind (child-idx . parent-idxs) child-rel
                    (if (null parent-idxs)
                        0
                        (let ((parent1 (cdr (assoc (car parent-idxs) genomes)))
                              (parent2 (cdr (assoc (cdr parent-idxs) genomes)))
                              (child (cdr (assoc child-idx genomes))))
                          (* (similarity parent1 child) (similarity parent2 child))))))))
    
    (defun relationship-graph (child-relationships)
      (let ((edges (mapcan #'(lambda (child-rel)
                               (destructuring-bind (child-idx . parent-idxs) child-rel
                                 (if (null parent-idxs)
                                     nil
                                     (list (cons child-idx (car parent-idxs))
                                           (cons child-idx (cdr parent-idxs))))))
                           child-relationships))
            (graph (make-hash-table)))
        (loop for edge in edges
              do (destructuring-bind (x . y) edge
                   (setf (gethash x graph) (cons y (gethash x graph)))
                   (setf (gethash y graph) (cons x (gethash y graph)))))
        graph))
    
    (defun component-of (graph vertex)
      (labels ((iter (so-far)
                 (let ((next (reduce #'union
                                     (mapcar #'(lambda (v) (gethash v graph)) so-far)
                                     :initial-value so-far)))
                   (if (subsetp next so-far)
                       next
                       (iter next)))))
        (iter (list vertex))))
    
    (defun all-components (graph vertices)
      (labels ((iter (so-far vertices-left)
                 (if (null vertices-left)
                     so-far
                     (let ((comp (component-of graph (car vertices-left))))
                       (iter (cons comp so-far)
                             (set-difference vertices-left comp))))))
        (iter nil vertices)))
    
    (defun main-3 (filename)
      (let* ((genomes (read-inputs filename))
             (child-relationships (child-relationships genomes))
             (relationship-graph (relationship-graph child-relationships))
             (keys (mapcar #'car child-relationships))
             (components (all-components relationship-graph keys)))
        (reduce #'+
                (car (sort components #'(lambda (c1 c2) (> (length c1) (length c2))))))))
    
  • source
  • [–] 2 points 1 year ago*

    Uiua

    There's probably a good solution hiding in here, but this ain't it. I originally went for the combinatoric approach for part 3, but it was giving me an answer that turned out to be out by 8, so brute force came to the rescue.

    Prep ← (
      βŠœβ–‘βŠΈβ‰ @\n
      Pairs ← β‰‘βŠ‚βŠ“(Β€|βŠœβˆ˜βŠΈβ‰ @,)βˆ©Β°β–‘Β°βŠŸ
      βŠƒ(≑(β–‘PairsβŠœβ–‘βŠΈ(Β¬β¦·" > ")Β°β–‘)β†˜1|βŠœβ–‘βŠΈβ‰ @,Β°β–‘βŠ’)
      β†˜Β―1∧(βŠ‚Β°β–‘) βŠ™["  "]
    )
    "Oronris,Urakris,Oroneth,Uraketh\nr > a,i,o\ni > p,w\nn > e,r\no > n,m\nk > f,r\na > k\nU > r\ne > t\nO > r\nt > h\n"
    Β°β–‘βŠ’β–½βŠΈβ‰‘(/β†§βˆŠβŠ™(β§ˆβ‚‚βˆ˜Β°β–‘))Β€Prep # Part1 --> "Oroneth"
    "Xanverax,Khargyth,Nexzeth,Helther,Braerex,Tirgryph,Kharverax\nr > v,e,a,g,y\na > e,v,x,r\ne > r,x,v,t\nh > a,e,v\ng > r,y\ny > p,t\ni > v,r\nK > h\nv > e\nB > r\nt > h\nN > e\np > h\nH > e\nl > t\nz > e\nX > a\nn > v\nx > z\nT > i\n"
    /++1βŠšβ‰‘(/β†§βˆŠβŠ™(β§ˆβ‚‚βˆ˜Β°β–‘))Β€Prep # Part2 --> 23
    P ← Prep"Khara,Xaryt,Noxer,Kharax\nr > v,e,a,g,y\na > e,v,x,r,g\ne > r,x,v,t\nh > a,e,v\ng > r,y\ny > p,t\ni > v,r\nK > h\nv > e\nB > r\nt > h\nN > e\np > h\nH > e\nl > t\nz > e\nX > a\nn > v\nx > z\nT > i\n"
    Keys  ← βŠ™β—ŒP
    Names ← β—Œ P
    GoodNames ← (
      Λœβ–½Names≑(/β†§βˆŠβŠ™(β§ˆβ‚‚βˆ˜Β°β–‘))Β€P                 # Remove names that fail part2
      β–½βŠΈ(¬≑(/+≑(⨬(0|β‰βˆ©βŒžβ†™β†§β—‘βˆ©β§»βˆ©Β°β–‘)>β—‘βˆ©(β§»Β°β–‘))))⊸€ # Exclude all names that are prefixes of others.
    )
    # Build up all names and count them. Yuk, slower, but righter.
    βŠ™0GoodNames
    ≑◇([∘]
       ⍒(
        /β—‡βŠ‚β‰‘β—‡(β–‘β‰‘ΛœβŠ‚βŠ™Β€β‰‘βŠ£β–½β€š(=β‰‘βŠ’)Keys⊸⊣) # Add next chars, collect.
        ⨬(∘|βŠ™+⟜⧻)⊸(β‰₯7⧻⊒)             # Add to counts if long enough.
      | <11βŠ£β–³)
       β—Œ # We don't need the data, just the counts.
    )
    /+
    
  • source
  • [–] 2 points 1 year ago

    Common Lisp's loop macro has a pretty crazy list of features. New ones in this solution are for x on xs, which binds the iteration variable x to successive tails (instead of elements) of xs, and maximize, which is an alternative accumulator to sum. There's nothing interesting about the solution itself though -- simple brute-force enumeration.

    (ql:quickload :str)
    
    (defun parse-line (line)
      (mapcar #'parse-integer (str:split "," line)))
    
    (defun read-inputs (filename)
      (let ((input-lines (uiop:read-file-lines filename)))
        (parse-line (car input-lines))))
    
    (defun pairs (ns)
      (loop for tail on ns
            if (not (null (cdr tail)))
              collect (cons (car tail) (cadr tail))))
    
    (defun through-center? (nails segment)
      (destructuring-bind (x . y) segment
        (= (mod (- x y) nails) (/ nails 2))))
    
    (defun main-1 (filename)
      (let ((positions (read-inputs filename)))
        (loop for segment in (pairs positions)
              sum (if (through-center? 32 segment) 1 0))))
    
    (defun crosses? (seg1 seg2)
      "When everything is normalized to 1..nails indices, seg1 crosses seg2 iff one of seg2's
      endpoints lies strictly between the endpoints of seg1, and the other one of seg2's endpoints
      lies strictly below or above the endpoints of seg1."
      (destructuring-bind (x1 . y1) seg1
        (destructuring-bind (x2 . y2) seg2
          (let ((big1 (max x1 y1))
                (small1 (min x1 y1)))
            (or (and (< small1 x2 big1)
                     (or (< y2 small1) (> y2 big1)))
                (and (or (< x2 small1) (> x2 big1))
                     (< small1 y2 big1)))))))
    
    (defun main-2 (filename)
      (let ((positions (read-inputs filename)))
        (loop for seg-list on (pairs positions)
              sum (loop for seg2 in (cdr seg-list)
                        sum (if (crosses? (car seg-list) seg2) 1 0)))))
    
    (defun score (threads seg)
      (loop for thread in threads
            sum (if (crosses? thread seg) 1 0)))
    
    (defun main-3 (filename)
      (let* ((positions (read-inputs filename))
             (threads (pairs positions))
             (nails 256))
        (loop for x1 from 1 to nails
              maximize (loop for y1 from (1+ x1) to nails
                             maximize (score threads (cons x1 y1))))))
    
  • source
  • [–] 2 points 1 year ago

    Even after 20 years in "nicer", "safer", "more modern" languages ... I still miss Lisp. It's the only language that makes me feel like I'm sculpting in clay, instead of carving stone (Haskell) or laying bricks (Java) or building with Lego (Python). Sure, sometimes the clay comes out kind of lumpy, but that's part of the experience.

    (ql:quickload :str)
    (ql:quickload :cl-ppcre)
    
    (defun parse-names-line (line)
      (str:split "," line))
    
    (defun parse-rule-line (line)
      (ppcre:register-groups-bind
          (initial finals)
          ("^([A-Za-z]) > ([A-Za-z,]+)$" line)
        (cons (char initial 0)
              (mapcar #'(lambda (s) (char s 0)) (str:split "," finals)))))
    
    (defun read-inputs (filename)
      (let ((input-lines (uiop:read-file-lines filename)))
        (list (cons :names (parse-names-line (car input-lines)))
              (cons :rules (mapcar #'parse-rule-line (cddr input-lines))))))
    
    (defun valid? (rules name)
      (flet ((valid-pair? (x y)
               (member y (cdr (assoc x rules)))))
        (loop for i from 0 to (- (length name) 2)
              when (not (valid-pair? (char name i) (char name (1+ i))))
                return nil
              finally (return t))))
    
    (defun main-1 (filename)
      (let* ((names-and-rules (read-inputs filename))
             (names (cdr (assoc :names names-and-rules)))
             (rules (cdr (assoc :rules names-and-rules))))
        (loop for name in names
              when (valid? rules name)
                return name)))
    
    (defun main-2 (filename)
      (let* ((names-and-rules (read-inputs filename))
             (names (cdr (assoc :names names-and-rules)))
             (rules (cdr (assoc :rules names-and-rules))))
        (loop for i from 0 to (1- (length names))
              sum (if (valid? rules (nth i names)) (1+ i) 0))))
    
    (defun augment (rules prefixes)
      (flet ((augment-one (prefix)
               (mapcar #'(lambda (c) (str:concat prefix (string c)))
                       (cdr (assoc (uiop:last-char prefix) rules)))))
        (mapcan #'augment-one prefixes)))
    
    (defun main-3 (filename)
      (let* ((min-length 7)
             (max-length 11)
             (names-and-rules (read-inputs filename))
             (rules (cdr (assoc :rules names-and-rules)))
             (prefixes (remove-if-not #'(lambda (prefix) (valid? rules prefix))
                                      (cdr (assoc :names names-and-rules))))
             (names-by-length (make-hash-table)))
        (loop for l from (apply #'min (mapcar #'length prefixes)) to max-length
              do (setf (gethash l names-by-length)
                       (remove-duplicates
                        (append (remove-if-not #'(lambda (prefix) (= l (length prefix))) prefixes)
                                (augment rules (gethash (1- l) names-by-length)))
                        :test #'equal)))
        (loop for l from min-length to max-length
              sum (length (gethash l names-by-length)))))
    
  • source
  • [–] 3 points 1 year ago

    Uiua

    Straightforward until part3. I left the naive approach running while I put together the version below, but of course the better algorithm got there first.

    Prep ← (
      β‰ΛœβŠŸΒ°βŠβ‰‘β–‘                # store char with index
      βŠ•(βŠŸβŠƒ(⊒⊒|░≑(Β°β–‘βŠ£)))βŠ›βŠΈβ‰‘β—‡βŠ’ # group them
      β‰β†―βˆž_6βŠββŠΈβ‰‘(Β°β–‘βŠ’βŠ’)        # re-arrange
      β–½=1β—Ώ2°⊏                # drop labels, as we never used them...
    )
    /+/+⊞>βˆ©Β°β–‘Β°βŠŸβŠ’Prep "ABabACacBCbca"     # Part1
    /+≑(/+/+⊞>βˆ©Β°β–‘Β°βŠŸ)Prep "ABabACacBCbca" # Part2
    
    # Note that the pattern repeats, so the only distinct values
    # are in the first run and last run. Everything in between
    # will be identical. Only works when distance < string length...
    ⟜Prep "AABCBABCABCabcabcABCCBAACBCa"
    Rep   ← 2
    Split ← [1 -2Rep 1] # multipliers for the three results for each profession.
    Dist  ← 10
    ≑(≑(βŠŸβˆ©β–‘)βŠƒ(≑+βŠ™(Β€Β°β–‘βŠ’)|€♭≑+βŠ™(Β€Β°β–‘βŠ£)))€×⇑3β§» # Create the start, middle, end set for each.
    /+≑(/+Γ—Split≑(/+/+⊞(≀Dist⌡-)βˆ©Β°β–‘Β°βŠŸ))
    
  • source
  • [–] 2 points 1 year ago

    In part 3 we can avoid thinking about the structure of the input string (left vs center vs right portions) and get the efficiency back by building up the counts of available mentors in each window position incrementally. The result is more code and takes a lot of space, but minimizes the number of irregular boundary cases.

    (ql:quickload :str)
    
    (defun parse-line (line)
      (let ((result (make-hash-table :test #'equal)))
        (loop for n from 0 to (1- (length line))
              for c = (char line n)
              for old = (gethash c result)
              do (setf (gethash c result) (cons n old)))
        result))
    
    (defun read-inputs (filename)
      (parse-line (car (uiop:read-file-lines filename))))
    
    (defun pairs (pos-hash early-type late-type)
      (loop for early-index in (gethash early-type pos-hash)
            sum (length (remove-if #'(lambda (late-index) (< late-index early-index))
                                   (gethash late-type pos-hash)))))
    
    (defun main-1 (filename)
      (pairs (read-inputs filename) #\A #\a))
    
    (defun main-2 (filename)
      (let ((pos-hash (read-inputs filename)))
        (reduce #'+
                (mapcar #'(lambda (type-pair) (apply #'pairs (cons pos-hash type-pair)))
                        '((#\A #\a) (#\B #\b) (#\C #\c))))))
    
    (defun char-at (base-string pos)
      (char base-string (mod pos (length base-string))))
    
    (defun type-window-counts (base-string copies radius type)
      (let* ((max-pos (* copies (length base-string)))
             (counts (make-array max-pos)))
        (setf (aref counts 0)
              (loop for i from 0 to radius
                    sum (if (eql type (char-at base-string i)) 1 0)))
        (loop for i from 1 to (1- max-pos)
              do (let ((new-count
                         (+ (aref counts (1- i))
                            (if (and (< (+ i radius) max-pos)
                                     (eql type (char-at base-string (+ i radius))))
                                1 0)
                            (if (and (>= (- i (1+ radius)) 0)
                                     (eql type (char-at base-string (- i (1+ radius)))))
                                -1 0))))
                   (setf (aref counts i) new-count)))
        counts))
    
    (defun window-counts (base-string copies radius types)
      (mapcar #'(lambda (type)
                  (cons type (type-window-counts base-string copies radius type)))
              types))
    
    (defun count-pairs (base-string copies source-type target-type-window-counts)
      (let ((max-pos (* copies (length base-string))))
        (loop for i from 0 to (1- max-pos)
              sum (if (eql source-type (char-at base-string i))
                      (aref target-type-window-counts i) 0))))
    
    (defun main-3 (filename)
      (let* ((base-string (car (uiop:read-file-lines filename)))
             (copies 1000)
             (radius 1000)
             (type-pairs '((#\a #\A) (#\b #\B) (#\c #\C)))
             (window-counts (window-counts base-string copies radius (mapcar #'cadr type-pairs))))
        (reduce #'+
                (mapcar #'(lambda (type-pair)
                            (count-pairs base-string copies (car type-pair)
                                         (cdr (assoc (cadr type-pair) window-counts))))
                        type-pairs))))
    
  • source
  • view more: β€Ή prev next β€Ί