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

  • Rin@lemm.ee
    link
    fedilink
    arrow-up
    1
    ·
    3 hours ago

    This one was a little bit of a pain. I loved it.

    TypeScript

    Solution
    import { AdventOfCodeSolutionFunction } from "./solutions";
    
    enum Direction {
        UP,
        UP_RIGHT,
        RIGHT,
        BOTTOM_RIGHT,
        BOTTOM,
        BOTTOM_LEFT,
        LEFT,
        UP_LEFT,
    };
    
    const ALL_DIRECTIONS = [
        Direction.RIGHT,
        Direction.BOTTOM_RIGHT,
        Direction.BOTTOM,
        Direction.BOTTOM_LEFT,
        Direction.LEFT,
        Direction.UP_LEFT,
        Direction.UP,
        Direction.UP_RIGHT,
    ];
    
    const check_coords = (grid: Array<Array<string>>, x: number, y: number) => {
        return y >= grid.length ||
            y < 0 ||
            x >= grid[y].length ||
            x < 0
    }
    
    const search_direction = (grid: Array<Array<string>>, x: number, y: number, direction: Direction, find: Array<string>) => {
        // exit conditions
        // no more to find
        if (find.length == 0)
            return 1; // found the end
    
        // invalid coords
        if (check_coords(grid, x, y))
            return 0;
    
        // make new mutable list
        const newFind = [...find];
        const searchChar = newFind.shift();
    
        // wrong character
        if (grid[y][x] !== searchChar)
            return 0;
    
        switch (direction) {
            case Direction.UP:
                return search_direction(grid, x, y + 1, direction, newFind);
    
            case Direction.UP_RIGHT:
                return search_direction(grid, x + 1, y + 1, direction, newFind);
    
            case Direction.RIGHT:
                return search_direction(grid, x + 1, y, direction, newFind);
    
            case Direction.BOTTOM_RIGHT:
                return search_direction(grid, x + 1, y - 1, direction, newFind);
    
            case Direction.BOTTOM:
                return search_direction(grid, x, y - 1, direction, newFind);
    
            case Direction.BOTTOM_LEFT:
                return search_direction(grid, x - 1, y - 1, direction, newFind);
    
            case Direction.LEFT:
                return search_direction(grid, x - 1, y, direction, newFind);
    
            case Direction.UP_LEFT:
                return search_direction(grid, x - 1, y + 1, direction, newFind);
    
            default:
                return 0;
        }
    }
    
    const part_1_search = (grid: Array<Array<string>>, x: number, y: number, find: Array<string>) => {
        return ALL_DIRECTIONS.reduce<number>(
            (instances, direction) =>
                instances + search_direction(grid, x, y, direction, find),
            0
        );
    }
    
    const part_2_search = (grid: Array<Array<string>>, x: number, y: number, find: Array<string>) => {
        return (
            search_direction(grid, x - 1, y + 1, Direction.BOTTOM_RIGHT, find) +
            search_direction(grid, x + 1, y + 1, Direction.BOTTOM_LEFT, find) +
            search_direction(grid, x - 1, y - 1, Direction.UP_RIGHT, find) +
            search_direction(grid, x + 1, y - 1, Direction.UP_LEFT, find)
        ) == 2 ? 1 : 0;
    }
    
    export const solution_4: AdventOfCodeSolutionFunction = (input) => {
        const grid = input.split("\n").map(st => st.trim()).map(v => v.split(""));
    
        let part_1 = 0;
        let part_2 = 0;
    
        const find_1 = "XMAS".split("");
        const find_2 = "MAS".split("");
    
        for (let y = 0; y < grid.length; y++) {
            for (let x = 0; x < grid[y].length; x++) {
                part_1 += part_1_search(grid, x, y, find_1);
                part_2 += part_2_search(grid, x, y, find_2);
            }
        }
    
        return {
            part_1,
            part_2,
        };
    }
    

    Felt like this code quality is better than what I usually output :)

  • ystael@beehaw.org
    link
    fedilink
    arrow-up
    1
    ·
    edit-2
    4 hours ago

    J

    Unsurprisingly this is the kind of problem that J is really good at. The dyadic case (table) of the adverb / is doing all the heavy lifting here: it makes a higher rank tensor by traversing items of the specified rank on each side and combining them according to the remaining frame of each side’s shape. The hard part is arranging the arguments so that your resulting matrix has its axes in the correct order.

    data_file_name =: '4.data'
    
    NB. cutopen yields boxed lines, so unbox them and ravel items to make a letter matrix
    grid =: ,. > cutopen fread data_file_name
    NB. pad the grid on every side with #'XMAS' - 1 spaces
    hpadded_grid =: (('   ' &amp; ,) @: (, &amp; '   '))"1 grid
    padded_grid =: (3 1 $ ' ') , hpadded_grid , (3 1 $ ' ')
    NB. traversal vectors
    directions =: 8 2 $ 1 0 1 1 0 1 _1 1 _1 0 _1 _1 0 _1 1 _1
    NB. rpos cpos matches rdir cdir if the string starting at rpos cpos in
    NB. direction rdir cdir is the string we want
    matches =: 4 : 0
    */ ,'XMAS' -: padded_grid {~ &lt;"1 x +"1 y *"1 0 i. 4
    )"1
    positions =: (3 + i. 0 { $ grid) ,"0/ (3 + i. 1 { $ grid)
    result1 =: +/, positions matches/ directions
    
    NB. pairs of traversal vectors
    x_directions =: 4 2 2 $ 1 1 _1 1 1 1 1 _1 _1 _1 _1 1 _1 _1 1 _1
    NB. rpos cpos x_matches 2 2 $ rdir1 cdir1 rdir2 cdir2 if there is an 'A' at
    NB. rpos cpos and the string in each of dir1 and dir2 centered at rpos cpos
    NB. is the string we want
    x_matches =: 4 : 0
    NB. (2 2 $ rdir1 cdir1 rdir2 cdir2) *"1 0/ (_1 + i.3) yields a matrix
    NB. 2 3 $ (_1 * dir1) , (0 * dir1) , (1 * dir1) followed by the same for dir2
    */ ,'MAS' -:"1 padded_grid {~ &lt;"1 x +"1 y *"1 0/ _1 + i. 3
    )"1 2
    result2 =: +/, positions x_matches/ x_directions
    
  • reboot6675@sopuli.xyz
    link
    fedilink
    arrow-up
    1
    ·
    4 hours ago

    Go

    Just a bunch of ifs and bounds checking. Part 2 was actually simpler.

    Code
    func part1(W [][]rune) {
    	m := len(W)
    	n := len(W[0])
    	xmasCount := 0
    
    	for i := 0; i < m; i++ {
    		for j := 0; j < n; j++ {
    			if W[i][j] != 'X' {
    				continue
    			}
    			if j < n-3 && W[i][j+1] == 'M' && W[i][j+2] == 'A' && W[i][j+3] == 'S' {
    				// Horizontal left to right
    				xmasCount++
    			}
    			if j >= 3 && W[i][j-1] == 'M' && W[i][j-2] == 'A' && W[i][j-3] == 'S' {
    				// Horizontal right to left
    				xmasCount++
    			}
    			if i < m-3 && W[i+1][j] == 'M' && W[i+2][j] == 'A' && W[i+3][j] == 'S' {
    				// Vertical up to down
    				xmasCount++
    			}
    			if i >= 3 && W[i-1][j] == 'M' && W[i-2][j] == 'A' && W[i-3][j] == 'S' {
    				// Vertical down to up
    				xmasCount++
    			}
    			if j < n-3 && i < m-3 && W[i+1][j+1] == 'M' && W[i+2][j+2] == 'A' && W[i+3][j+3] == 'S' {
    				// Diagonal left to right and up to down
    				xmasCount++
    			}
    			if j >= 3 && i < m-3 && W[i+1][j-1] == 'M' && W[i+2][j-2] == 'A' && W[i+3][j-3] == 'S' {
    				// Diagonal right to left and up to down
    				xmasCount++
    			}
    			if j < n-3 && i >= 3 && W[i-1][j+1] == 'M' && W[i-2][j+2] == 'A' && W[i-3][j+3] == 'S' {
    				// Diagonal left to right and down to up
    				xmasCount++
    			}
    			if j >= 3 && i >= 3 && W[i-1][j-1] == 'M' && W[i-2][j-2] == 'A' && W[i-3][j-3] == 'S' {
    				// Diagonal right to left and down to up
    				xmasCount++
    			}
    		}
    	}
    
    	fmt.Println(xmasCount)
    }
    
    func part2(W [][]rune) {
    	m := len(W)
    	n := len(W[0])
    	xmasCount := 0
    
    	for i := 0; i <= m-3; i++ {
    		for j := 0; j <= n-3; j++ {
    			if W[i+1][j+1] != 'A' {
    				continue
    			}
    			if W[i][j] == 'M' && W[i][j+2] == 'M' && W[i+2][j] == 'S' && W[i+2][j+2] == 'S' {
    				xmasCount++
    			} else if W[i][j] == 'M' && W[i][j+2] == 'S' && W[i+2][j] == 'M' && W[i+2][j+2] == 'S' {
    				xmasCount++
    			} else if W[i][j] == 'S' && W[i][j+2] == 'S' && W[i+2][j] == 'M' && W[i+2][j+2] == 'M' {
    				xmasCount++
    			} else if W[i][j] == 'S' && W[i][j+2] == 'M' && W[i+2][j] == 'S' && W[i+2][j+2] == 'M' {
    				xmasCount++
    			}
    		}
    	}
    
    	fmt.Println(xmasCount)
    }
    
    func main() {
    	file, _ := os.Open("input.txt")
    	defer file.Close()
    	scanner := bufio.NewScanner(file)
    
    	var W [][]rune
    	for scanner.Scan() {
    		line := scanner.Text()
    		W = append(W, []rune(line))
    	}
    
    	part1(W)
    	part2(W)
    }
    
  • vole@lemmy.world
    link
    fedilink
    English
    arrow-up
    1
    ·
    5 hours ago

    Raku

    Oof, my struggle to make custom index walking paths for part 1 did not pay off for part 2.

    Solution
    sub MAIN($input) {
        my $file = (open $input).slurp;
        my @grid is List = $file.lines».comb».list;
        my @transposedGrid is List = [Z] @grid;
        my @reversedGrid is List = @grid».reverse;
        my @transposedReversedGrid is List = @transposedGrid».reverse;
    
        my @horizontalScanRows is List = generateScanHorizontal(@grid);
        my @transposedHorizontalScanRows is List = generateScanHorizontal(@transposedGrid);
    
        my @part-one-counts = [];
        @part-one-counts.push(count-xmas(@grid, @horizontalScanRows)); # Right
        @part-one-counts.push(count-xmas(@transposedGrid, @transposedHorizontalScanRows)); # Down
        @part-one-counts.push(count-xmas(@reversedGrid, @horizontalScanRows)); # Left
        @part-one-counts.push(count-xmas(@transposedReversedGrid, @transposedHorizontalScanRows)); # Up
    
        my @diagonalScanRows is List = generateScanDiagonal(@grid);
        my @transposedDiagonalScanRows is List = generateScanDiagonal(@transposedGrid);
        @part-one-counts.push(count-xmas(@grid, @diagonalScanRows)); # Down Right
        @part-one-counts.push(count-xmas(@grid, @diagonalScanRows».reverse)); # Up Left
        @part-one-counts.push(count-xmas(@reversedGrid, @diagonalScanRows)); # Down Left
        @part-one-counts.push(count-xmas(@reversedGrid, @diagonalScanRows».reverse)); # Up Right
    
        my $part-one-solution = @part-one-counts.sum;
        say "part 1: $part-one-solution";
    
    
        my @part-two-counts = [];
        @part-two-counts.push(countGridMatches(@grid, (<M . S>,<. A .>,<M . S>)));
        @part-two-counts.push(countGridMatches(@grid, (<S . S>,<. A .>,<M . M>)));
        @part-two-counts.push(countGridMatches(@grid, (<S . M>,<. A .>,<S . M>)));
        @part-two-counts.push(countGridMatches(@grid, (<M . M>,<. A .>,<S . S>)));
    
        my $part-two-solution = @part-two-counts.sum;
        say "part 2: $part-two-solution";
    
    }
    
    sub count-xmas(@grid, @scanRows) {
        my $xmas-count = 0;
        for @scanRows -> @scanRow {
            my $xmas-pos = 0;
            for @scanRow -> @pos {
                my $char = @grid[@pos[0]][@pos[1]];
                if "X" eq $char {
                    $xmas-pos = 1;
                }elsif <X M A S>[$xmas-pos] eq $char {
                    if $xmas-pos == 3 {
                        $xmas-pos = 0;
                        $xmas-count += 1;
                    } else {
                        $xmas-pos += 1;
                    }
                } else {
                    $xmas-pos = 0;
                }
            }
        }
        return $xmas-count;
    }
    
    sub generateScanHorizontal(@grid) {
        # Horizontal
        my $rows = @grid.elems;
        my $cols = @grid[0].elems;
        my @scanRows = ();
        for 0..^$rows -> $row {
            my @scanRow = ();
            for 0..^$cols -> $col {
                @scanRow.push(($row, $col));
            }
            @scanRows.push(@scanRow);
        }
        return @scanRows.List».List;
    }
    
    sub generateScanDiagonal(@grid) {
        # Down-right diagonal
        my $rows = @grid.elems;
        my $cols = @grid[0].elems;
        my @scanRows = ();
        for 0..^($rows + $cols - 1) -> $diag {
            my @scanRow = ();
            my $starting-row = max(-$cols + $diag + 1, 0);
            my $starting-col = max($rows - $diag - 1, 0);
            my $diag-len = min($rows - $starting-row, $cols - $starting-col);
            for 0..^$diag-len -> $diag-pos {
                @scanRow.push(($starting-row + $diag-pos, $starting-col + $diag-pos));
            }
            @scanRows.push(@scanRow);
        }
        return @scanRows.List».List;
    }
    
    sub countGridMatches(@grid, @needle) {
        my $count = 0;
        for 0..(@grid.elems - @needle.elems) -> $top {
            TOP-LEFT:
            for 0..(@grid[$top].elems - @needle[0].elems) -> $left {
                for 0..^@needle.elems -> $row-offset {
                    for 0..^@needle[$row-offset].elems -> $col-offset {
                        my $needle-char = @needle[$row-offset][$col-offset];
                        next if $needle-char eq ".";
                        next TOP-LEFT if $needle-char ne @grid[$top+$row-offset][$left+$col-offset];
                    }
                }
                $count += 1;
            }
        }
        return $count;
    }
    

    github

  • SteveDinn@lemmy.ca
    link
    fedilink
    English
    arrow-up
    1
    ·
    5 hours ago

    C#

    namespace Day04;
    
    static class Program
    {
        public record struct Point(int Row, int Col);
    
        static void Main(string[] args)
        {
            var sample = File.ReadAllLines("sample.txt");
            var data = File.ReadAllLines("data.txt");
    
            Console.WriteLine($"Part 1 (sample): {SolvePart1(sample)}");
            Console.WriteLine($"Part 1 (data): {SolvePart1(data)}");
    
            Console.WriteLine($"Part 2 (sample): {SolvePart2(sample)}");
            Console.WriteLine($"Part 2 (data): {SolvePart2(data)}");
        }
    
        private static readonly string Search = "XMAS";
    
        private static readonly Func<Point, Point>[] DirectionalMoves =
        {
            p => new Point(p.Row + 1, p.Col),
            p => new Point(p.Row + 1, p.Col + 1),
            p => new Point(p.Row, p.Col + 1),
            p => new Point(p.Row - 1, p.Col + 1),
            p => new Point(p.Row - 1, p.Col),
            p => new Point(p.Row - 1, p.Col - 1),
            p => new Point(p.Row, p.Col - 1),
            p => new Point(p.Row + 1, p.Col - 1),
        };
    
        private static readonly Func<Point, Point>[] ForwardSlashMoves =
        {
            p => new Point(p.Row - 1, p.Col - 1),
            p => new Point(p.Row + 1, p.Col + 1),
        };
        
        private static readonly Func<Point, Point>[] BackSlashMoves =
        {
            p => new Point(p.Row + 1, p.Col - 1),
            p => new Point(p.Row - 1, p.Col + 1),
        };
    
        static long SolvePart1(string[] data)
        {
            return Enumerable
                .Range(0, data.Length)
                .SelectMany(row => Enumerable.Range(0, data[row].Length)
                    .Select(col => new Point(row, col)))
                .Where(p => IsMatch(data, p, Search[0]))
                .Sum(p => DirectionalMoves
                    .Count(move => DeepMatch(data, move(p), move, Search, 1)));
        }
    
        static long SolvePart2(string[] data)
        {
            return Enumerable
                .Range(0, data.Length)
                .SelectMany(row => Enumerable.Range(0, data[row].Length)
                    .Select(col => new Point(row, col)))
                .Where(p => IsMatch(data, p, 'A'))
                .Count(p => CheckDiagonalMoves(data, p, ForwardSlashMoves)
                            && CheckDiagonalMoves(data, p, BackSlashMoves));
        }
    
        static bool CheckDiagonalMoves(string[] data, Point p, Func<Point, Point>[] moves)
            => (IsMatch(data, moves[0](p), 'S') && IsMatch(data, moves[1](p), 'M'))
               || (IsMatch(data, moves[0](p), 'M') && IsMatch(data, moves[1](p), 'S'));
    
        static bool DeepMatch(string[] data, Point p, Func<Point, Point> move, string search, int searchIndex) =>
            (searchIndex >= search.Length) ? true :
            (!IsMatch(data, p, search[searchIndex])) ? false :
            DeepMatch(data, move(p), move, search, searchIndex + 1);
    
        static bool IsMatch(string[] data, Point p, char searchChar)
            => IsInBounds(data, p) && (data[p.Row][p.Col] == searchChar);
    
        static bool IsInBounds(string[] data, Point p) =>
            (p.Row >= 0) && (p.Col >= 0) && (p.Row < data.Length) && (p.Col < data[0].Length);
    }
    
  • proved_unglue
    link
    fedilink
    arrow-up
    1
    ·
    5 hours ago

    Kotlin

    fun part1(input: String): Int {
        return countWordOccurrences(input.lines())
    }
    
    fun part2(input: String): Int {
        val grid = input.lines().map(String::toList)
        var count = 0
        for (row in 1..grid.size - 2) {
            for (col in 1..grid[row].size - 2) {
                if (grid[row][col] == 'A') {
                    count += countCrossMatch(grid, row, col)
                }
            }
        }
        return count
    }
    
    private fun countCrossMatch(grid: List<List<Char>>, row: Int, col: Int): Int {
        val surroundingCorners = listOf(
            grid[row - 1][col - 1], // upper left
            grid[row - 1][col + 1], // upper right
            grid[row + 1][col - 1], // lower left
            grid[row + 1][col + 1], // lower right
        )
        // no matches:
        //   M S   S M
        //    A     A
        //   S M   M S
        return if (surroundingCorners.count { it == 'M' } == 2
            && surroundingCorners.count { it == 'S' } == 2
            && surroundingCorners[0] != surroundingCorners[3]
        ) 1 else 0
    }
    
    private fun countWordOccurrences(matrix: List<String>): Int {
        val rows = matrix.size
        val cols = if (rows > 0) matrix[0].length else 0
        val directions = listOf(
            Pair(0, 1),   // Horizontal right
            Pair(1, 0),   // Vertical down
            Pair(1, 1),   // Diagonal down-right
            Pair(1, -1),  // Diagonal down-left
            Pair(0, -1),  // Horizontal left
            Pair(-1, 0),  // Vertical up
            Pair(-1, -1), // Diagonal up-left
            Pair(-1, 1)   // Diagonal up-right
        )
    
        fun isWordAt(row: Int, col: Int, word: String, direction: Pair<Int, Int>): Boolean {
            val (dx, dy) = direction
            for (i in word.indices) {
                val x = row + i * dx
                val y = col + i * dy
                if (x !in 0 until rows || y !in 0 until cols || matrix[x][y] != word[i]) {
                    return false
                }
            }
            return true
        }
    
        var count = 0
    
        for (row in 0 until rows) {
            for (col in 0 until cols) {
                for (direction in directions) {
                    if (isWordAt(row, col, "XMAS", direction)) {
                        count++
                    }
                }
            }
        }
    
        return count
    }
    
  • mykl@lemmy.world
    link
    fedilink
    arrow-up
    2
    ·
    edit-2
    3 hours 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
        ·
        4 hours 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.

  • sjmulder@lemmy.sdf.org
    link
    fedilink
    arrow-up
    2
    ·
    9 hours 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

  • TunaCowboy@lemmy.world
    link
    fedilink
    arrow-up
    2
    ·
    edit-2
    10 hours ago

    python

    solution
    import aoc
    
    def setup():
        return (aoc.get_lines(4, padded=(True, '.', 3)), 0)
    
    def one():
        lines, acc = setup()
        for row, l in enumerate(lines):
            for col, c in enumerate(l):
                if c == 'X':
                    w = l[col - 3:col + 1]
                    e = l[col:col + 4]
                    n = c + lines[row - 1][col] + \
                        lines[row - 2][col] + lines[row - 3][col]
                    s = c + lines[row + 1][col] + \
                        lines[row + 2][col] + lines[row + 3][col]
                    nw = c + lines[row - 1][col - 1] + \
                        lines[row - 2][col - 2] + lines[row - 3][col - 3]
                    ne = c + lines[row - 1][col + 1] + \
                        lines[row - 2][col + 2] + lines[row - 3][col + 3]
                    sw = c + lines[row + 1][col - 1] + \
                        lines[row + 2][col - 2] + lines[row + 3][col - 3]
                    se = c + lines[row + 1][col + 1] + \
                        lines[row + 2][col + 2] + lines[row + 3][col + 3]
                    for word in [w, e, n, s, nw, ne, sw, se]:
                        if word in ['XMAS', 'SAMX']:
                            acc += 1
        print(acc)
    
    def two():
        lines, acc = setup()
        for row, l in enumerate(lines):
            for col, c in enumerate(l):
                if c == 'A':
                    l = lines[row - 1][col - 1] + c + lines[row + 1][col + 1]
                    r = lines[row + 1][col - 1] + c + lines[row - 1][col + 1]
                    if l in ['MAS', 'SAM'] and r in ['MAS', 'SAM']:
                        acc += 1
        print(acc)
    
    one()
    two()
    
  • Leo Uino@lemmy.sdf.org
    link
    fedilink
    arrow-up
    4
    ·
    12 hours 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
    
  • Gobbel2000
    link
    fedilink
    arrow-up
    1
    ·
    10 hours ago

    Rust

    One of those with running through tricky grid indices. The vector types from the euclid crate helped in dealing with positions.

    Code
    use euclid::{vec2, default::*};
    
    fn count_xmas(grid: &[&[u8]], pos: (usize, usize)) -> u32 {
        if grid[pos.1][pos.0] != b'X' {
            return 0
        }
    
        let bounds = Rect::new(Point2D::origin(), Size2D::new(grid[0].len() as i32, grid.len() as i32));
        const DIRS: [Vector2D<i32>; 8] = [
            vec2(1, 0), vec2(-1, 0), vec2(0, 1), vec2(0, -1),
            vec2(1, 1), vec2(1, -1), vec2(-1, 1), vec2(-1, -1),
        ];
        let mut count = 0;
        for dir in DIRS {
            let mut cur = Point2D::from(pos).to_i32();
            let mut found = true;
            for letter in [b'M', b'A', b'S'] {
                cur += dir;
                if !bounds.contains(cur) || grid[cur.y as usize][cur.x as usize] != letter {
                    found = false;
                    break
                }
            }
            if found {
                count += 1;
            }
        }
        count
    }
    
    fn part1(input: String) {
        let grid = input.lines().map(|l| l.as_bytes()).collect::<Vec<_>>();    
        let count = (0..grid.len()).map(|y| {
                (0..grid[y].len()).map(|x| count_xmas(&grid, (x, y))).sum::<u32>()
            })
            .sum::<u32>();
        println!("{count}");
    }
    
    fn is_x_mas(grid: &[&[u8]], pos: (usize, usize)) -> bool {
        if grid[pos.1][pos.0] != b'A' {
            return false
        }
    
        const DIRS: [Vector2D<i32>; 4] = [vec2(1, -1), vec2(1, 1), vec2(-1, 1), vec2(-1, -1)];
        let pos = Point2D::from(pos).to_i32();
        (0..4).any(|d| {
            let m_pos = [pos + DIRS[d], pos + DIRS[(d + 1) % 4]]; // 2 adjacent positions of M
            let s_pos = [pos + DIRS[(d + 2) % 4], pos + DIRS[(d + 3) % 4]]; // others S
            m_pos.iter().all(|p| grid[p.y as usize][p.x as usize] == b'M') &&
            s_pos.iter().all(|p| grid[p.y as usize][p.x as usize] == b'S')
        })
    }
    
    fn part2(input: String) {
        let grid = input.lines().map(|l| l.as_bytes()).collect::<Vec<_>>();    
        let count = (1..grid.len() - 1).map(|y| {
                (1..grid[y].len() - 1).filter(|&x| is_x_mas(&grid, (x, y))).count()
            })
            .sum::<usize>();
        println!("{count}");
    }
    
    util::aoc_main!();
    

    (also on github)

  • VegOwOtenks@lemmy.world
    link
    fedilink
    arrow-up
    4
    ·
    13 hours 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 ()
    
  • janAkali@lemmy.one
    link
    fedilink
    English
    arrow-up
    3
    ·
    edit-2
    12 hours 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

  • Ananace@lemmy.ananace.dev
    link
    fedilink
    arrow-up
    1
    ·
    10 hours ago

    I tried to think of some clever LINQ to do this one, but was blanking entirely.

    So naïve search it is.

    C#
    string wordsearch = "";
    int width;
    int height;
    
    public void Input(IEnumerable<string> lines)
    {
      wordsearch = string.Join("", lines);
      height = lines.Count();
      width = lines.First().Length;
    }
    
    public void Part1()
    {
      int words = 0;
      for (int y = 0; y < height; y++)
        for (int x = 0; x < width; x++)
          words += SearchFrom(x, y);
    
      Console.WriteLine($"Words: {words}");
    }
    public void Part2()
    {
      int words = 0;
      for (int y = 1; y < height - 1; y++)
        for (int x = 1; x < width - 1; x++)
          words += SearchCross(x, y);
    
      Console.WriteLine($"Crosses: {words}");
    }
    
    public int SearchFrom(int x, int y)
    {
      char at = wordsearch[y * width + x];
      if (at != 'X')
        return 0;
    
      int words = 0;
      for (int ydir = -1; ydir <= 1; ++ydir)
        for (int xdir = -1; xdir <= 1; ++xdir)
        {
          if (xdir == 0 && ydir == 0)
            continue;
    
          if (SearchWord(x, y, xdir, ydir))
            words++;
        }
    
      return words;
    }
    
    private readonly string word = "XMAS";
    public bool SearchWord(int x, int y, int xdir, int ydir)
    {
      int wordit = 0;
      while (true)
      {
        char at = wordsearch[y * width + x];
        if (at != word[wordit])
          return false;
    
        if (wordit == word.Length - 1)
          return true;
    
        wordit++;
    
        x += xdir;
        y += ydir;
    
        if (x < 0 || y < 0 || x >= width || y >= height)
          return false;
      }
    }
    
    public int SearchCross(int x, int y)
    {
      if (x == 0 || y == 0 || x == width - 1 || y == width - 1)
        return 0;
    
      char at = wordsearch[y * width + x];
      if (at != 'A')
        return 0;
    
      int found = 0;
      for (int ydir = -1; ydir <= 1; ++ydir)
        for (int xdir = -1; xdir <= 1; ++xdir)
        {
          if (xdir == 0 || ydir == 0)
            continue;
    
          if (wordsearch[(y + ydir) * width + (x + xdir)] != 'M')
            continue;
          if (wordsearch[(y - ydir) * width + (x - xdir)] != 'S')
            continue;
    
          found++;
        }
    
      if (found == 2)
        return 1;
    
      return 0;
    }
    
    • CameronDevOPM
      link
      fedilink
      arrow-up
      1
      ·
      10 hours ago

      I haven’t quite started yet, and this one does feel like a busy work kinda problem. I was wondering if I could write something to rotate the board and do the search, but I think that might be not worth the effort

  • hades@lemm.ee
    link
    fedilink
    arrow-up
    2
    ·
    12 hours ago

    C#

    public class Day04 : Solver
    {
      private int width, height;
      private char[,] data;
    
      public void Presolve(string input) {
        var lines = input.Trim().Split("\n").ToList();
        height = lines.Count;
        width = lines[0].Length;
        data = new char[height, width];
        for (int i = 0; i < height; i++) {
          for (int j = 0; j < width; j++) {
            data[i, j] = lines[i][j];
          }
        }
      }
    
      private static readonly string word = "XMAS";
    
      public string SolveFirst()
      {
        int counter = 0;
        for (int start_i = 0; start_i < height; start_i++) {
          for (int start_j = 0; start_j < width; start_j++) {
            if (data[start_i, start_j] != word[0]) continue;
            for (int di = -1; di <= 1; di++) {
              for (int dj = -1; dj <= 1; dj++) {
                if (di == 0 && dj == 0) continue;
                int end_i = start_i + di * (word.Length - 1);
                int end_j = start_j + dj * (word.Length - 1);
                if (end_i < 0 || end_j < 0 || end_i >= height || end_j >= width) continue;
                for (int k = 1; k < word.Length; k++) {
                  if (data[start_i + di * k, start_j + dj * k] != word[k]) break;
                  if (k == word.Length - 1) counter++;
                }
              }
            }
          }
        }
        return counter.ToString();
      }
    
      public string SolveSecond()
      {
        int counter = 0;
        for (int start_i = 1; start_i < height - 1; start_i++) {
          for (int start_j = 1; start_j < width - 1; start_j++) {
            if (data[start_i, start_j] != 'A') continue;
            int even_mas_starts = 0;
            for (int di = -1; di <= 1; di++) {
              for (int dj = -1; dj <= 1; dj++) {
                if (di == 0 && dj == 0) continue;
                if ((di + dj) % 2 != 0) continue;
                if (data[start_i + di, start_j + dj] != 'M') continue;
                if (data[start_i - di, start_j - dj] != 'S') continue;
                even_mas_starts++;
              }
            }
            if (even_mas_starts == 2) counter++;
          }
        }
        return counter.ToString();
      }
    }