The whole thing runs quickly, under 1/10 s, but I am afraid the code is rather unreadable. Apologies
code
defmodule Main do
def run() do
get_input()
|> Enum.map(&parse/1)
# |> Enum.map(&prep1/1)
|> Enum.map(&prep2/1)
|> solve()
end
def get_input() do
# "testinput18"
"input18"
|> File.read!()
|> String.trim()
|> String.split("\n")
end
def parse(l), do: Regex.run(~r/([RLUD]) (\d+) \(#([0-9a-f]+)([0-9a-f])\)/,l) |> tl()
def prep1([d,n|_]), do: [d,String.to_integer(n)]
@n_to_dir %{"0"=>"R", "1"=>"D", "2"=>"L", "3"=>"U"}
def prep2([_,_,n,d]), do: [@n_to_dir[d],String.to_integer(n,16)]
def addsctup({a,b},k,{c,d}), do: {a+k*c, b+k*d}
# visually checked (part1), and the boundary is always separated from other parts
# of the boundary by at least one empty tile, so can do this algo
@dirs %{"R"=>{0,1},"L"=>{0,-1},"U"=>{-1,0},"D"=>{1,0}}
def mkbdry2(ls) do
lss = ([Enum.at(ls,-1)|ls] ++ [hd(ls)]) |> Enum.chunk_every(3,1,:discard) # to capture first and last
{_, verts, horzs} =
for [[pdir|_],[dir,dist],[ndir|_]] <- lss, reduce: {{0,0},MapSet.new(),MapSet.new()} do
{pt,verts,horzs} ->
npt = addsctup(pt,dist,@dirs[dir])
{p1,p2} = if dir in ["R","D"] do {pt,npt} else {npt,pt} end # sort by size
case {dir in ["R","L"], pdir, ndir} do
{true, d, d} -> {npt, verts, MapSet.put(horzs, {p1,p2,dir,dist,true})}
{true, _, _} -> {npt, verts, MapSet.put(horzs, {p1,p2,dir,dist,false})}
{false,_, _} -> {npt, MapSet.put(verts,{p1,p2,dir,dist}), horzs}
end
end
{ Enum.group_by(horzs, fn {{r,_},_,_,_,_} -> r end),
Enum.group_by(verts, fn {{_,c},_,_,_} -> c end) }
end
def is_in_vert?(r,c,vs) do
vs[c] |> Enum.map(fn {{r1,_},{r2,_},_,_} -> r1 <= r and r <= r2 end) |> Enum.any?()
end
def is_start_horz?(c,hh) do
f = hh |> Enum.map(fn {{_,c1},{_,c2},_,_,flip} -> {c1 == c, c2, flip} end)
|> Enum.filter(&elem(&1,0))
if f == [] do {false,nil,nil} else hd(f) end
end
def count_along(r,vs,hs) do
vss = Map.keys(vs) |> Enum.sort()
hh = Map.get(hs,r,[])
{finhz,fcc,fflip} = is_start_horz?(hd(vss),hh)
fio = if finhz do false else is_in_vert?(r, hd(vss), vs) end
fct = if fio or finhz do 1 else 0 end
for [c1,c2] <- (vss |> Enum.chunk_every(2,1,:discard)),
reduce: {fct, fio, finhz, fcc, fflip}
# { count_so_far, in_or_out, in_horizontal_bdry, col_where_horiz_bit_ends, do_we_flip_after_horiz? }
do {ct, io, inhz, cc, flip} ->
{ninhz,ncc,nflip} = is_start_horz?(c2,hh)
cond do
cc == c2 -> {ct + c2 - c1, if flip do not io else io end, false, nil, nil }
inhz -> {ct + c2 - c1, io, true, cc, flip}
ninhz and io -> {ct + c2 - c1, io, true, ncc, nflip}
ninhz and not io -> {ct + 1, io, true, ncc, nflip}
true -> (
case {is_in_vert?(r, c2, vs), io} do
{true, true} -> { ct + c2 - c1, false, ninhz, ncc, nflip }
{true, false} -> { ct + 1, true, ninhz, ncc, nflip }
{false, true} -> { ct + c2 - c1 , true, ninhz, ncc, nflip }
{false,false} -> { ct, false, ninhz, ncc, nflip }
end)
end
end |> elem(0)
end
def solve(ls) do
{hs,vs} = ls |> mkbdry2()
intervals = (Map.keys(hs) |> Enum.sort() |> Enum.chunk_every(2,1,:discard))
|> Enum.map(fn [r1,r2] -> { r1 + 1, r2 - r1 - 1} end)
|> Enum.filter(fn {_,l} -> l > 0 end)
|> Enum.map(fn {r,l} -> l*count_along(r,vs,hs) end)
|> Enum.sum()
Map.keys(hs) |> Enum.sort() |> Enum.map(fn r -> count_along(r,vs,hs) end)
|> Enum.sum()
|> Kernel.+(intervals)
end
end
:timer.tc(&Main.run/0)
|> IO.inspect(charlists: :as_lists)