Day 4: Ceres Search

Megathread guidelines

  • Keep top level comments as only solutions, if you want to say something other than a solution put it in a new post. (replies to comments can be whatever)
  • You can send code in code blocks by using three backticks, the code, and then three backticks or use something such as https://topaz.github.io/paste/ if you prefer sending it through a URL

FAQ

  • Quant@programming.dev
    link
    fedilink
    arrow-up
    2
    ·
    5 days ago

    Uiua

    This one was nice. The second part seemed quite daunting at first but wasn’t actually that hard in the end.

    Run with example input here

    Row    ← ⌕ "XMAS"
    RevRow ← ⌕"SAMX"
    Sum    ← /+/+
    Count  ← +∩Sum⊃Row RevRow
    
    PartOne ← (
      &rs ∞ &fo "input-4.txt"
      ⊜∘≠@\n.
      ⊙+⟜∩Count⟜⍉ # horizontal and vertical search
      ⟜(/+⧈(Count⍉≡⬚@ ↻⇡⧻.)4)
      /+⧈(Count⍉≡⬚@ ↻¯⇡⧻.)4
      ++
    )
    
    Mask ← °⊚×2⇡5
    # Create variations of X-MAS
    Vars ← (
      ["M S"
       " A "
       "M S"]
      ≡♭[∩⟜⍉]≡⇌.
      Mask
      ⊏0⊞▽¤
    )
    
    PartTwo ← (
      &rs ∞ &fo "input-4.txt"
      ⊜∘≠@\n.
      ⧈(/+♭⊞≍⊙¤Vars▽Mask♭)3_3
      Sum
    )
    
    &p "Day 4:"
    &pf "Part 1: "
    &p PartOne
    &pf "Part 2: "
    &p PartTwo
    
  • wer2@lemm.ee
    link
    fedilink
    arrow-up
    1
    ·
    6 days ago

    Lisp

    Not super happy with the code, but it got the job done.

    Part 1 and 2
    (defun p1-process-line (line)
      (to-symbols line))
    
    (defun found-word-h (word data i j)
      "checks for a word existing from the point horizontally to the right"
      (loop for j2 from j 
            for w in word
            when (not (eql w (aref data i j2)))
              return nil
            finally (return t)))
    
    (defun found-word-v (word data i j)
      "checks for a word existing from the point vertically down"
      (loop for i2 from i 
            for w in word
            when (not (eql w (aref data i2 j)))
              return nil
            finally (return t)))
    
    (defun found-word-d-l (word data i j)
      "checks for a word existsing from the point diagonally to the left and down"
      (destructuring-bind (n m) (array-dimensions data)
        (declare (ignorable n))
        
        (and (>= (- i (length word)) -1)
             (>= m (+ j  (length word)))
             (loop for i2 from i downto 0
                   for j2 from j
                   for w in word
                   when (not (eql w (aref data i2 j2)))
                     return nil
                   finally  (return t)))))
    
    (defun found-word-d-r (word data i j)
      "checks for a word existing from the point diagonally to the right and down"
      (destructuring-bind (n m) (array-dimensions data)
        (and (>= n (+ i (length word)))
             (>= m (+ j  (length word)))
             (loop for i2 from i
                   for j2 from j
                   for w in word
                   when (not (eql w (aref data i2 j2)))
                     return nil
                   finally  (return t)))
        ))
    
    (defun count-word-h (data word)
      "Counts horizontal matches of the word"
      (let ((word-r (reverse word))
            (word-l (length word)))
        (destructuring-bind (n m) (array-dimensions data)
          (loop for i from 0 below n 
                sum (loop for j from 0 upto (- m word-l)
                          count (found-word-h word data i j)
                          count (found-word-h word-r data i j))))))
    
    (defun count-word-v (data word)
      "Counts vertical matches of the word"
      (let ((word-r (reverse word))
            (word-l (length word)))
        (destructuring-bind (n m) (array-dimensions data)
          (loop for j from 0 below m 
                sum (loop for i from 0 upto (- n word-l)
                          count (found-word-v word data i j)
                          count (found-word-v word-r data i j))))))
    
    (defun count-word-d (data word)
      "Counts diagonal matches of the word"
      (let ((word-r (reverse word)))
        (destructuring-bind (n m) (array-dimensions data)
          (loop for i from 0 below n
                sum (loop for j from 0 below m
                          count (found-word-d-l word data i j)
                          count (found-word-d-l word-r data i j)
                          count (found-word-d-r word data i j)
                          count (found-word-d-r word-r data i j)
                          )))))
    
    
    (defun run-p1 (file)
      "cares about the word xmas in any direction"
      (let ((word '(X M A S))
            (data (list-to-2d-array (read-file file #'p1-process-line))))
        (+
         (count-word-v data word)
         (count-word-h data word)
         (count-word-d data word))))
    
    
    (defun run-p2 (file) 
      "cares about an x of mas crossed with mas"
      (let ((word '(M A S))
            (word-r '(S A M))
            (data (list-to-2d-array (read-file file #'p1-process-line))))
        (destructuring-bind (n m) (array-dimensions data)
          (loop for i from 0 below (- n 2)
                sum (loop for j from 0 below (- m 2)
                          count (and (found-word-d-r word data i j)
                                     (found-word-d-l word data (+ i 2) j))
                          count (and (found-word-d-r word-r data i j)
                                     (found-word-d-l word data (+ i 2) j))
                          count (and (found-word-d-r word data i j)
                                     (found-word-d-l word-r data (+ i 2) j))
                          count (and (found-word-d-r word-r data i j)
                                     (found-word-d-l word-r data (+ i 2) j))
                            )))))
    
  • hosaka@programming.dev
    link
    fedilink
    arrow-up
    1
    ·
    6 days ago

    Zig

    const std = @import("std");
    const List = std.ArrayList;
    
    const tokenizeScalar = std.mem.tokenizeScalar;
    const parseInt = std.fmt.parseInt;
    const print = std.debug.print;
    const eql = std.mem.eql;
    
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const alloc = gpa.allocator();
    
    const Point = struct {
        x: isize,
        y: isize,
        fn add(self: *const Point, point: *const Point) Point {
            return Point{ .x = self.x + point.x, .y = self.y + point.y };
        }
    };
    
    // note: i have no idea how to use this or if it's even possible
    // const DirectionType = enum(u8) { Up, Down, Left, Right, UpLeft, UpRight, DownLeft, DownRight };
    // const Direction = union(DirectionType) {
    //     up: Point = .{ .x = 0, .y = 0 },
    // };
    
    const AllDirections = [_]Point{
        .{ .x = 0, .y = -1 }, // up
        .{ .x = 0, .y = 1 }, // down
        .{ .x = -1, .y = 0 }, // left
        .{ .x = 1, .y = 0 }, // right
        .{ .x = -1, .y = -1 }, // up left
        .{ .x = 1, .y = -1 }, // up right
        .{ .x = -1, .y = 1 }, // down left
        .{ .x = 1, .y = 1 }, // down right
    };
    
    const Answer = struct {
        xmas: u32,
        mas: u32,
    };
    
    pub fn searchXmas(letters: List([]const u8), search_char: u8, position: Point, direction: Point) u32 {
        const current_char = getChar(letters, position);
        if (current_char == search_char) {
            const next = position.add(&direction);
            if (current_char == 'M') {
                return searchXmas(letters, 'A', next, direction);
            } else if (current_char == 'A') {
                return searchXmas(letters, 'S', next, direction);
            } else if (current_char == 'S') {
                return 1; // found all letters
            }
        }
        return 0;
    }
    
    pub fn countXmas(letters: List([]const u8), starts: List(Point)) u32 {
        var counter: u32 = 0;
        for (starts.items) |start| {
            for (AllDirections) |direction| {
                const next = start.add(&direction);
                counter += searchXmas(letters, 'M', next, direction);
            }
        }
        return counter;
    }
    
    pub fn countMas(letters: List([]const u8), starts: List(Point)) u32 {
        var counter: u32 = 0;
        for (starts.items) |start| {
            const a_char = getChar(letters, start) orelse continue;
            const top_left_char = getChar(letters, start.add(&AllDirections[4])) orelse continue;
            const down_right_char = getChar(letters, start.add(&AllDirections[7])) orelse continue;
            const top_right_char = getChar(letters, start.add(&AllDirections[5])) orelse continue;
            const down_left_char = getChar(letters, start.add(&AllDirections[6])) orelse continue;
    
            const tldr = [3]u8{ top_left_char, a_char, down_right_char };
            const trdl = [3]u8{ top_right_char, a_char, down_left_char };
            if ((eql(u8, &tldr, "MAS") or eql(u8, &tldr, "SAM")) and (eql(u8, &trdl, "MAS") or eql(u8, &trdl, "SAM"))) {
                counter += 1;
            }
        }
        return counter;
    }
    
    pub fn getChar(letters: List([]const u8), point: Point) ?u8 {
        if (0 > point.x or point.x >= letters.items.len) {
            return null;
        }
        const row = @as(usize, @intCast(point.x));
    
        if (0 > point.y or point.y >= letters.items[row].len) {
            return null;
        }
        const col = @as(usize, @intCast(point.y));
        return letters.items[row][col];
    }
    
    pub fn solve(input: []const u8) !Answer {
        var rows = tokenizeScalar(u8, input, '\n');
    
        var letters = List([]const u8).init(alloc);
        defer letters.deinit();
        var x_starts = List(Point).init(alloc);
        defer x_starts.deinit();
        var a_starts = List(Point).init(alloc);
        defer a_starts.deinit();
    
        var x: usize = 0;
        while (rows.next()) |row| {
            try letters.append(row);
            for (row, 0..) |letter, y| {
                if (letter == 'X') {
                    try x_starts.append(.{ .x = @intCast(x), .y = @intCast(y) });
                } else if (letter == 'A') {
                    try a_starts.append(.{ .x = @intCast(x), .y = @intCast(y) });
                }
            }
            x += 1;
        }
    
        // PART 1
        const xmas = countXmas(letters, x_starts);
    
        // PART 2
        const mas = countMas(letters, a_starts);
    
        return Answer{ .xmas = xmas, .mas = mas };
    }
    
    pub fn main() !void {
        const answer = try solve(@embedFile("input.txt"));
        print("Part 1: {d}\n", .{answer.xmas});
        print("Part 2: {d}\n", .{answer.mas});
    }
    
    test "test input" {
        const answer = try solve(@embedFile("test.txt"));
        try std.testing.expectEqual(18, answer.xmas);
    }
    
    
  • Deebster@programming.dev
    link
    fedilink
    arrow-up
    2
    ·
    edit-2
    7 days ago

    Rust

    I had a hunch about part two that didn’t pay off, so I over-coded this instead of just using an array of arrays.

    use std::{fs, str::FromStr};
    
    use color_eyre::eyre::{Report, Result};
    
    #[derive(Debug, Copy, Clone)]
    enum Direction {
        N,
        NE,
        E,
        SE,
        S,
        SW,
        W,
        NW,
    }
    
    impl Direction {
        fn all() -> &'static [Direction] {
            &[
                Direction::N,
                Direction::NE,
                Direction::E,
                Direction::SE,
                Direction::S,
                Direction::SW,
                Direction::W,
                Direction::NW,
            ]
        }
    }
    
    #[derive(Debug, PartialEq, Eq)]
    struct WordSearch {
        grid: Vec<char>,
        width: usize,
        height: usize,
    }
    
    impl FromStr for WordSearch {
        type Err = Report;
    
        fn from_str(s: &str) -> Result<Self, Self::Err> {
            let grid: Vec<_> = s.chars().filter(|&ch| ch != '\n').collect();
            let width = s
                .chars()
                .position(|ch| ch == '\n')
                .ok_or_else(|| Report::msg("grid width cannot be zero, or one line"))?;
            let height = grid.len() / width;
            Ok(Self {
                grid,
                width,
                height,
            })
        }
    }
    
    impl WordSearch {
        fn neighbour(&self, i: usize, dir: Direction) -> Option<usize> {
            let width = self.width;
            let length = self.grid.len();
            use Direction::*;
            match dir {
                N if i >= width => Some(i - width),
                NE if i >= width && i % width != width - 1 => Some(i - width + 1),
                E if i % width != width - 1 => Some(i + 1),
                SE if i + width + 1 < length && i % width != width - 1 => Some(i + width + 1),
                S if i + width < length => Some(i + width),
                SW if i + width - 1 < length && i % width != 0 => Some(i + width - 1),
                W if i % width != 0 => Some(i - 1),
                NW if i >= width && i % width != 0 => Some(i - width - 1),
                _ => None,
            }
        }
    
        fn word_count(&self, word: &str) -> Result<usize> {
            let mut found = 0;
            for i in 0..self.grid.len() {
                for dir in Direction::all() {
                    if self.word_present(word, i, *dir) {
                        found += 1;
                    }
                }
            }
            Ok(found)
        }
    
        fn x_count(&self) -> Result<usize> {
            let mut found = 0;
            for i in 0..self.grid.len() {
                if self.x_present(i) {
                    found += 1;
                }
            }
            Ok(found)
        }
    
        fn word_present(&self, word: &str, location: usize, dir: Direction) -> bool {
            let mut next = Some(location);
            for ch in word.chars() {
                let i = if let Some(i) = next {
                    i
                } else {
                    // Off the edge
                    return false;
                };
    
                if self.grid[i] != ch {
                    return false;
                }
                next = self.neighbour(i, dir);
            }
            true
        }
    
        fn x_present(&self, location: usize) -> bool {
            if self.grid.get(location) != Some(&'A') {
                return false;
            }
            let diags = [
                (Direction::NE, Direction::SW),
                (Direction::NW, Direction::SE),
            ];
            diags.iter().all(|(dir_a, dir_b)| {
                let Some(a_idx) = self.neighbour(location, *dir_a) else {
                    return false;
                };
                let Some(b_idx) = self.neighbour(location, *dir_b) else {
                    return false;
                };
                let a = self.grid[a_idx];
                let b = self.grid[b_idx];
                (a == 'M' && b == 'S') || (b == 'M' && a == 'S')
            })
        }
    }
    
    fn part1(filepath: &str) -> Result<usize> {
        let input = fs::read_to_string(filepath)?;
        let grid = WordSearch::from_str(&input)?;
        grid.word_count("XMAS")
    }
    
    fn part2(filepath: &str) -> Result<usize> {
        let input = fs::read_to_string(filepath)?;
        let grid = WordSearch::from_str(&input)?;
        grid.x_count()
    }
    
    fn main() -> Result<()> {
        color_eyre::install()?;
    
        println!("Part 1: {}", part1("d04/input.txt")?);
        println!("Part 2: {}", part2("d04/input.txt")?);
        Ok(())
    }
    
  • aurele
    link
    fedilink
    arrow-up
    1
    ·
    6 days ago

    Elixir

    defmodule AdventOfCode.Solution.Year2024.Day04 do
      use AdventOfCode.Solution.SharedParse
    
      defmodule Map do
        defstruct [:chars, :width, :height]
      end
    
      @impl true
      def parse(input) do
        chars = String.split(input, "\n", trim: true) |> Enum.map(&String.codepoints/1)
        %Map{chars: chars, width: length(Enum.at(chars, 0)), height: length(chars)}
      end
    
      def at(%Map{} = map, x, y) do
        cond do
          x < 0 or x >= map.width or y < 0 or y >= map.height -> ""
          true -> map.chars |> Enum.at(y, []) |> Enum.at(x, "")
        end
      end
    
      def part1(map) do
        dirs = for dx <- -1..1, dy <- -1..1, {dx, dy} != {0, 0}, do: {dx, dy}
        xmas = String.codepoints("XMAS") |> Enum.with_index() |> Enum.drop(1)
    
        for x <- 0..(map.width - 1),
            y <- 0..(map.height - 1),
            "X" == at(map, x, y),
            {dx, dy} <- dirs,
            xmas
            |> Enum.all?(fn {c, n} -> at(map, x + dx * n, y + dy * n) == c end),
            reduce: 0 do
          t -> t + 1
        end
      end
    
      def part2(map) do
        for x <- 0..(map.width - 1),
            y <- 0..(map.height - 1),
            "A" == at(map, x, y),
            (at(map, x - 1, y - 1) <> at(map, x + 1, y + 1)) in ["MS", "SM"],
            (at(map, x - 1, y + 1) <> at(map, x + 1, y - 1)) in ["MS", "SM"],
            reduce: 0 do
          t -> t + 1
        end
      end
    end
    
  • morrowind@lemmy.ml
    link
    fedilink
    arrow-up
    1
    ·
    7 days ago

    Smalltalk

    I could have done it in 2 fns if I made them more generic, but couldn’t be bothered

    day4p1: input
        | lines sum w h|
        
        sum := ('XMAS' asRegex matchesIn: input) size. "forward"
        sum := sum + ('SAMX' asRegex matchesIn: input) size. "backwards sep cause overlapping"
        
        lines := input lines.
        h := lines size.
        w := (lines at: 1) size.
        
        1 to: h-3 do: [ :p1 |
            1 to: w do: [ :p2 |
                sum := sum + (self d4diag: lines p1: p1 p2: p2 dir: -1).
                sum := sum + (self d4diag: lines p1: p1 p2: p2 dir: 0).
                sum := sum + (self d4diag: lines p1: p1 p2: p2 dir: 1).    
            ]
        ].
        
        ^ sum.
    
    d4diag: input p1: p1 p2: p2 dir: dir
        | reverse xm ii |
        
        xm := 'XMAS'.
        
        reverse := ((input at: p1) at: p2) = $S.
        
        0 to: 3 do: [ :i |
            ii := reverse ifTrue: [ 4 - i ] ifFalse: [ i + 1 ].
                                                        "if out of bounds, obv not possible"
            ((xm at: ii) = ((input at: p1 + i) at: i * dir + p2 ifAbsent: [^ 0])) ifFalse: [^ 0]
        ].
    
        ^ 1.
    

    Part 2

    day4p2: input
        | lines sum w h pos |
        "Find all diag mas, then check of 1 . -1 (we can look back on every -1"
        
        sum := 0.
        lines := input lines.
        h := lines size.
        w := (lines at: 1) size.
        
        1 to: h-2 do: [ :p1 |
            pos := Array new: w withAll: false.
            1 to: w do: [ :p2 |
                (self d42: lines p1: p1 p2: p2 dir: -1)
                    ifTrue: [
                        sum := sum + ((pos at: p2-2) ifTrue:[1] ifFalse:[0]).
                    ].
                
                (self d42: lines p1: p1 p2: p2 dir: 1) ifTrue: [pos at: p2 put: true].
            ]
        ].
        
        ^ sum.
    
    d42: input p1: p1 p2: p2 dir: dir
        | reverse xm ii |
        
        xm := 'MAS'.
        
        reverse := ((input at: p1) at: p2) = $S.
        
        0 to: 2 do: [ :i |
            ii := reverse ifTrue: [ 3 - i ] ifFalse: [ i + 1 ].
                                                        "if out of bounds, obv not possible"
            ((xm at: ii) = ((input at: p1 + i) at: i * dir + p2 ifAbsent: [^ false])) ifFalse: [^ false]
        ].
    
        ^ true.
    
  • Sleepless One@lemmy.ml
    link
    fedilink
    English
    arrow-up
    1
    ·
    7 days ago

    Rust

    I’m a day behind on this one due to a lot of work with my job and school.

    use std::iter::zip;
    
    use crate::utils::read_lines;
    
    pub fn solution1() {
        let puzzle = read_puzzle();
    
        let horizontal_sum = puzzle
            .iter()
            .map(|line| {
                line.windows(4)
                    .filter(|window| {
                        matches!(window, [b'X', b'M', b'A', b'S'] | [b'S', b'A', b'M', b'X'])
                    })
                    .count() as u32
            })
            .sum::<u32>();
        let vertical_and_diagonal_sum = puzzle
            .windows(4)
            .map(|window| {
                count_xmas(window, (0, 0, 0, 0))
                    + count_xmas(window, (0, 1, 2, 3))
                    + count_xmas(window, (3, 2, 1, 0))
            })
            .sum::<u32>();
    
        println!(
            "XMAS count = {}",
            horizontal_sum + vertical_and_diagonal_sum
        );
    }
    
    pub fn solution2() {
        let puzzle = read_puzzle();
    
        let sum = puzzle
            .windows(3)
            .map(|window| {
                zip(
                    window[0].windows(3),
                    zip(window[1].windows(3), window[2].windows(3)),
                )
                .map(|(a, (b, c))| (a, b, c))
                .filter(|tuple| {
                    matches!(
                        tuple,
                        ([b'M', _, b'M'], [_, b'A', _], [b'S', _, b'S'])
                            | ([b'S', _, b'M'], [_, b'A', _], [b'S', _, b'M'])
                            | ([b'M', _, b'S'], [_, b'A', _], [b'M', _, b'S'])
                            | ([b'S', _, b'S'], [_, b'A', _], [b'M', _, b'M'])
                    )
                })
                .count() as u32
            })
            .sum::<u32>();
    
        println!("X-MAS count = {sum}");
    }
    
    fn count_xmas(
        window: &[Vec<u8>],
        (skip0, skip1, skip2, skip3): (usize, usize, usize, usize),
    ) -> u32 {
        zip(
            window[0].iter().skip(skip0),
            zip(
                window[1].iter().skip(skip1),
                zip(window[2].iter().skip(skip2), window[3].iter().skip(skip3)),
            ),
        )
        .map(|(a, (b, (c, d)))| (a, b, c, d))
        .filter(|tup| matches!(tup, (b'X', b'M', b'A', b'S') | (b'S', b'A', b'M', b'X')))
        .count() as u32
    }
    
    fn read_puzzle() -> Vec<Vec<u8>> {
        read_lines("src/day4/input.txt")
            .map(|line| line.into_bytes())
            .collect()
    }
    

    The standard library windows method and pattern matching have been carrying me this year so far.

  • Chais
    link
    fedilink
    arrow-up
    1
    ·
    edit-2
    7 days ago

    Python

    Essentially I’m extracting strings from the word search and compare them to the desired value. For part one that means extracting from an X in eight directions. Because I’m reading from the central X outwards, I don’t need to reverse any of them.
    Part two reads two strings in an X-shape around the coordinates of each X. The resulting strings are filtered down to include only “MAS” and “SAM”. If there are exactly two strings we found an X-MAS.

    from pathlib import Path
    
    
    def parse_input(input: str) -> list[str]:
        return input.strip().splitlines()
    
    
    def extract_strings_one(m: int, n: int, haystack: list[str], l: int = 4) -> list[str]:
        result = []
        # Right
        if m + l <= len(haystack[n]):
            result.append(haystack[n][m : m + l])
        # Up-Right
        if m + l <= len(haystack[n]) and n > l - 2:
            result.append("".join([haystack[n - i][m + i] for i in range(l)]))
        # Up
        if n > l - 2:
            result.append("".join([haystack[n - i][m] for i in range(l)]))
        # Up-Left
        if m > l - 2 and n > l - 2:
            result.append("".join([haystack[n - i][m - i] for i in range(l)]))
        # Left
        if m > l - 2:
            result.append("".join([haystack[n][m - i] for i in range(l)]))
        # Down-Left
        if m > l - 2 and n + l <= len(haystack):
            result.append("".join([haystack[n + i][m - i] for i in range(l)]))
        # Down
        if n + l <= len(haystack):
            result.append("".join([haystack[n + i][m] for i in range(l)]))
        # Down-Right
        if m + l <= len(haystack[n]) and n + l <= len(haystack):
            result.append("".join([haystack[n + i][m + i] for i in range(l)]))
        return result
    
    
    def extract_strings_two(m: int, n: int, haystack: list[str], d: int = 1) -> list[str]:
        result = []
        if 0 <= m - d and m + d < len(haystack[n]) and 0 <= n - d and n + d < len(haystack):
            result.append("".join([haystack[n + i][m + i] for i in range(-d, d + 1)]))
            result.append("".join([haystack[n - i][m + i] for i in range(-d, d + 1)]))
        return result
    
    
    def part_one(input: str) -> int:
        lines = parse_input(input)
        xmas_count = 0
        for i, line in enumerate(lines):
            x = line.find("X", 0)
            while x != -1:
                xmas_count += len(
                    list(filter(lambda s: s == "XMAS", extract_strings_one(x, i, lines)))
                )
                x = line.find("X", x + 1)
        return xmas_count
    
    
    def part_two(input: str) -> int:
        lines = parse_input(input)
        x_mas_count = 0
        for i, line in enumerate(lines[1:-1], 1):
            a = line.find("A", 0)
            while a != -1:
                if (
                    len(
                        list(
                            filter(
                                lambda s: s in ("MAS", "SAM"),
                                extract_strings_two(a, i, lines),
                            )
                        )
                    )
                    == 2
                ):
                    x_mas_count += 1
                a = line.find("A", a + 1)
        return x_mas_count
    
    
    if __name__ == "__main__":
        input = Path("input").read_text("utf-8")
        print(part_one(input))
        print(part_two(input))
    
  • mykl@lemmy.world
    link
    fedilink
    arrow-up
    4
    ·
    edit-2
    8 days ago

    Uiua

    Just part1 for now as I need to walk the dog :-)

    [edit] Part 2 now added, and a nicer approach than Part 1 in my opinion, if you’re able to keep that many dimensions straight in your head :-)

    [edit 2] Tightened it up a bit more.

    Grid ← ⊜∘⊸≠@\n "MMMSXXMASM\nMSAMXMSMSA\nAMXSXMAAMM\nMSAMASMSMX\nXMASAMXAMM\nXXAMMXXAMA\nSMSMSASXSS\nSAXAMASAAA\nMAMMMXMMMM\nMXMXAXMASX"
    
    ≡⍉⍉×⇡4¤[1_0 0_1 1_1 11]         # Use core dirs to build sets of 4-offsets.
    ↯∞_2⇡△ Grid                       # Get all possible starting points.
    &p/+♭⊞(+∩(≍"XMAS")⇌.⬚@.⊡:Grid≡+¤) # Part 1. Join the two into a table, use to pick 4-elements, check, count.
    
    Diags   ← [[¯. 1_1] [¯. 11]]
    BothMas ← /×≡(+∩(≍"MS")⇌.)⬚@.⊡≡+Diags¤¤ # True if both diags here are MAS.
    &p/+≡BothMas⊚="A"⟜¤Grid                 # Part 2. For all "A"s in grid, check diags, count where good.
    
      • mykl@lemmy.world
        link
        fedilink
        arrow-up
        2
        ·
        8 days ago

        The operators have all got ascii names you can type, and the formatter converts them to the symbols. It’s a bit odd but really worthwhile, as you get access to the powerful array handling functionality that made solving today’s challenges so much more straightforward than in other languages.

  • lwhjp@lemmy.sdf.org
    link
    fedilink
    arrow-up
    4
    ·
    8 days ago

    Haskell

    Popular language this year :)

    I got embarrassingly stuck on this one trying to be clever with list operations. Then I realized I should just use an array…

    import Data.Array.Unboxed (UArray)
    import Data.Array.Unboxed qualified as A
    import Data.Bifunctor
    
    readInput :: String -> UArray (Int, Int) Char
    readInput s =
      let rows = lines s
          n = length rows
       in A.listArray ((1, 1), (n, n)) $ concat rows
    
    s1 `eq` s2 = s1 == s2 || s1 == reverse s2
    
    part1 arr = length $ filter isXmas $ concatMap lines $ A.indices arr
      where
        isXmas ps = all (A.inRange $ A.bounds arr) ps && map (arr A.!) ps `eq` "XMAS"
        lines p = [take 4 $ iterate (bimap (+ di) (+ dj)) p | (di, dj) <- [(1, 0), (0, 1), (1, 1), (1, -1)]]
    
    part2 arr = length $ filter isXmas innerPoints
      where
        innerPoints =
          let ((i1, j1), (i2, j2)) = A.bounds arr
           in [(i, j) | i <- [i1 + 1 .. i2 - 1], j <- [j1 + 1 .. j2 - 1]]
        isXmas p = up p `eq` "MAS" && down p `eq` "MAS"
        up (i, j) = map (arr A.!) [(i + 1, j - 1), (i, j), (i - 1, j + 1)]
        down (i, j) = map (arr A.!) [(i - 1, j - 1), (i, j), (i + 1, j + 1)]
    
    main = do
      input <- readInput <$> readFile "input04"
      print $ part1 input
      print $ part2 input
    
  • janAkali@lemmy.one
    link
    fedilink
    English
    arrow-up
    4
    ·
    edit-2
    8 days ago

    Nim

    Could be done more elegantly, but I haven’t bothered yet.

    proc solve(input: string): AOCSolution[int, int] =
      var lines = input.splitLines()
    
      block p1:
        # horiz
        for line in lines:
          for i in 0..line.high-3:
            if line[i..i+3] in ["XMAS", "SAMX"]:
              inc result.part1
    
        for y in 0..lines.high-3:
          #vert
          for x in 0..lines[0].high:
            let word = collect(for y in y..y+3: lines[y][x])
            if word in [@"XMAS", @"SAMX"]:
              inc result.part1
    
          #diag \
          for x in 0..lines[0].high-3:
            let word = collect(for d in 0..3: lines[y+d][x+d])
            if word in [@"XMAS", @"SAMX"]:
              inc result.part1
    
          #diag /
          for x in 3..lines[0].high:
            let word = collect(for d in 0..3: lines[y+d][x-d])
            if word in [@"XMAS", @"SAMX"]:
              inc result.part1
    
      block p2:
        for y in 0..lines.high-2:
          for x in 0..lines[0].high-2:
            let diagNW = collect(for d in 0..2: lines[y+d][x+d])
            let diagNE = collect(for d in 0..2: lines[y+d][x+2-d])
            if diagNW in [@"MAS", @"SAM"] and diagNE in [@"MAS", @"SAM"]:
              inc result.part2
    

    Codeberg repo

  • VegOwOtenks@lemmy.world
    link
    fedilink
    arrow-up
    4
    ·
    8 days ago

    I struggled a lot more when doing list slices that I would’ve liked to

    Haskell

    
    import Data.List qualified as List
    
    collectDiagonal :: [String] -> Int -> Int -> String
    collectDiagonal c y x
            | length c > y && length (c !! y) > x = c !! y !! x : collectDiagonal c (y+1) (x+1)
            | otherwise = []
    
    part1 c = do
            let forwardXMAS  = map (length . filter (List.isPrefixOf "XMAS") . List.tails) $ c
            let backwardXMAS = map (length . filter (List.isPrefixOf "XMAS") . List.tails . reverse) $ c
            let downwardXMAS  = map (length . filter (List.isPrefixOf "XMAS") . List.tails ) . List.transpose $ c
            let upwardXMAS = map (length . filter (List.isPrefixOf "XMAS") . List.tails . reverse ) . List.transpose $ c
            let leftSideDiagonals = map (\ y -> collectDiagonal c y 0) [0..length c]
            let leftTopDiagonals = map (\ x -> collectDiagonal c 0 x) [1..(length . List.head $ c)]
            let leftDiagonals = leftSideDiagonals ++ leftTopDiagonals
            let rightSideDiagonals = map (\ y -> collectDiagonal (map List.reverse c) y 0) [0..length c]
            let rightTopDiagonals = map (\ x -> collectDiagonal (map List.reverse c) 0 x) [1..(length . List.head $ c)]
            let rightDiagonals = rightSideDiagonals ++ rightTopDiagonals
            let diagonals = leftDiagonals ++ rightDiagonals
    
            let diagonalXMAS = map (length . filter (List.isPrefixOf "XMAS") . List.tails) $ diagonals
            let reverseDiagonalXMAS = map (length . filter (List.isPrefixOf "XMAS") . List.tails . reverse) $ diagonals
    
            print . sum $ [sum forwardXMAS, sum backwardXMAS, sum downwardXMAS, sum upwardXMAS, sum diagonalXMAS, sum reverseDiagonalXMAS]
            return ()
    
    getBlock h w c y x = map (take w . drop x) . take h . drop y $ c
    
    isXBlock b = do
            let diagonal1 = collectDiagonal b 0 0
            let diagonal2 = collectDiagonal (map List.reverse b) 0 0
    
            diagonal1 `elem` ["SAM", "MAS"] && diagonal2 `elem` ["SAM", "MAS"]
    
    part2 c = do
            
            let lineBlocks = List.map (getBlock 3 3 c) [0..length c - 1]
            let groupedBlocks = List.map (flip List.map [0..(length . head $ c) - 1]) lineBlocks
    
            print . sum . map (length . filter isXBlock) $ groupedBlocks
    
            return ()
    
    main = do
            c <- lines <$> getContents
    
            part1 c
            part2 c
    
            return ()
    
  • LeixB@lemmy.world
    link
    fedilink
    arrow-up
    3
    ·
    edit-2
    8 days ago

    Haskell

    import Control.Arrow
    import Data.Array.Unboxed
    import Data.List
    
    type Pos = (Int, Int)
    type Board = Array Pos Char
    data Dir = N | NE | E | SE | S | SW | W | NW
    
    target = "XMAS"
    
    parse s = listArray ((1, 1), (n, m)) [l !! i !! j | i <- [0 .. n - 1], j <- [0 .. m - 1]]
      where
        l = lines s
        (n, m) = (length $ head l, length l)
    
    move N = first pred
    move S = first succ
    move E = second pred
    move W = second succ
    move NW = move N . move W
    move SW = move S . move W
    move NE = move N . move E
    move SE = move S . move E
    
    check :: Board -> Pos -> Int -> Dir -> Bool
    check b p i d =
        i >= length target
            || ( inRange (bounds b) p
                    && (b ! p) == (target !! i)
                    && check b (move d p) (succ i) d
               )
    
    checkAllDirs :: Board -> Pos -> Int
    checkAllDirs b p = length . filter (check b p 0) $ [N, NE, E, SE, S, SW, W, NW]
    
    check2 :: Board -> Pos -> Bool
    check2 b p =
        all (inRange (bounds b)) moves && ((b ! p) == 'A') && ("SSMM" `elem` rotations)
      where
        rotations = rots $ (b !) <$> moves
        moves = flip move p <$> [NE, SE, SW, NW]
    
        rots xs = init $ zipWith (++) (tails xs) (inits xs)
    
    part1 b = sum $ checkAllDirs b <$> indices b
    part2 b = length . filter (check2 b) $ indices b
    
    main = getContents >>= print . (part1 &&& part2) . parse
    
  • Andy@programming.dev
    link
    fedilink
    arrow-up
    2
    ·
    edit-2
    5 days ago

    Factor

    spoiler
    : get-input ( -- rows )
      "vocab:aoc-2024/04/input.txt" utf8 file-lines ;
    
    : verticals ( rows -- lines )
      [ dimension last [0..b) ] keep cols ;
    
    : slash-origins ( rows -- coords )
      dimension
      [ first [0..b) [ 0 2array ] map ] [
        first2 [ 1 - ] [ 1 (a..b] ] bi*
        [ 2array ] with map
      ] bi append ;
    
    : backslash-origins ( rows -- coords )
      dimension first2
      [ [0..b) [ 0 2array ] map ]
      [ 1 (a..b] [ 0 swap 2array ] map ] bi* append ;
    
    : slash ( rows origin -- line )
      first2
      [ 0 [a..b] ]
      [ pick dimension last [a..b) ] bi* zip
      swap matrix-nths ;
    
    : backslash ( rows origin -- line )
      [ dup dimension ] dip first2
      [ over first [a..b) ]
      [ pick last [a..b) ] bi* zip nip
      swap matrix-nths ;
    
    : slashes ( rows -- lines )
      dup slash-origins
      [ slash ] with map ;
    
    : backslashes ( rows -- lines )
      dup backslash-origins
      [ backslash ] with map ;
    
    : word-count ( line word -- n )
      dupd [ reverse ] dip
      '[ _ subseq-indices length ] bi@ + ;
    
    : part1 ( -- n )
      get-input
      { [ ] [ verticals ] [ slashes ] [ backslashes ] } cleave-array concat
      [ "XMAS" word-count ] map-sum ;
    
    : origin-adistances ( rows origins line-quot: ( rows origin -- line ) -- origin-adistances-assoc )
      with zip-with
      "MAS" "SAM" [ '[ [ _ subseq-indices ] map-values ] ] bi@ bi append
      harvest-values
      [ [ 1 + ] map ] map-values ; inline
    
    : a-coords ( origin-adistances coord-quot: ( adistance -- row-delta col-delta ) -- coords )
      '[ first2 [ @ 2array v+ ] with map ] map-concat ; inline
    
    : slash-a-coords ( rows -- coords )
      dup slash-origins [ slash ] origin-adistances
      [ [ 0 swap - ] keep ] a-coords ;
    
    : backslash-a-coords ( rows -- coords )
      dup backslash-origins [ backslash ] origin-adistances
      [ dup ] a-coords ;
    
    : part2 ( -- n )
      get-input [ slash-a-coords ] [ backslash-a-coords ] bi
      intersect length ;
    

    Better viewed on GitHub.

  • sjmulder@lemmy.sdf.org
    link
    fedilink
    arrow-up
    2
    ·
    8 days ago

    C

    What can I say, bunch of for loops! I add a 3 cell border to avoid having to do bounds checking in the inner loops.

    Code
    #include "common.h"
    #define GZ 146
    
    int main(int argc, char **argv) {
    	static char g[GZ][GZ];
    	static const char w[] = "XMAS";
    	int p1=0,p2=0, x,y, m,i;
    
    	if (argc > 1) DISCARD(freopen(argv[1], "r", stdin));
    	for (y=3; y<GZ && fgets(g[y]+3, GZ-3, stdin); y++) ;
    
    	for (y=3; y<GZ-3; y++)
    	for (x=3; x<GZ-3; x++) {
    		for (m=1,i=0; i<4; i++) {m &= g[y+i][x]==w[i];} p1+=m;
    		for (m=1,i=0; i<4; i++) {m &= g[y-i][x]==w[i];} p1+=m;
    		for (m=1,i=0; i<4; i++) {m &= g[y][x+i]==w[i];} p1+=m;
    		for (m=1,i=0; i<4; i++) {m &= g[y][x-i]==w[i];} p1+=m;
    		for (m=1,i=0; i<4; i++) {m &= g[y+i][x+i]==w[i];} p1+=m;
    		for (m=1,i=0; i<4; i++) {m &= g[y-i][x-i]==w[i];} p1+=m;
    		for (m=1,i=0; i<4; i++) {m &= g[y+i][x-i]==w[i];} p1+=m;
    		for (m=1,i=0; i<4; i++) {m &= g[y-i][x+i]==w[i];} p1+=m;
    
    		p2 += g[y+1][x+1]=='A' &&
    		      ((g[y][x]  =='M' && g[y+2][x+2]=='S')  ||
    		       (g[y][x]  =='S' && g[y+2][x+2]=='M')) &&
    		      ((g[y+2][x]=='M' && g[y][x+2]  =='S')  ||
    		       (g[y+2][x]=='S' && g[y][x+2]  =='M'));
    	}
    
    	printf("04: %d %d\n", p1, p2);
    }
    

    https://github.com/sjmulder/aoc/blob/master/2024/c/day04.c