https://adventofcode.com/2021/day/8
For day 8, the puzzle declared that we had a seven segment display
(displaying four digits) that was on the fritz.
Our sample data represented the ten different values being displayed
during observation, followed by a '|' character, followed by the four
values representing the four digits being displayed for that
observation. Something like this:
sample=:{{)n
be cfbegad cbdgef fgaecd cgeb fdcge agebfd fecdb fabcd edb |
fdgacbe cefdb cefbgd gcbe
edbfga begcd cbg gc gcadebf fbgde acbgfd abcde gfcbed gfec |
fcgedb cgb dgebacf gc
fgaebd cg bdaec gdafb agbcfd gdcbef bgcad gfac gcb cdgabef |
cg cg fdcagb cbg
}}
The actual puzzle sample has more entries than this, but you can go
look that up yourself. Also, the puzzle samples are pre-wrapped -- the
code I am posting here expects that they the lines ending in | have
had their immediately following lines attached. Like this, though
email might wrap it some place...
sample1=: {{)n
acedgfb cdfbe gcdfa fbcad dab cefabd cdfgeb eafb cagedb ab | cdfeb
fcadb cdfeb cdbaf
}}
The puzzle page has some detailed explanation of the letter code, but
the starting point was an ascii based representation of seven segment
display structure:
aaaa
b c
b c
dddd
e f
e f
gggg
(Looks better monospaced.)
Anyways, for the first part of the puzzle, we were supposed to
identify the "easy digits" in the output part (the last four digits --
those after the '|'). These are the digits with a unique segment
count: 1 (two segments lit), 4 (four segments lit), 7 (three segments
lit) and 8 (seven segments lit). The first part wanted us to count how
many times these digits appeared in a sample.
So, for the first part, I threw together a simple parser.
First, I wanted to split the lines, selecting the part before the |
from the part after. (Later, I realized that I could have selected the
first 10 tokens and separated them from the last four.):
split=:{{
k=. y i. '|'
((k{.y);k}.y)-.L:0 '|'
}}
Then, I threw in a routine to split each line:
parse0=:{{
pairs=. split;._2 y
}}
And, I also decided that I wanted to know how many lit segments each digit had:
lens=: 6 2 5 5 4 5 6 3 7 6
So, for the first part, I tokenized the output part, and used the
length (in characters) of each token checked which were element of (1
4 7 8{lens) and counted the members:
aoc8a=:{{
dat=. parse0 y
out=.{:"1 dat
+/(1 4 7 8 { lens) e.~ ;#@>@;:L:0 out
}}
For the second part, we are supposed to completely analyze each line
so that we know what each digit in the output represents, then we
total each of the four digit numbers represented in the output part of
each represented display.
Here, I decided I wanted to work with tokens instead of characters, so
I added another parsing stage. Sorting the characters in each token
was probably not necessary, but it made me feel better...
parse1=: {{
/:~L:0 ;:each parse0 y
}}
Once I had this, I built a straightforward inference procedure:
analyze=: {{
txt=. ;parse1 y
simple=. (lens&(I.@e.)each L:0 ) #@> txt
samp=. txt{{
s=. ~.m #~ x&e.@> y
assert. 1=#s
;s
}}
one=. 1 samp simple
four=. 4 samp simple
seven=. 7 samp simple
eight=. 8 samp simple
aaaa=. seven -. one
bbdddd=. four -. one
d069=. ~.(6=#@>txt)#txt assert.3=#d069
l069=. #@>d069 ([ -. -.)L:0 bbdddd
zero=. ;(1=l069)#d069
dddd=. bbdddd-.zero
bb=. bbdddd-.dddd
d235=. ~.(5=#@>txt)#txt assert.3=#d235
five=. ;(;bb&e.each d235)#d235
three=. ;(;*/@(one&e.)each d235)#d235
two=. ;d235-.three;five
ff=. three -. two
cc=. one -. ff
six=. ;(-.;cc&e.each d069)#d069
nine=. d069-.zero;six
/:~ each zero;one;two;three;four;five;six;seven;eight;nine
}}
Hopefully my variable naming is reasonably obvious. here, But a couple
might be obscure:
Names like aaaa or bb contain the letter representing the
corresponding section(s) of the digits variable. (The values here are
not repeated -- I just repeated the characters in the names to
emphasize the distinction between horizontal and vertical segments.
Staying oriented was helpful to me when writing this.) I never had a
use for aaaa ... oh well...
Names like zero, one, two contain the corresponding tokens from that
line. (They do not necessarily have their characters in canonical
order, I fix that up on the last line.)
d069 is the three tokens which correspond to digits 0, 6 and 9
l069 is the length of each of those tokens with a couple segments removed.
In other words, it constructs a lookup table. ((analyze input) i.
output) gives the four digits of the output.
I could have made this neater (by restructuring where I did my
parsing, and/or by making parsing pass boxed values through
unmodified), but my part b implementation looked like this:
aoc8b=: {{
keys=. analyze;.2 y
+/10 #. keys i."1 /:~each ;@{:"1 parse1 y
}}
----------------------
After going through all that, the next day I decided I could have been
a lot more concise in my implementation. Also, I didn't need to
manually unwrap lines.
Here's the rewrite:
normalize=: {{
lines=.<;._2 y,(-.LF e.y)#LF
divs=. '|' e.&> lines
if. (#lines) < +/divs do.
lines=. (divs#lines),each (-.divs)#lines
end.
{{(10&{. ,&< _4&{.) /:~each ;:y}}@> lines
}}
infer=: {{
'inp out'=:,normalize y
'one seven four is235 is069 eight'=. ,(<^:(1<#)/.~ #@>) (/: #@>)inp
'bb ee ff'=. (~.{~ 6 4 9 i.~ #/.~) ;inp
'cc'=. one-.ff
'aaaa dddd'=. (seven;four)-.L:0 bb,cc,ff
'two three five'=. (/: (bb,ff) +/@e."1 >) is235
'zero six nine'=. (/: (dddd,cc) e."1 >) is069
10 #. (zero;one;two;three;four;five;six;seven;eight;nine) i. out
}};._2
Writing that wasn't really right for puzzle solving speed, but perhaps
it's readable enough. The infer routine starts by counting token
lengths (sorting tokens in ascending length order, then grouping by
length and counting how many tokens of each length appear), then
breaks things down based on segment structure of those digits and
groups of digits..
Leaving me with this cleaner implementation for the second part:
aoc8b=: +/@infer
--
Raul
----------------------------------------------------------------------
For information about J forums see http://www.jsoftware.com/forums.htm