Skip to content

Commit 2718b4c

Browse files
committed
day4
1 parent 9e64df2 commit 2718b4c

File tree

7 files changed

+241
-0
lines changed

7 files changed

+241
-0
lines changed

Sources/AdventOfCode.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ let allChallenges: [any AdventDay] = [
55
Day01(),
66
Day02(),
77
Day03(),
8+
Day04(),
89
]
910

1011
@main

Sources/Day04.swift

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import Algorithms
2+
3+
func searchInNeighbors(pos: Coord, _ grid: Grid) -> [Coord] {
4+
pos.fullNeighbors.included(in: grid).filter { n in
5+
grid[n] == "M"
6+
}
7+
}
8+
9+
func isMAS(pos: Coord, _ grid: Grid) -> Bool {
10+
let candidates = pos.cornerNeighbors
11+
if (candidates.allSatisfy { $0.isInside(grid: grid) }) {
12+
let l = candidates.map { grid[$0] }
13+
let letterSet = Set(l)
14+
// same letter can not be on opposite side
15+
return l[0] != l[2] && l[1] != l[3] && letterSet.count == 2 && letterSet == ["M", "S"]
16+
}
17+
return false
18+
}
19+
20+
func isWhole(pos: Coord, inDir: Coord, grid: Grid) -> Bool {
21+
let aPos = pos + inDir
22+
let sPos = aPos + inDir
23+
if aPos.isInside(grid: grid) && sPos.isInside(grid: grid) {
24+
return grid[aPos] == "A" && grid[sPos] == "S"
25+
}
26+
return false
27+
}
28+
29+
struct Day04: AdventDay {
30+
var data: String
31+
var grid: Grid {
32+
Grid(from: data)
33+
}
34+
35+
func countAllWhere(letter search: Character, predicate: (Coord, Grid) -> Bool) -> Int {
36+
grid.raw.enumerated().map { (y, line) in
37+
line.enumerated().map { (x, letter) in
38+
if letter == search {
39+
return predicate(Coord(x: x, y: y), grid)
40+
}
41+
return false
42+
}
43+
}.flatMap { $0 }.count { $0 == true }
44+
}
45+
46+
func part1() -> Int {
47+
var count = 0
48+
// get all positions of "X"
49+
for (y, line) in grid.raw.enumerated() {
50+
for (x, letter) in line.enumerated() {
51+
if letter == "X" {
52+
let currentPos = Coord(x: x, y: y)
53+
let results = searchInNeighbors(pos: currentPos, grid)
54+
if results.count > 0 {
55+
for candidate in results {
56+
let inDir = candidate - currentPos
57+
if isWhole(pos: candidate, inDir: inDir, grid: grid) {
58+
count += 1
59+
}
60+
}
61+
}
62+
}
63+
}
64+
}
65+
return count
66+
}
67+
68+
func part2() -> Int {
69+
countAllWhere(letter: "A", predicate: isMAS)
70+
}
71+
}

Sources/Extensions/String+Parsing.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ extension StringProtocol {
1414
self.split(separator: "\n")
1515
}
1616

17+
func toCharacterGrid() -> [[Substring.Element]] {
18+
self.lines().map { Array($0) }
19+
}
20+
1721
func integers(separator: String = " ") -> [Int] {
1822
self.split(separator: separator).map { Int($0) }.compactMap { $0 }
1923
}

