Almost caught up. Posting part 1 for posterity:
#!/usr/bin/env elixir
defmodule Day9.Part1 do
@ascii_adjustment 48
defp parse(str) do
[{_size, _id} | _diskmap] =
diskmap =
str
|> String.trim()
|> to_charlist()
|> Enum.map(&(&1 - @ascii_adjustment))
|> Enum.with_index()
%{
empty_space: empty_space,
files: [{first_file_size, _ffid} = _file | _files] = files,
optimal_size: optimal_size
} =
diskmap
|> Enum.reduce(
%{},
fn {size, id} = item, acc ->
if rem(id, 2) == 0 do
acc
|> Map.update(:files, [item], fn files -> [item | files] end)
|> Map.update(:optimal_size, size, &(&1 + size))
else
acc
|> Map.update(:empty_space, [item], fn empty_space -> [item | empty_space] end)
end
end
)
|> Map.new(fn
{:empty_space = k, v} -> {k, v |> Enum.map(&elem(&1, 0)) |> Enum.reverse()}
{:files = k, v} -> {k, v |> Enum.reverse() |> Enum.map(&elem(&1, 0)) |> Enum.with_index()}
optimal_size -> optimal_size
end)
optimal_size = optimal_size - first_file_size
empty_space_count = empty_space |> Enum.count()
file_count = files |> Enum.count()
ends_with_space? = empty_space_count == file_count
empty_space =
if ends_with_space?,
do: empty_space |> List.delete_at(empty_space_count - 1),
else: empty_space
initial_insertion_index = 1
%{
files: files,
empty_space: empty_space,
optimal_size: optimal_size,
insertion_index: initial_insertion_index
}
end
defp pull([] = _files, _blocks), do: {[], []}
defp pull(files, 0 = _blocks), do: {[], files}
defp pull([{block_size, id} = file | files], blocks) do
block_diff = block_size - blocks
case block_diff do
x when x < 0 ->
{pulled_files, remaining_files} = pull(files, abs(block_diff))
{[file | pulled_files], remaining_files}
x when x > 0 ->
{[{blocks, id}], [{block_diff, id} | files]}
0 ->
{[{blocks, id}], files}
end
end
defp pull_from_end(files, blocks) do
{pulled, remaining} = pull(Enum.reverse(files), blocks)
{pulled, remaining |> Enum.reverse()}
end
defp optimize(%{
files: files,
empty_space: _empty_space,
optimal_size: nil,
insertion_index: _index
}),
do: files
defp optimize(%{
files: files,
empty_space: _empty_space,
optimal_size: optimal_size,
insertion_index: _index
})
when optimal_size <= 0,
do: files
defp optimize(%{
files: files,
empty_space: [],
optimal_size: _optimal_size,
insertion_index: _index
}),
do: files
defp optimize(%{
files: files,
empty_space: [empty_size | empty_spaces],
optimal_size: optimal_size,
insertion_index: index
}) do
next_optimal_size = optimal_size - empty_size
{files_to_insert, remaining_files} =
if next_optimal_size < 0,
do: pull_from_end(files, optimal_size),
else: pull_from_end(files, empty_size)
next_file_index = index + 1
next_optimal_size =
if next_file_index > remaining_files |> Enum.count() do
nil
else
next_optimal_size - (remaining_files |> Enum.at(index) |> elem(0))
end
file_count = files_to_insert |> Enum.count()
modified_files =
(remaining_files
|> Enum.take(index)) ++ files_to_insert ++ (remaining_files |> Enum.drop(index))
insertion_index = next_file_index + file_count
optimize(%{
files: modified_files,
empty_space: empty_spaces,
optimal_size: next_optimal_size,
insertion_index: insertion_index
})
end
def checksum(files) do
files
|> Enum.reduce(
{0, 0},
fn {block_size, id} = _chunk, {index, acc} ->
ids = for _ <- 0..(block_size - 1), do: id
acc =
acc +
(ids
|> Enum.with_index()
|> Enum.map(fn {id, i} ->
id * (i + index)
end)
|> Enum.sum())
{index + block_size, acc}
end
)
end
def solve() do
File.read!("09/input.txt")
|> parse()
|> optimize()
|> checksum()
|> elem(1)
|> IO.puts()
end
end
Day9.Part1.solve()
Still working on part 2