I think mine is pretty much the same idea as Raul's for part 1, tough I
used symbols rather than integers for encoding the caves (nodes).
NB. Day 12: Cave mapping paths
sl=: (\: #&>) NB. sort boxes by descending length
NB. remove - and sort boxes by length s.t. start, end are first: unordered
edges, so sorting does not matter.
i12=: (sl {."1)([: sl [: <;._2 ,&'-') ;._2 in 12
NB. find all paths from start to end through caves, visiting each lowercase
cave max once
islc=: (97 ([+i.@>:@-~) 122) *./@e.~ a.&i. NB. is all lower case?
a12=:{{
links=. s: (, |."1) y NB. bidirectional links, so that can look up based
on {."1
lcn =. s: (#~ islc&>) ~. ({."1,{:"1)y NB. lowercase nodes
paths=. ,: s: ' start' NB. list of distinct paths (no box because equal
length)
complete=. 0$a: NB. boxed because ~: lengths
while. *#paths do.
NB. add possible nodes to paths (excluding visited lowercase and the
last)
NB. raze bx path apd lcn in path rem from excllast dest where
source is last
paths =. ;@:(<@(,"1 0 (#~ e.&lcn) -.~ links (]-.~ {:"1@[ #~ {."1@[
= ]) {:)"1) paths
NB. remove complete paths & add to complete
iscomp=. (s:<'end')={:"1 paths
complete=. complete, <iscomp#paths
paths=. paths #~ -.iscomp
end. NB. end if no incomplete paths left
+/#&>complete
}}
For part B, I first made a misinterpretation of the problem: I thought
*any* lowercase node could be visited twice, which lead to a ridiculous
number of solutions, prompting aggressive optimisations (in b12 below),
while the same approach of a12 would have worked as well (see b12a), though
quite a bit slower.
NB. part B: the same, but one lcn can be visited twice; start, end only
once.
b12a=: {{
NB. bidirectional links, so that can look up based on {."1
links=. s: (, |."1) y
NB. remove start as destination
links=. (#~ (s:<'start')~:{:"1) links
lcn =. s: (#~ islc&>) ~. ({."1,{:"1)y NB. lowercase nodes
NB. list of distinct paths (no box because equal length); don't store
start
paths=. ,:s:<'start'
ncomplete=. 0
NB. complete=: 0$a:
NB. *wrong*: only a single lcn can be picked twice
NB. eligible =: (lcn ([#~2<:+/@:="0 1) [) -.~ ]
NB. correct:
eligible =: (lcn ([ (#~ ]>:1+2>>./@]) (+/@:="0 1)) [) -.~ ]
NB. newlinks: make list of connected nodes, removing last (inf loop)
newlinks=: links (]-.~ {:"1@[ #~ {."1@[ = ]) {:
while. *#paths do.
NB. add possible nodes to paths (excluding visited lowercase and the
last)
NB. raze bx apd start rm lcn in path rem from excllast dest
where source is last
paths =. ;@:(<@(,"1 0 ] eligible newlinks)"1) paths
NB. remove complete paths & add to complete
iscomp=. (s:<'end')={:"1 paths
ncomplete=. ncomplete++/iscomp
NB. complete=: complete,<iscomp#paths
paths=. paths #~ -.iscomp
end. NB. end if no incomplete paths left
ncomplete
}}
NB. outcome: a: 10; b: 36
t1=: (sl {."1)([: sl [: <;._2 ,&'-') ;._2{{)n
start-A
start-b
A-c
A-b
b-d
A-end
b-end
}}
NB. outcomes: a: 19; b: 103
t2=: (sl {."1)([: sl [: <;._2 ,&'-') ;._2{{)n
dc-end
HN-start
start-kj
dc-start
dc-HN
LN-dc
HN-end
kj-sa
kj-HN
kj-dc
}}
b12 is my heavily optimised version, based on the consideration that only
the last node and set of visited lower case nodes (not quite a set, since
elements can appear multiple times, but order not important) matter in this
problem, the rest can safely be abstracted away. Doing so would lead to
many duplicate paths that can also be collapsed to a single one if one
keeps counts.
Result: 40x faster, 78x leaner
This would be able to keep track of all paths also if all lcn's would be
allowed to be visited twice, and likely more.
b12=: {{
NB. bidirectional links, so that can look up based on {."1
links=. s: (, |."1) y
NB. remove start as destination; start is not revisited
links=. (#~ (s:<'start')~:{:"1) links
NB. list of distinct paths
paths=. <"0 links ({:"1@[ #~ {."1@[ = ]) (s:<'start')
cts=. #/.~ paths NB. keeps track of duplicity of tracks
NB. remove start as source (already in paths)
links=. (#~ (s:<'start')~:{."1) links
NB. set of lowercase nodes
lcn =. s: (#~ islc&>) ~. ({."1,{:"1)y
NB. set of uppercase nodes
ucn =. s: (#~ -.@:islc&>) ~. ({."1,{:"1)y
ncomplete=. 0 NB. number of complete paths
NB. helper verbs:
NB. filter to remove non-eligible lcn's; x: path; y:reachable links
NB. wrong: only a *single* lcn can be picked twice, while the commented
one allowed *every* lcn to appear twice.
NB. eligible =: (lcn ([#~2<:+/@:="0 1) [) -.~ ]
eligible =. (lcn ([ (#~ ]>:1+2>>./@]) (+/@:="0 1)) [) -.~ ]
NB. newlinks: make list of connected nodes, removing last (inf loop)
newlinks=. links (]-.~ {:"1@[ #~ {."1@[ = ]) {:
NB. Breadth-first search:
while. *#paths do.
NB. add possible nodes to paths (excluding visited lowercase and the
last)
NB. sorted lcn's in path append eligible newlinks
newpaths =. (/:~@(}:^:(ucn e.~ {:)) <@,"1 0 ] eligible newlinks )&.>
paths
NB. ^sort essential to trim possible dupes v
newcts=. cts #~ #&> newpaths NB. duplicate original counts for each
new path
NB. flatten newpaths
newpaths=. ;newpaths
NB. sum counts for equivalent paths
newcts=. newpaths +//. newcts
NB. unique new paths, reduces maintenance cost
newpaths=. ~. newpaths
NB. remove complete new paths & add to ncomplete
NB. complete paths end in 'end'
iscomp=. (s:<'end')={:&> newpaths
NB. add complete counts
ncomplete=. ncomplete++/iscomp#newcts
NB. remove complete paths
paths=. newpaths #~ -.iscomp
NB. remove complete counts
cts=. newcts #~ -.iscomp
end. NB. end if no incomplete paths left
ncomplete
}}
(a12;b12)i12
Jan-Pieter
On Sun, Jan 2, 2022, 05:52 Raul Miller <[email protected]> wrote:
> https://adventofcode.com/2021/day/12
>
> For day 12, we had a "cave map" which was a list of connections
> between named pairs of rooms.
>
> unpack=:-.&;:&'-';._2
>
> sample=: unpack {{)n
> start-A
> start-b
> A-c
> A-b
> b-d
> A-end
> b-end
> }}
>
> Here, our puzzle task, for part A, was to explore the cave system. We
> needed to visit each room at least once. Rooms with lower case names
> needed to be visited exactly once. Rooms with ALL CAPS names could be
> visited as many times as we wanted. Specifically, we needed to count
> how many distinct paths lead from 'start' to 'end'.
>
> I could have solved this more elegantly, but here was my approach for
> that part of the puzzle:
>
> a12=:{{
> c=. (#~ (<'end')~:{."1) (#~ (<'start')~:{:"1) y,|."1 y
> ref=. ~.a:,,c
> 'from to'=. |:ref i.c
> 'start end'=. ref i.;:'start end'
> r=. i.0 0
> paths=. (start = from)#from,.to
> reps=. (i. (#~ ] = toupper each)) ref
> while.#paths do.
> done=. end = {:"1 paths
> r=. r, done#paths
> paths=. (-.done)#paths
> nexts=. (-.to e."1 paths -."1 reps)*({:"1 paths)=/from
> paths=. ((+/"1 nexts)#paths),.(,nexts)#(#,nexts)$to
> end.
> ;:inv r { ref
> }}
>
> Basically, the connection pairs were two way (except for start and
> end), so the first thing I did was convert the bidirectional
> connection pairs to unidirectional connection pairs (c).
>
> Then, I assigned integer cave ids and set up my initial state (a list
> of incomplete paths from the start location -- technically, I could
> have left the start node out of my 'ref' of connection pairs, since I
> would never use those connections again).
>
> Once I had that, I started tracing through to all the possible next
> available connections for each of my paths (breadth first search), and
> moving each complete path to my result variable.
>
> At the end, so I could comprehend the result, I converted the paths
> back to a textual form. The actual result I needed was #a12
>
> #a12 sample
> 10
>
> For part B, the rules changed. Except for start and end, we could also
> visit one (and only one) of the rooms with lower case names twice.
>
> Again, there's certainly tidier ways of solving this, but here's how I
> approached that:
>
> b12=: {{
> c=. (#~ (<'end')~:{."1) (#~ (<'start')~:{:"1) y,|."1 y
> ref=. ~.a:,,c
> 'from to'=. |:ref i.c
> 'start end'=. ref i.;:'start end'
> r=. i.0 0
> paths=. (start = from)#from,.to
> reps=. (i. (#~ ] = toupper each)) ref
> while.#paths do.
> done=. end = {:"1 paths
> r=. r, done#paths
> paths=. (-.done)#paths
> any=. 1 = >./"1 }."1 #/.~("1) 0,.paths -."1 reps
> nexts=. (any+.-.to e."1 paths -."1 reps)*({:"1 paths)=/from
> paths=. ((+/"1 nexts)#paths),.(,nexts)#(#,nexts)$to
> end.
> ;:inv r { ref
> }}
>
> The big difference here was that I was tracking whether any
> destination room was viable or only unvisited and ALL CAPS rooms were
> viable destinations. Other than that, it's the same as the part A
> implementation.
>
> And, similar to part A, the actual result I needed was #b12
>
> #b12 sample
> 36
>
> --
> Raul
> ----------------------------------------------------------------------
> For information about J forums see http://www.jsoftware.com/forums.htm
>
----------------------------------------------------------------------
For information about J forums see http://www.jsoftware.com/forums.htm