Sources/Helpers/Coord.swift

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
struct Coord {
2+
var x: Int
3+
var y: Int
4+
5+
/// Offsets for the corner elements (X shape)
6+
/// Corners are ordered clockwise starting at the top left
7+
/// top left, top right, bottom right, bottom left
8+
static let corners = [
9+
Coord(x: -1, y: -1),
10+
Coord(x: 1, y: -1),
11+
Coord(x: 1, y: 1),
12+
Coord(x: -1, y: 1),
13+
]
14+
15+
/// Offsets for the cross elements (+ shape)
16+
///
17+
static let cross = [
18+
Coord(x: 0, y: -1),
19+
Coord(x: 0, y: 1),
20+
Coord(x: -1, y: 0),
21+
Coord(x: 1, y: 0),
22+
]
23+
24+
var cornerNeighbors: [Coord] {
25+
Coord.corners.map { self + $0 }
26+
}
27+
28+
var crossNeighbors: [Coord] {
29+
Coord.cross.map { self + $0 }
30+
}
31+
32+
var fullNeighbors: [Coord] {
33+
self.cornerNeighbors + self.crossNeighbors
34+
}
35+
36+
func isInside(grid: Grid) -> Bool {
37+
self.x >= 0 && self.x <= grid.width - 1 && self.y >= 0 && self.y <= grid.height - 1
38+
}
39+
40+
}
41+
42+
extension Coord: Equatable {}
43+
44+
extension Coord: AdditiveArithmetic {
45+
static let zero = Coord(x: 0, y: 0)
46+
47+
// add conformance for the AdditiveArithmetic protocol
48+
static func + (lhs: Self, rhs: Self) -> Self {
49+
return Self(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
50+
}
51+
52+
static func - (lhs: Self, rhs: Self) -> Self {
53+
return Self(x: lhs.x - rhs.x, y: lhs.y - rhs.y)
54+
}
55+
}
56+
57+
extension Array where Array.Element == Coord {
58+
func included(in grid: Grid) -> Self {
59+
self.filter { $0.isInside(grid: grid) }
60+
}
61+
}

Sources/Helpers/Grid.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
struct Grid {
2+
typealias Element = Character
3+
4+
var raw: [[Element]]
5+
6+
// dimensions
7+
var width: Int {
8+
raw[0].count
9+
}
10+
var height: Int {
11+
raw.count
12+
}
13+
14+
subscript(_ coord: Coord) -> Element {
15+
raw[coord.y][coord.x]
16+
}
17+
18+
subscript(col: Int, row: Int) -> Element {
19+
raw[row][col]
20+
}
21+
}
22+
23+
extension Grid {
24+
/// creates a Grid of characters from a multiline string
25+
init(from data: String) {
26+
self.raw = data.lines().map { Array($0) }
27+
}
28+
29+
func includes(coord: Coord) -> Bool {
30+
coord.x >= 0 && coord.x <= self.width - 1 && coord.y >= 0 && coord.y <= self.height - 1
31+
}
32+
}

Tests/Day04.swift

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import Testing
2+
3+
@testable import aoc
4+
5+
@Suite("Day04")
6+
struct Day04Tests {
7+
let testData = """
8+
MMMSXXMASM
9+
MSAMXMSMSA
10+
AMXSXMAAMM
11+
MSAMASMSMX
12+
XMASAMXAMM
13+
XXAMMXXAMA
14+
SMSMSASXSS
15+
SAXAMASAAA
16+
MAMMMXMMMM
17+
MXMXAXMASX
18+
"""
19+
20+
@Test("Neighbor search")
21+
func testNeighborSearch() async throws {
22+
let grid = Grid(
23+
from: """
24+
XMAS
25+
XMAS
26+
XMAS
27+
XMAS
28+
""")
29+
#expect(
30+
searchInNeighbors(pos: Coord(x: 0, y: 0), grid) == [Coord(x: 1, y: 0), Coord(x: 1, y: 1)])
31+
}
32+
33+
@Test("part1 simple")
34+
func testSimplePart1() async throws {
35+
let grid = """
36+
XMAS
37+
XMAS
38+
XMAS
39+
XMAS
40+
"""
41+
let challenge = Day04(data: grid)
42+
#expect(challenge.part1() == 2)
43+
}
44+
45+
@Test("part1")
46+
func testPart1() async throws {
47+
let challenge = Day04(data: testData)
48+
#expect(challenge.part1() == 18)
49+
}
50+
51+
@Test("part2")
52+
func testPart2() async throws {
53+
let challenge = Day04(data: testData)
54+
#expect(challenge.part2() == 9)
55+
}
56+
}

Tests/Helpers/Grid.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import Testing
2+
3+
@testable import aoc
4+
5+
@Suite("Grid")
6+
struct GridTests {
7+
@Test func testGridCreation() async throws {
8+
let unitGrid = Grid(raw: [["0"]])
9+
#expect(unitGrid.width == 1)
10+
#expect(unitGrid.height == 1)
11+
let highGrid = Grid(raw: [["1"], ["2"]])
12+
13+
#expect(highGrid.width == 1)
14+
#expect(highGrid.height == 2)
15+
}
16+
}

0 commit comments

Comments
 (0)