Iām well behind but I think I managed this. Grids in functional languages fill me with dread but I took the following approach for part 1 was first check the for a match like this:
@samx "SAMX"
@xmas "XMAS"
@new_line "\n"
defp check_line(<<>>, hits), do: hits
defp check_line(<<@samx, _::binary>> = line, hits) do
<<_::binary-size(3), rest::binary>> = line
check_line(rest, hits + 1)
end
defp check_line(<<@xmas, _::binary>> = line, hits) do
<<_::binary-size(3), rest::binary>> = line
check_line(rest, hits + 1)
end
defp check_line(<<_::binary-size(1), rest::binary>>, hits), do: check_line(rest, hits)
Then turn the binary into columns and each diagonal. The trick for the diagnonals is to iterate along the top row, then down the rightmost column for sout east diagonals. Then go from top right to back along the top row and down the leftmost column.
Anyway it came out a bit more verbose than I hoped can probably simplify it a bit.
def day4_1() do
grid = "./day_4_input.txt" |> File.read!()
line_length = line_length(grid, 0)
row_count = check_line(grid, 0)
ne_count = north_east_diagonal(grid, line_length)
se_count = south_east_diagonal(grid, line_length)
column_count = column_count(grid, line_length)
row_count + ne_count + se_count + column_count
end
@samx "SAMX"
@xmas "XMAS"
@new_line "\n"
defp check_line(<<>>, hits), do: hits
defp check_line(<<@samx, _::binary>> = line, hits) do
<<_::binary-size(3), rest::binary>> = line
check_line(rest, hits + 1)
end
defp check_line(<<@xmas, _::binary>> = line, hits) do
<<_::binary-size(3), rest::binary>> = line
check_line(rest, hits + 1)
end
defp check_line(<<_::binary-size(1), rest::binary>>, hits), do: check_line(rest, hits)
# We include the new line in the count because it makes the rest of the stuff work better
defp line_length(<<@new_line, _::binary>>, count), do: count + 1
defp line_length(<<_::binary-size(1), rest::binary>>, count), do: line_length(rest, count + 1)
def column_count(grid, line_length) do
Enum.reduce(0..(line_length - 1), "", fn x, lines ->
columns({x, line_length - 2}, grid, line_length, lines)
end)
|> check_line(0)
end
def columns({_, y}, _, _, acc) when y < 0, do: <<acc::binary, @new_line>>
def columns({x, y}, grid, line_length, acc) do
char = :binary.part(grid, x + y * line_length, 1)
columns({x, y - 1}, grid, line_length, <<acc::binary, char::binary>>)
end
def south_east_diagonal(grid, line_length) do
se_diagonal_index({line_length - 2, 0}, line_length - 2, [])
|> Enum.reduce("", fn diagonal_indexes, acc ->
line =
diagonal_indexes
|> Enum.reduce("", fn {x, y}, acc ->
char = :binary.part(grid, x + y * line_length, 1)
<<acc::binary, char::binary>>
end)
<<acc::binary, line::binary, @new_line>>
end)
|> check_line(0)
end
# We've gone past bottom left.
def se_diagonal_index({0, y}, last_idx, acc) when y == last_idx, do: acc
# This is the first case hit - the top right
def se_diagonal_index({last_idx, 0}, last_idx, acc) do
se_diagonal_index({last_idx - 1, 0}, last_idx, [[{last_idx, 0}] | acc])
end
# This is the switch up case, where we round the corner on the top left hand side going down
def se_diagonal_index({x, _}, last_idx, acc) when x < 0 do
se_diagonal_index({0, 1}, last_idx, acc)
end
# this is going along the top row, we heading backwards on the x axis
def se_diagonal_index({x, 0} = current_cell, last_idx, acc) do
diagonal = [
current_cell | Enum.map(1..(last_idx - x), fn y_coord -> {x + 1 * y_coord, y_coord} end)
]
se_diagonal_index({x - 1, 0}, last_idx, [diagonal | acc])
end
# this is going down the leftmost column.
def se_diagonal_index({0, y} = current, last_idx, acc) do
diagonal = [current | Enum.map(1..(last_idx - y), fn y_coord -> {y_coord, y + y_coord} end)]
se_diagonal_index({0, y + 1}, last_idx, [diagonal | acc])
end
def north_east_diagonal(grid, line_length) do
# It's - 2, 1 because of the newline char at the end of each line 1 because of the 0 index
# We start at X of 2 because first few rows can never match as they are too short.
ne_diagonal_idx({0, 0}, line_length - 2, [])
|> Enum.reduce("", fn diagonal_indexes, acc ->
line =
diagonal_indexes
|> Enum.reduce("", fn {x, y}, acc ->
char = :binary.part(grid, x + y * line_length, 1)
<<acc::binary, char::binary>>
end)
<<acc::binary, line::binary, @new_line>>
end)
|> check_line(0)
end
def ne_diagonal_idx({last_idx, y}, last_idx, acc) when y >= last_idx, do: acc
def ne_diagonal_idx({0, 0}, last_idx, acc) do
ne_diagonal_idx({1, 0}, last_idx, [[{0, 0}] | acc])
end
def ne_diagonal_idx({x, 0}, last_idx, acc) when x > last_idx do
ne_diagonal_idx({x - 1, 1}, last_idx, acc)
end
def ne_diagonal_idx({x, 0} = current_cell, last_idx, acc) do
diagonal = [current_cell | Enum.map(1..x, fn y_coord -> {x - 1 * y_coord, y_coord} end)]
ne_diagonal_idx({x + 1, 0}, last_idx, [diagonal | acc])
end
def ne_diagonal_idx({x, y} = current, last_idx, acc) do
diagonal = [
current | Enum.map(1..(last_idx - y), fn y_coord -> {x - 1 * y_coord, y + y_coord} end)
]
ne_diagonal_idx({x, y + 1}, last_idx, [diagonal | acc])
end