Like others, parsed the given map into a graph, with vertices being the intersections (plus the start and the end), each path between two intersections being an edge whose weight is the length of the path. Then for part 1 I used Bellman-Ford with negative weights. For part 2 I got lazy to things myself, and employed libgraph’s Pathfinding.all
function which calculates all simple paths from the start to the end, and then just found the longest.
Part 1 runs in 2.5 seconds (almost all of which is parsing the map - I suppose I do something silly there); part 2 almost 30 seconds.
code
Mix.install([{:libgraph, "~> 0.16.0"}])
defmodule Main do
def run() do
get_input()
|> Enum.map(&String.to_charlist/1) |> mkgrid() |> prep()
# |> solve1()
|> solve2()
end
def get_input() do
# "testinput23"
"input23"
|> File.read!() |> String.trim() |> String.split("\n")
end
def mkgrid(ls) do
for {row, r} <- Enum.with_index(ls),
{val, c} <- Enum.with_index(row),
into: %{}, do: {{r,c}, val}
end
def addt({a,b},{c,d}), do: {a+c,b+d}
def minust({a,b}), do: {-a,-b}
# params: grid, todo:[{pt,dir}, ...], found_edges:[{from,dir,to,len},...]
# retval: found_edges[]
def graph_edges(_,[],es), do: es
def graph_edges(g,[{pt,dir}|todo],es) do
{{tr,tc},dist,ntodo} = follow_path(g,[{pt,dir}],pt,0)
ebgs = es |> Enum.map(fn {p,d,_,_} -> {p,d} end)
nftodo = ntodo |> Enum.filter(fn {p,d} -> {p,d} not in ebgs end)
|> Enum.filter(fn {p,d} -> {p,d} not in todo end)
graph_edges(g, todo ++ nftodo, [{pt,dir,{tr,tc},dist}|es])
end
@dirs [{-1,0},{1,0},{0,1},{0,-1}]
@chdirs %{ {-1,0}=>?^, {1,0}=>?v, {0,1}=>?>, {0,-1}=>?< }
def follow_path(g,[{pt,dir}],_,n) do
npt = addt(pt,dir)
if npt not in Map.keys(g) do {pt, n, []}
else
pndirs = @dirs # potential new dirs (filter out # and nil)
|> List.delete(minust(dir))
|> Enum.map(fn d -> {d,g[addt(npt,d)]} end)
|> Enum.filter(fn {_,c} -> c != ?# and c != nil end)
ndirs = pndirs
|> Enum.filter(fn {d,c} -> c == ?. or c == @chdirs[d] end)
|> Enum.map(fn {d,_} -> {npt,d} end)
if length(pndirs) >= 2 do {npt,n+1,ndirs}
else follow_path(g,ndirs,npt,n+1) end
end
end
def follow_path(_,ndirs,ppt,n), do: {ppt,n,ndirs}
def mkgraph(edges) do
vertmap = edges |> Enum.reduce(%{}, fn {f,_,t,l},acc ->
Map.merge(acc,%{f=>[{t,l}],t=>[]}, fn _k,l1,l2 -> l1++l2 end) end)
{vertmap,edges}
end
def bellman_ford(vtxs,edges,st) do # use negative of weights to find max
dists = Map.from_keys(vtxs,10000)
dists = Map.put(dists,st,0)
for _i <- 1..(length(vtxs)-1), reduce: dists do dts ->
for {f,_,t,w} <- edges, reduce: dts do dtss ->
if dtss[f] - w < dtss[t], do: Map.put(dtss,t,dtss[f] - w), else: dtss
end
end
end
def prep(grid) do
st = {0,1}
{rmax,_} = Enum.max_by(Map.keys(grid), &elem(&1,0))
{_,cmax} = Enum.max_by(Map.keys(grid), &elem(&1,1))
ed = {rmax,cmax-1}
{vertexmap,edges} = graph_edges(grid,[{st,{1,0}}],[]) # 2.5 secs on input23
|> mkgraph()
{vertexmap,edges,st,ed}
end
def solve1({vmap,edges,st,ed}) do
dists = bellman_ford(Map.keys(vmap),edges,st)
- dists[ed]
end
def path_length([a,b|rest],g,sum) do
wt = g |> Graph.edge(a,b) |> Map.get(:weight,0)
path_length([b|rest], g, sum+wt)
end
def path_length([_vtx],_g,sum), do: sum
def solve2({_vmap,edges,st,ed}) do
gr = for {f,_,t,w} <- edges, reduce: Graph.new(type: :directed) do
g -> g |> Graph.add_edge(f,t,weight: w) |> Graph.add_edge(t,f,weight: w) end
Graph.Pathfinding.all(gr,st,ed) # this does not work with undirected graphs!! # 25 secs
|> Enum.map(&path_length(&1,gr,0))
|> Enum.max()
end
end
:timer.tc(&Main.run/0)
|> IO.inspect(charlists: :as_lists)