Skip to content

Instantly share code, notes, and snippets.

@jemmyw
Created December 10, 2018 11:07
Show Gist options
  • Save jemmyw/0d6d0998b4a967bdbfb0b3ba57b79859 to your computer and use it in GitHub Desktop.
Save jemmyw/0d6d0998b4a967bdbfb0b3ba57b79859 to your computer and use it in GitHub Desktop.
require IEx
defmodule Steps do
def parse(str) when is_bitstring(str) do
str |> String.split("\n") |> parse()
end
def parse([]), do: []
def parse([h | t]) do
[
~r/Step (.+) must be finished before step (.+) can begin./
|> Regex.run(h)
|> List.to_tuple()
|> Tuple.delete_at(0)
| parse(t)
]
end
def first_step(steps) do
leftl = steps |> Enum.map(&elem(&1, 0)) |> MapSet.new()
rightl = steps |> Enum.map(&elem(&1, 1)) |> MapSet.new()
first_value = MapSet.difference(leftl, rightl) |> Enum.sort() |> Enum.at(0)
left =
MapSet.union(leftl, rightl)
|> MapSet.delete(first_value)
|> MapSet.to_list()
|> Enum.sort()
{first_value, left}
end
def step_list(steps, done \\ {[], []})
def step_list(steps, {[], _}) do
{first_value, left} = first_step(steps)
step_list(
steps,
{
[first_value],
left
}
)
|> Enum.reverse()
end
def step_list(_, {done, []}) do
done
end
def step_list(steps, {done, left}) do
{next_done, next_left} = next_step(steps, done, left)
case next_done do
nil ->
done
_ ->
step_list(
steps,
{
[next_done | done],
next_left
}
)
end
end
def required_for(steps, letter) do
Enum.filter(
steps,
fn
{_, ^letter} -> true
_ -> false
end
)
|> Enum.map(&elem(&1, 0))
|> MapSet.new()
end
def next_step(steps, done, left)
def next_step(steps, done, [h | t]) do
required = required_for(steps, h)
done_set = done |> MapSet.new()
cond do
MapSet.subset?(required, done_set) ->
{h, t}
true ->
{sn, sl} = next_step(steps, done, t)
# Add our head back on the list
{sn, [h | sl]}
end
end
def next_step(_, _, []), do: {nil, []}
def seconds(step, delay \\ 60) do
[h | _] = step |> to_charlist
h - 64 + delay
end
def time(steps, delay \\ 60, num_workers \\ 4)
def time(steps, delay, num_workers) do
workers = Enum.map(1..num_workers, fn _ -> blank_worker() end)
{first_value, left} = first_step(steps)
next_second(steps, 0, delay, workers, [], [first_value | left])
end
def all_workers_available?(workers, second) do
Enum.all?(workers, &worker_available?(&1, second))
end
def worker_available?({nil, _}, _), do: true
def worker_available?({_, ends_at}, second) when second > ends_at, do: true
def worker_available?(_, _), do: false
def blank_worker(), do: {nil, nil}
def worker_for_step(nil, _, _), do: blank_worker()
def worker_for_step(step, second, delay) do
{step, second + seconds(step, delay) - 1}
end
def next_second(steps, second, delay, workers, done, left) do
{working_workers, next_done} =
Enum.reduce(workers, {[], done}, fn worker, {workers, done} ->
case {worker_available?(worker, second), worker |> elem(0)} do
{true, nil} -> {[blank_worker() | workers], done}
{true, step} -> {[blank_worker() | workers], [step | done]}
_ -> {[worker | workers], done}
end
end)
{next_workers, next_left} =
Enum.reduce(working_workers, {[], left}, fn worker, {workers, left} ->
case worker_available?(worker, second) do
false ->
{[worker | workers], left}
true ->
{step, next_left} = next_step(steps, next_done, left)
{
[worker_for_step(step, second, delay) | workers],
next_left
}
end
end)
# IO.write(second |> to_string |> String.pad_leading(3) |> String.pad_trailing(4))
# Enum.each(next_workers, fn worker ->
# IO.write(worker |> elem(0) |> to_string |> String.pad_leading(3) |> String.pad_trailing(4))
# end)
# IO.write(next_done |> Enum.reverse() |> Enum.join() |> String.pad_leading(4))
# IO.write("\n")
case all_workers_available?(next_workers, second) do
true -> second
_ -> next_second(steps, second + 1, delay, next_workers, next_done, next_left)
end
end
end
steps = File.read("7.txt") |> elem(1) |> Steps.parse()
# IO.inspect(steps)
# Step 1
steps |> Steps.step_list() |> Enum.join() |> IO.inspect()
# Step 2
steps |> Steps.time(60, 5) |> IO.inspect()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment