<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Keith&#039;s Electronics Blog &#187; 2023</title>
	<atom:link href="http://www.neufeld.newton.ks.us/electronics/?cat=39&#038;feed=rss2" rel="self" type="application/rss+xml" />
	<link>http://www.neufeld.newton.ks.us/electronics</link>
	<description></description>
	<lastBuildDate>Fri, 18 Apr 2025 00:10:55 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.1.3</generator>
		<item>
		<title>AoC 2023 D8P1:  Traversing a Digraph</title>
		<link>http://www.neufeld.newton.ks.us/electronics/?p=2371</link>
		<comments>http://www.neufeld.newton.ks.us/electronics/?p=2371#comments</comments>
		<pubDate>Sat, 09 Dec 2023 15:39:57 +0000</pubDate>
		<dc:creator>Keith Neufeld</dc:creator>
				<category><![CDATA[2023]]></category>
		<category><![CDATA[Advent of Code]]></category>

		<guid isPermaLink="false">http://www.neufeld.newton.ks.us/electronics/?p=2371</guid>
		<description><![CDATA[Day 8 part 1 gives us a directed graph of nodes with links to two (hopefully other) nodes and a set of dance moves to perform through the graph; how many steps to get from AAA to ZZZ at the end of a dance pattern? (A lot more steps if you stray into DDD, EEE, [...]]]></description>
			<content:encoded><![CDATA[<p><a href="https://adventofcode.com/2023/day/8">Day 8 part 1</a> gives us a directed graph of nodes with links to two (hopefully other) nodes and a set of dance moves to perform through the graph; how many steps to get from <code>AAA</code> to <code>ZZZ</code> at the end of a dance pattern?  (A lot more steps if you stray into <code>DDD</code>, <code>EEE</code>, or <code>GGG</code>.)</p>
<p><code>RL</p>
<p>AAA = (BBB, CCC)<br />
BBB = (DDD, EEE)<br />
CCC = (ZZZ, GGG)<br />
DDD = (DDD, DDD)<br />
EEE = (EEE, EEE)<br />
GGG = (GGG, GGG)<br />
ZZZ = (ZZZ, ZZZ)<br />
</code></p>
<p>All one need do is make a hash of the nodes with their branches, then dance through it.</p>
<p><span id="more-2371"></span></p>
<p><code>my %index = ( L => 0, R => 1 );</code></p>
<p>The dance moves are specified as <code>L</code> or </code>R</code> and for no particular reason I decided to save the branches as arrays instead of hashes, so I prepare to translate dance moves into array indices.  Why on earth did I call this <code>%index</code> instead of <code>%dirindex</code> or <code>%dir</code>?  Because I had just woken up and it was time to do AoC?  Probably.</p>
<p><code>$_ = <>; chomp;<br />
my @instr = map { $index{$_} } split(//);</p>
<p><>;<br />
</code></p>
<p>I grab the dance moves, translate them from directions to indices, and toss the following blank line.</p>
<p><code>my %map;<br />
while (<>) {<br />
    my ($node, $l, $r) = /(\w{3})/g;<br />
    $map{$node} = [ $l, $r ];<br />
}<br />
</code></p>
<p>Then read in and save all the nodes.</p>
<p><code>my $count = 0;<br />
my $pos = "AAA";</p>
<p>until ($count % @instr == 0 &#038;&#038; $pos eq "ZZZ") {<br />
    $pos = $map{$pos}[$instr[$count++ % @instr]];<br />
}<br />
</code></p>
<p>Finally, start at <code>AAA</code>; take steps, counting as I go (and using the modulus of the number of dance moves in the pattern); and stop at <code>ZZZ</code> only if it's the end of this dance pattern.</p>
<h3>Full Program</h3>
<p><code>#!/usr/bin/perl</p>
<p>use warnings;<br />
use strict;</p>
<p>my %index = ( L => 0, R => 1 );</p>
<p>$_ = <>; chomp;<br />
my @instr = map { $index{$_} } split(//);</p>
<p><>;</p>
<p>my %map;<br />
while (<>) {<br />
    my ($node, $l, $r) = /(\w{3})/g;<br />
    $map{$node} = [ $l, $r ];<br />
}</p>
<p>my $count = 0;<br />
my $pos = "AAA";</p>
<p>until ($count % @instr == 0 &#038;&#038; $pos eq "ZZZ") {<br />
    $pos = $map{$pos}[$instr[$count++ % @instr]];<br />
}</p>
<p>print "$count steps\n";<br />
</code></p>
]]></content:encoded>
			<wfw:commentRss>http://www.neufeld.newton.ks.us/electronics/?feed=rss2&#038;p=2371</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>AoC 2023 D7P2:  Pseudo-Poker Hands with Wildcards</title>
		<link>http://www.neufeld.newton.ks.us/electronics/?p=2364</link>
		<comments>http://www.neufeld.newton.ks.us/electronics/?p=2364#comments</comments>
		<pubDate>Sat, 09 Dec 2023 15:14:56 +0000</pubDate>
		<dc:creator>Keith Neufeld</dc:creator>
				<category><![CDATA[2023]]></category>
		<category><![CDATA[Advent of Code]]></category>

		<guid isPermaLink="false">http://www.neufeld.newton.ks.us/electronics/?p=2364</guid>
		<description><![CDATA[Part 2 redefines J from jack to joker, making jokers wildcards when determining type of hand but the lowest value when comparing individual cards. This requires very little modification to the part 1 program: my $cardlist = "AKQT98765432J"; Change the card sort order; my $jokers = grep { $_ eq "J" } @cards; count the [...]]]></description>
			<content:encoded><![CDATA[<p>Part 2 redefines <code>J</code> from jack to joker, making jokers wildcards when determining type of hand but the lowest value when comparing individual cards.  This requires very little modification to the part 1 program:</p>
<p><code>    my $cardlist = "AKQT98765432J";</code></p>
<p>Change the card sort order;</p>
<p><code>    my $jokers = grep { $_ eq "J" } @cards;</code></p>
<p>count the jokers;</p>
<p><code>    ++ $tally{$_} foreach grep { $_ ne "J" } @cards;</code></p>
<p>omit the jokers when counting cards for type of hand;</p>
<p><code>    $ofakind[0] += $jokers;</code></p>
<p>and in this poker variant, simply add the count of jokers to the count of the most-frequent card when determining type of hand.</p>
<p><span id="more-2364"></span></p>
<h3>Full Program</h3>
<p><code>#!/usr/bin/perl</p>
<p>use warnings;<br />
use strict;</p>
<p>sub cardval;</p>
<p>my @hands;</p>
<p>while (<>) {<br />
    my ($cards, $bid) = /(\w+)/g;<br />
    my @cards = split(//, $cards);<br />
    my @values = map { cardval($_) } @cards;<br />
    my $jokers = grep { $_ eq "J" } @cards;</p>
<p>    my %tally;<br />
    ++ $tally{$_} foreach grep { $_ ne "J" } @cards;<br />
    my @ofakind = sort { $b <=> $a } values %tally;<br />
    $ofakind[0] += $jokers;<br />
    my $type = $ofakind[0] == 5 ? 6			#   5 of kind -- 6<br />
	    : $ofakind[0] == 4 ? 5			#   4 of kind -- 5<br />
	    : $ofakind[0] == 3 &#038;&#038; $ofakind[1] == 2 ? 4	#   full house -- 4<br />
	    : $ofakind[0] == 3 ? 3			#   3 of kind -- 3<br />
	    : $ofakind[0] == 2 &#038;&#038; $ofakind[1] == 2 ? 2	#   2 pair -- 2<br />
	    : $ofakind[0] == 2 ? 1			#   1 pair -- 1<br />
	    : 0;					#   nothing -- 0</p>
<p>    push(@hands, { CARDS => [ @cards ], BID => $bid, VALUES => [ @values ],<br />
	    TYPE => $type });<br />
}</p>
<p>my @sortedhands = sort { ${$a}{TYPE} <=> ${$b}{TYPE}<br />
	|| ${$a}{VALUES}[0] <=> ${$b}{VALUES}[0]<br />
	|| ${$a}{VALUES}[1] <=> ${$b}{VALUES}[1]<br />
	|| ${$a}{VALUES}[2] <=> ${$b}{VALUES}[2]<br />
	|| ${$a}{VALUES}[3] <=> ${$b}{VALUES}[3]<br />
	|| ${$a}{VALUES}[4] <=> ${$b}{VALUES}[4]<br />
} @hands;</p>
<p>my $sum;<br />
$sum += ($_ + 1) * $sortedhands[$_]{BID} foreach (0 .. scalar @sortedhands - 1);<br />
print "sum is $sum\n";</p>
<p>sub cardval {<br />
    my $c = shift;<br />
    my $cardlist = "AKQT98765432J";<br />
    return length($cardlist) - index($cardlist, $c);<br />
}<br />
</code></p>
]]></content:encoded>
			<wfw:commentRss>http://www.neufeld.newton.ks.us/electronics/?feed=rss2&#038;p=2364</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>AoC 2023 D7P1:  Pseudo-Poker Hands</title>
		<link>http://www.neufeld.newton.ks.us/electronics/?p=2357</link>
		<comments>http://www.neufeld.newton.ks.us/electronics/?p=2357#comments</comments>
		<pubDate>Sat, 09 Dec 2023 15:08:34 +0000</pubDate>
		<dc:creator>Keith Neufeld</dc:creator>
				<category><![CDATA[2023]]></category>
		<category><![CDATA[Advent of Code]]></category>

		<guid isPermaLink="false">http://www.neufeld.newton.ks.us/electronics/?p=2357</guid>
		<description><![CDATA[Day 7 introduces a card game with hand values loosely based on poker: 5 of a kind, 4 of a kind, full house, 3 of a kind, 2 pair, 2 of a kind, nuffin&#8217;. A tie on the type of hand is resolved by the rank of the cards in order dealt (not in order [...]]]></description>
			<content:encoded><![CDATA[<p><a href="https://adventofcode.com/2023/day/7">Day 7</a> introduces a card game with hand values loosely based on poker:  5 of a kind, 4 of a kind, full house, 3 of a kind, 2 pair, 2 of a kind, nuffin&#8217;.  A tie on the type of hand is resolved by the rank of the cards <em>in order dealt</em> (not in order of rank), with ordering AKQJT98765432.  Given these rules, rank your hands, then multiply the ordinal value of the resulting list by a unique coefficient for that hand.</p>
<p><code>32T3K 765<br />
T55J5 684<br />
KK677 28<br />
KTJJT 220<br />
QQQJA 483<br />
</code></p>
<p>This sort is going to require a fair number of comparisons, each involving rank of types of hands and each potentially involving rank of cards.  These comparisons are computationally-intensive enough that I&#8217;m leery about executing them during each comparison of the sort, so I calculated and cached them up front.</p>
<p><span id="more-2357"></span></p>
<p><code>sub cardval {<br />
    my $c = shift;<br />
    my $cardlist = "AKQJT98765432";<br />
    return length($cardlist) - index($cardlist, $c);<br />
}<br />
</code></p>
<p>The card&#8217;s rank can be represented as its (here 0-based) ordinal position in an ascending-order list of ranks.  I had a descending-order list and subtracted the position in the string from the length of the string; but doing this again, I&#8217;d use Perl&#8217;s <code>reverse</code> operator to reverse the string.</p>
<p><code>$cardlist</code> is constant and it would be more efficient to declare it once outside the subroutine than each time the subroutine is called.  I&#8217;d love to define it immediately before the subroutine definition, but that code won&#8217;t have been executed yet when the subroutine is called.  I could have defined it at the top of the program but I don&#8217;t like separating it so far from its sole use in the code.  I don&#8217;t know whether Perl has developed a better way to handle this situation.</p>
<p><code>while (<>) {<br />
    my ($cards, $bid) = /(\w+)/g;<br />
    my @cards = split(//, $cards);<br />
    my @values = map { cardval($_) } @cards;<br />
</code></p>
<p>Reading in the hands, I grab the two chunks of &#8220;word&#8221; characters, split the card string into a list, and use Perl&#8217;s <code>map</code> operator to translate the list of cards into a list of their values/ranks.</p>
<p><code>    my %tally;<br />
    ++ $tally{$_} foreach @cards;<br />
    my @ofakind = sort { $b <=> $a } values %tally;<br />
    my $type = $ofakind[0] == 5 ? 6			#   5 of kind -- 6<br />
	    : $ofakind[0] == 4 ? 5			#   4 of kind -- 5<br />
	    : $ofakind[0] == 3 &#038;&#038; $ofakind[1] == 2 ? 4	#   full house -- 4<br />
	    : $ofakind[0] == 3 ? 3			#   3 of kind -- 3<br />
	    : $ofakind[0] == 2 &#038;&#038; $ofakind[1] == 2 ? 2	#   2 pair -- 2<br />
	    : $ofakind[0] == 2 ? 1			#   1 pair -- 1<br />
	    : 0;					#   nothing -- 0<br />
</code></p>
<p>Then I count how many of each rank of card I have in this hand and sort that list of counts into descending order.  From the number of cards that I have the most of, and sometimes the second-most of, I can determine the type of the hand; and I assign a numerical score to each type.</p>
<p>When we have three or two of the most-frequent card, there are two possible types of hands depending on whether the remaining cards contain a pair.  I could have written those four cases as two outer ternaries each with an inner ternary; but for ease of reading (and I assure you my code is nicely tabbed and aligned when you&#8217;re not looking at it in WordPress), I didn&#8217;t mind the expense of rechecking the count of the most-frequent card in order to write the code for every type of hand the same way.</p>
<p><code>    push(@hands, { CARDS => [ @cards ], BID => $bid, VALUES => [ @values ],<br />
	    TYPE => $type });<br />
}<br />
</code></p>
<p>I&#8217;ve now precalculated everything that I wanted to cache before the sort and I hang onto it in a list of references to anonymous hashes, which function like a C struct, with the hash key being the struct member.</p>
<p>I don&#8217;t actually need the cards any more, but cached them in case I wanted them later for debugging.</p>
<p><code>my @sortedhands = sort { ${$a}{TYPE} <=> ${$b}{TYPE}<br />
        || ${$a}{VALUES}[0] <=> ${$b}{VALUES}[0]<br />
        || ${$a}{VALUES}[1] <=> ${$b}{VALUES}[1]<br />
        || ${$a}{VALUES}[2] <=> ${$b}{VALUES}[2]<br />
        || ${$a}{VALUES}[3] <=> ${$b}{VALUES}[3]<br />
        || ${$a}{VALUES}[4] <=> ${$b}{VALUES}[4]<br />
} @hands;<br />
</code></p>
<p>With all that cached, the sort itself is easy.  Note that Perl uses the special variables <code>$a</code> and <code>$b</code> for the comparands in each sort comparison; and since we&#8217;re sorting a list of hash references, <code>$a</code> and <code>$b</code> do need to be dereferenced.</p>
<p><code>my $sum;<br />
$sum += ($_ + 1) * $sortedhands[$_]{BID} foreach (0 .. scalar @sortedhands - 1);<br />
print "sum is $sum\n";<br />
</code></p>
<p>With the hands sorted, calculating the requested sum is easy.</p>
<h3>Full Program</h3>
<p><code>#!/usr/bin/perl</p>
<p>use warnings;<br />
use strict;</p>
<p>sub cardval;</p>
<p>my @hands;</p>
<p>while (<>) {<br />
    my ($cards, $bid) = /(\w+)/g;<br />
    my @cards = split(//, $cards);<br />
    my @values = map { cardval($_) } @cards;</p>
<p>    my %tally;<br />
    ++ $tally{$_} foreach @cards;<br />
    my @ofakind = sort { $b <=> $a } values %tally;<br />
    my $type = $ofakind[0] == 5 ? 6			#   5 of kind -- 6<br />
	    : $ofakind[0] == 4 ? 5			#   4 of kind -- 5<br />
	    : $ofakind[0] == 3 &#038;&#038; $ofakind[1] == 2 ? 4	#   full house -- 4<br />
	    : $ofakind[0] == 3 ? 3			#   3 of kind -- 3<br />
	    : $ofakind[0] == 2 &#038;&#038; $ofakind[1] == 2 ? 2	#   2 pair -- 2<br />
	    : $ofakind[0] == 2 ? 1			#   1 pair -- 1<br />
	    : 0;					#   nothing -- 0</p>
<p>    push(@hands, { CARDS => [ @cards ], BID => $bid, VALUES => [ @values ],<br />
	    TYPE => $type });<br />
}</p>
<p>my @sortedhands = sort { ${$a}{TYPE} <=> ${$b}{TYPE}<br />
	|| ${$a}{VALUES}[0] <=> ${$b}{VALUES}[0]<br />
	|| ${$a}{VALUES}[1] <=> ${$b}{VALUES}[1]<br />
	|| ${$a}{VALUES}[2] <=> ${$b}{VALUES}[2]<br />
	|| ${$a}{VALUES}[3] <=> ${$b}{VALUES}[3]<br />
	|| ${$a}{VALUES}[4] <=> ${$b}{VALUES}[4]<br />
} @hands;</p>
<p>my $sum;<br />
$sum += ($_ + 1) * $sortedhands[$_]{BID} foreach (0 .. scalar @sortedhands - 1);<br />
print "sum is $sum\n";</p>
<p>sub cardval {<br />
    my $c = shift;<br />
    my $cardlist = "AKQJT98765432";<br />
    return length($cardlist) - index($cardlist, $c);<br />
}<br />
</code></p>
]]></content:encoded>
			<wfw:commentRss>http://www.neufeld.newton.ks.us/electronics/?feed=rss2&#038;p=2357</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>AoC 2023 D6P1, P2:  Quadratic Formula or Count</title>
		<link>http://www.neufeld.newton.ks.us/electronics/?p=2347</link>
		<comments>http://www.neufeld.newton.ks.us/electronics/?p=2347#comments</comments>
		<pubDate>Thu, 07 Dec 2023 03:42:02 +0000</pubDate>
		<dc:creator>Keith Neufeld</dc:creator>
				<category><![CDATA[2023]]></category>
		<category><![CDATA[Advent of Code]]></category>

		<guid isPermaLink="false">http://www.neufeld.newton.ks.us/electronics/?p=2347</guid>
		<description><![CDATA[Day 6 problem 1 asks us to consider a series of toy boat races. The longer you have the boat on the charger, the faster it&#8217;ll travel during the remaining units of time. With how many different integer charge times can you beat the record distance in that race? The distance traveled is (ignoring units) [...]]]></description>
			<content:encoded><![CDATA[<p><a href="https://adventofcode.com/2023/day/6">Day 6 problem 1</a> asks us to consider a series of toy boat races.  The longer you have the boat on the charger, the faster it&#8217;ll travel during the remaining units of time.  With how many different integer charge times can you beat the record distance in that race?</p>
<p>The distance traveled is (ignoring units) t<sub>charge</sub> ( t<sub>total</sub> &#8211; t<sub>charge</sub>); so the answer to the problem can be found directly by using your favorite quadratic solver on t<sub>charge</sub><sup>2</sup> &#8211; t<sub>total</sub> t<sub>charge</sub> + d<sub>record</sub> = 0, which will have zero, one, or two real solutions.  If it has zero solutions, one solution that&#8217;s non-integer, or two non-integer solutions between two consecutive integers, then there are zero integer charge times that beat the record.  Otherwise count the number of integers from the floor of the lower solution plus one to the ceiling of the upper minus one.  (That sounds weird but math it out &#8212; it ensures not merely tying the record but beating it.)</p>
<p>Anyone who remembers algebra and has dignity and self-respect would use this trivial approach.</p>
<p>I wrote a program to count winning charge times by iteration.</p>
<p><span id="more-2347"></span></p>
<p><code>    my $count = grep { $_ * ($t - $_) > $d } (1 .. $t - 1);</code></p>
<p>All of the Perl syntax in this program, I&#8217;ve already used this year, so there&#8217;s nothing new to explain.  The only thing that seems worth calling out is that finding &#8220;qualifying&#8221; members of a list, or even of a small range of numbers, is very conveniently done with <code>grep</code>.</p>
<p>Part 2 says that the columns of numbers in the input don&#8217;t represent separate races; the spaces between them were unintentional and the digits should be run together into larger numbers.  Having already written this in part 1:</p>
<p><code>    @time = /(\d+)/g if /Time/;<br />
    @dist = /(\d+)/g if /Dist/;<br />
</code></p>
<p>it was the pinnacle of <del>laziness</del> efficiency to reuse that digit extraction and join the results together:</p>
<p><code>    $t = join("", /(\d+)/g) if /Time/;<br />
    $d = join("", /(\d+)/g) if /Dist/;<br />
</code></p>
<p>But one could also use <code>s/[^\d]+//g</code> to delete everything that&#8217;s not a digit, if that&#8217;s what floats your boat.</p>
<h3>Full Part 1 Program</h3>
<p><code>#!/usr/bin/perl</p>
<p>use warnings;<br />
use strict;</p>
<p>my (@time, @dist);</p>
<p>while (<>) {<br />
    @time = /(\d+)/g if /Time/;<br />
    @dist = /(\d+)/g if /Dist/;<br />
}</p>
<p>my $prod;<br />
while (@time) {<br />
    my $t = shift @time; my $d = shift @dist;</p>
<p>    my $count = grep { $_ * ($t - $_) > $d } (1 .. $t - 1);</p>
<p>    $prod = defined $prod ? $prod * $count : $count;<br />
}</p>
<p>print "product $prod\n";<br />
</code></p>
<h3>Full Part 2 Program</h3>
<p><code>#!/usr/bin/perl</p>
<p>use warnings;<br />
use strict;</p>
<p>my ($t, $d);</p>
<p>while (<>) {<br />
    $t = join("", /(\d+)/g) if /Time/;<br />
    $d = join("", /(\d+)/g) if /Dist/;<br />
}</p>
<p>my $count = grep { $_ * ($t - $_) > $d } (1 .. $t - 1);</p>
<p>print "count $count\n";<br />
</code></p>
]]></content:encoded>
			<wfw:commentRss>http://www.neufeld.newton.ks.us/electronics/?feed=rss2&#038;p=2347</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>AoC 2023 D5P2:  Interval Intersections</title>
		<link>http://www.neufeld.newton.ks.us/electronics/?p=2332</link>
		<comments>http://www.neufeld.newton.ks.us/electronics/?p=2332#comments</comments>
		<pubDate>Thu, 07 Dec 2023 01:50:30 +0000</pubDate>
		<dc:creator>Keith Neufeld</dc:creator>
				<category><![CDATA[2023]]></category>
		<category><![CDATA[Advent of Code]]></category>

		<guid isPermaLink="false">http://www.neufeld.newton.ks.us/electronics/?p=2332</guid>
		<description><![CDATA[Day 5 part 2 reveals that the seed list does not actually denote individual seed values; it is pairs of numbers denoting ranges of seeds. Run each range through the translations and find the lowest value in any resulting range. After the way I wrote the first program, this made me feel like Obi-Wan just [...]]]></description>
			<content:encoded><![CDATA[<p>Day 5 part 2 reveals that the seed list does not actually denote individual seed values; it is pairs of numbers denoting ranges of seeds.  Run each <em>range</em> through the translations and find the lowest value in any resulting range.</p>
<p>After the way I wrote the first program, this made me feel like Obi-Wan just told me to go home and rethink my life.  I could enumerate each range of seeds and run them all through the translations &#8230; for certain values of &#8220;could&#8221; that include more processing power, electricity, and time than I have remaining in my years on this planet.</p>
<p>It was obvious that I was going to have to treat ranges as data structures, intersect them with ranges in translation rules, translate them accordingly, and be prepared to split seed ranges that overlapped translation ranges to apply the translation to only a portion of the input range.  Afflicted with a serious case of <em>I don&#8217;t wanna</em>, I pretended to be too busy with other things to get around to writing this yesterday.</p>
<p>But early this morning I cured my case of <em>I don&#8217;t wanna</em> in the way I&#8217;ve learned to cure any case related to programming:  Write the utilitarian loops that do the boring work of the program; and once they&#8217;re done, there&#8217;s so little of the program left to write that I&#8217;m ready to go ahead and do it.</p>
<p><em>20231207 edit: I omitted handling one way that intervals can intersect and it was accidental that my program handles that case correctly.  More below.</em></p>
<p><span id="more-2332"></span></p>
<h3>Read the Seed Ranges</h3>
<p>In part 1, I iterated through seeds, applying consecutive translations to each.  For part 2, I iterate through mapping tables, applying each to all seed ranges / translated seed ranges.</p>
<p><code>$_ = <>;<br />
chomp;<br />
my @ranges = /(\d+)/g if /^seeds/;<br />
</code></p>
<p>So start by grabbing all of the seed ranges.  Don&#8217;t bother parsing them into (start, length) pairs yet.</p>
<h3>Read a Translation Map</h3>
<p><code>#   Loop through mapping rules, applying each to our list of seed ranges.<br />
while (<>) {<br />
    #   Parse the map, saving destination, start, length, end, and offset.<br />
    my @map;<br />
    foreach (grep { ! /map/ } split /\n/) {<br />
	my @n = split /\s+/;<br />
	push @map, { D => $n[0], S => $n[1], L => $n[2],<br />
		E => $n[1] + $n[2] - 1, O => $n[0] - $n[1] };<br />
    }<br />
</code></p>
<p>Each remaining input record is a translation map.  I totes forgot to <code>chomp</code> here and I suspect that <code>split</code> on whitespace (A) matches newlines as whitespace and (B) discards empty trailing fields, but don&#8217;t quote me on that without you test it yourself first.</p>
<p>So &#8230; split the paragraph into individual lines.  Discard the line that has <code>map</code> in it.  For the remaining lines, split into whitespace-delimited fields.  Create an anonymous hash and push its reference onto the array of this map.  I went all out here and precomputed every value I thought I might want later, stuffing them all into the hash:  <code>D</code>est, <code>S</code>ource, and <code>L</code>ength were provided; and I also calculated <code>E</code>nd and <code>O</code>ffset here because I was going to use them multiple times later and because I was more likely to write an off-by-one error later than when I was paying attention here.</p>
<h3>Checking Current Range for Translation Intersection</h3>
<p><code>    #   Apply this transformation to our list of number ranges,<br />
    #   building a list of resulting number ranges.<br />
    my @newranges;<br />
    range: while (@ranges) {<br />
	my $s = shift @ranges; my $l = shift @ranges; my $e = $s + $l - 1;<br />
</code></p>
<p>For this set of map rules, loop through all of our current set of ranges of interest.  Within each, break out the (start, length) pair and for convenience, also calculate the end of the range.</p>
<p><code>	#   Check whether the current range intersects any map range.<br />
	maprule: foreach my $href (@map) {<br />
	    #   If this range doesn't intersect this map rule, try the next.<br />
	    next maprule if $e < $$href{S} || $$href{E} < $s;<br />
</code></p>
<p>Loop through the mapping rules.  For each, first check whether the current range of interest doesn't even intersect with the range of this map rule -- the end of the range of interest is less than the start of the map range, or the end of the map range is less than the start of the range of interest.  If no intersection with this map rule, go on and try the next.</p>
<p><code>	    #   If this range is completely enclosed in this map rule, apply.<br />
	    if ($$href{S} <= $s &#038;&#038; $e <= $$href{E}) {<br />
		$s += $$href{O};<br />
		push(@newranges, $s, $l);<br />
		next range;<br />
	    }<br />
</code></p>
<p>Maybe this range of interest is a subset of the range of the map rule?  If so, apply the map rule's translation (offset) to the start of the range, then push the translated (start, length) pair onto the list of new ranges to be used in the next mapping, and move on to the next range of interest.</p>
<p><code>	    #   This range intersects but is not fully enclosed in<br />
	    #   this map rule.  Split, apply the transformation to the<br />
	    #   intersection, and push the leftover back onto this pass.<br />
	    if ($s < $$href{S}) {	#   overlaps start of map rule<br />
		my $s1 = $s;<br />
		my $e1 = $$href{S} - 1;<br />
		my $l1 = $e1 - $s1 + 1;<br />
		unshift(@ranges, $s1, $l1);</p>
<p>		my $s2 = $$href{S};<br />
		my $e2 = $e;<br />
		my $l2 = $e2 - $s2 + 1;<br />
		push(@newranges, $s2 + $$href{O}, $l2);</p>
<p>		next range;<br />
</code></p>
<p>If we've fallen through the logic to this point, then the range of interest <em>does</em> overlaps this mapping rule's range (and is not simply a subset).  Does the range of interest overlap the beginning of the map rule (or the end)?</p>
<p>Either way, break this range of interest into two ranges of interest.  In the case of overlapping the beginning of the map range, the first new range of interest is passed <em>back into the list of ranges for the current round</em> (because another map rule might apply to it) with no translation and the second is passed forward for the next round with this translation rule's offset applied.  Clearly all of this could have been written in a single line; but breaking it out into variables makes it very clear what I'm doing, is easier for me to avoid more off-by-one errors, costs me next to nothing, and is probably even optimized away.</p>
<p><code>unshift</code> pushes values onto the beginning of a list (array); <code>push</code> pushes values onto the end.  I'm putting the unprocessed leftovers from this cut back onto the beginning of the current list and the translated range from this cut onto the end of next time's list.</p>
<p>And then move on to the next range of interest.</p>
<p>The code for a range of interest that overlaps the end of this mapping rule's range is very similar.</p>
<p><em>20231207 edit: As I was drafting notes about intervals in preparation for programming, I included the case where a map rule range was completely enclosed within a range of interest, splitting the range of interest into <strong>three</strong> intervals; but I forgot about that when coding.</p>
<p>My code accidentally handles it correctly because the case for a range of interest overlapping the start of a map range splits at the beginning of the map range <strong>and pushes the latter part back into the current queue</strong>.  When it comes up again, it gets handled by the code for overlapping the end of a map rule.</p>
<p>Very lucky and very sloppy.</em></p>
<p><code>	#   No intersection, so current range is untouched.<br />
	push(@newranges, $s, $l);<br />
    }</p>
<p>    #   Save the resulting list of ranges for next time.<br />
    @ranges = @newranges;<br />
</code></p>
<p>If we fall through the loop of testing all of the mapping rules without having matched one and <code>next</code>ed on to the next range of interest, then this range of interest has no translation in this map and is preserved intact for the next round.</p>
<h3>Find the Minimum Translated Value</h3>
<p><code>#   Find the minimum range start after all processing.<br />
my $min;<br />
while (@ranges) {<br />
    my $s = shift @ranges; shift @ranges;<br />
    $min = $s if !defined $min || $s < $min;<br />
}<br />
</code></p>
<p>In my approach to part 1, the minimum was captured as a running value along the way; but in part 2, I need to go back and find it myself.</p>
<p><code>@ranges</code> is (still) an unstructured list of (start, length) pairs and I don't remember if there's a cool-kid way to select alternating values from a list; so I iterate through the list, looking for the minimum range start (which will be the lowest value in any range).</p>
<h3>Full Program</h3>
<p><code>#!/usr/bin/perl</p>
<p>use warnings;<br />
use strict;</p>
<p>#   Get seed list; trust that it's first.<br />
$/ = "\n\n";<br />
$_ = <>;<br />
chomp;<br />
my @ranges = /(\d+)/g if /^seeds/;</p>
<p>#   Loop through mapping rules, applying each to our list of seed ranges.<br />
while (<>) {<br />
    #   Parse the map, saving destination, start, length, end, and offset.<br />
    my @map;<br />
    foreach (grep { ! /map/ } split /\n/) {<br />
	my @n = split /\s+/;<br />
	push @map, { D => $n[0], S => $n[1], L => $n[2],<br />
		E => $n[1] + $n[2] - 1, O => $n[0] - $n[1] };<br />
    }</p>
<p>    #   Apply this transformation to our list of number ranges,<br />
    #   building a list of resulting number ranges.<br />
    my @newranges;<br />
    range: while (@ranges) {<br />
	my $s = shift @ranges; my $l = shift @ranges; my $e = $s + $l - 1;</p>
<p>	#   Check whether the current range intersects any map range.<br />
	maprule: foreach my $href (@map) {<br />
	    #   If this range doesn't intersect this map rule, try the next.<br />
	    next maprule if $e < $$href{S} || $$href{E} < $s;</p>
<p>	    #   If this range is completely enclosed in this map rule, apply.<br />
	    if ($$href{S} <= $s &#038;&#038; $e <= $$href{E}) {<br />
		$s += $$href{O};<br />
		push(@newranges, $s, $l);<br />
		next range;<br />
	    }</p>
<p>	    #   This range intersects but is not fully enclosed in<br />
	    #   this map rule.  Split, apply the transformation to the<br />
	    #   intersection, and push the leftover back onto this pass.<br />
	    if ($s < $$href{S}) {	#   overlaps start of map rule<br />
		my $s1 = $s;<br />
		my $e1 = $$href{S} - 1;<br />
		my $l1 = $e1 - $s1 + 1;<br />
		unshift(@ranges, $s1, $l1);</p>
<p>		my $s2 = $$href{S};<br />
		my $e2 = $e;<br />
		my $l2 = $e2 - $s2 + 1;<br />
		push(@newranges, $s2 + $$href{O}, $l2);</p>
<p>		next range;<br />
	    } else {			#   overlaps end of map rule<br />
		my $s1 = $s;<br />
		my $e1 = $$href{E};<br />
		my $l1 = $e1 - $s1 + 1;<br />
		push(@newranges, $s1 + $$href{O}, $l1);</p>
<p>		my $s2 = $$href{E} + 1;<br />
		my $e2 = $e;<br />
		my $l2 = $e2 - $s2 + 1;<br />
		unshift(@ranges, $s2, $l2);</p>
<p>		next range;<br />
	    }<br />
	}</p>
<p>	#   No intersection, so current range is untouched.<br />
	push(@newranges, $s, $l);<br />
    }</p>
<p>    #   Save the resulting list of ranges for next time.<br />
    @ranges = @newranges;<br />
}</p>
<p>#   Find the minimum range start after all processing.<br />
my $min;<br />
while (@ranges) {<br />
    my $s = shift @ranges; shift @ranges;<br />
    $min = $s if !defined $min || $s < $min;<br />
}</p>
<p>print "\nminimum location $min\n";<br />
</code></p>
]]></content:encoded>
			<wfw:commentRss>http://www.neufeld.newton.ks.us/electronics/?feed=rss2&#038;p=2332</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>AoC 2023 D5P1:  Integer Ranges</title>
		<link>http://www.neufeld.newton.ks.us/electronics/?p=2326</link>
		<comments>http://www.neufeld.newton.ks.us/electronics/?p=2326#comments</comments>
		<pubDate>Thu, 07 Dec 2023 01:08:11 +0000</pubDate>
		<dc:creator>Keith Neufeld</dc:creator>
				<category><![CDATA[2023]]></category>
		<category><![CDATA[Advent of Code]]></category>

		<guid isPermaLink="false">http://www.neufeld.newton.ks.us/electronics/?p=2326</guid>
		<description><![CDATA[Advent of Code day 5 has us processing ranges of integers. Given seed values and lists of translations in the form dest src length, find the lowest value after applying all the translations. seeds: 79 14 55 13 seed-to-soil map: 50 98 2 52 50 48 soil-to-fertilizer map: 0 15 37 37 52 2 39 [...]]]></description>
			<content:encoded><![CDATA[<p><a href="https://adventofcode.com/2023/day/5">Advent of Code day 5</a> has us processing ranges of integers.  Given seed values and lists of translations in the form <code>dest src length</code>, find the lowest value after applying all the translations.</p>
<p><code>seeds: 79 14 55 13</p>
<p>seed-to-soil map:<br />
50 98 2<br />
52 50 48</p>
<p>soil-to-fertilizer map:<br />
0 15 37<br />
37 52 2<br />
39 0 15</p>
<p>...<br />
</code></p>
<p>I went over the top on this problem, loading all of the translations into memory as though they were important, then iterated through seeds in my outer loop and translations looping inside that on each seed.</p>
<p><span id="more-2326"></span></p>
<h3>Parsing the File</h3>
<p><code>sub getinput {<br />
    $/ = "\n\n";</p>
<p>    while (<>) {<br />
	chomp;<br />
</code></p>
<p><code>$/</code> is Perl&#8217;s input record separator, newline by default.  If you set it to something else, each read of a record (line) of input will be separated/ terminated by this record separator.  I set it to two newlines to grab a paragraph of input at a time.  Then <code>chomp</code> is aware and discards the trailing input record separator (the trailing pair of newlines, not just the last trailing newline).</p>
<p><code>	@seed = /(\d+)/g, next if /^seeds/;</code></p>
<p>Perl&#8217;s regular expression operators work fine on multi-line strings (and have some special modifiers available that I don&#8217;t need here).  So this line watches for the input paragraph that begins with <code>seeds</code>, grabs <code>( )</code> strings of digits <code>\d+</code> out of it, all of them <code>/g</code>, returns that list, and stores it in the array <code>@seed</code>.</p>
<p><code>	s/(\S+)\s+map:\n//; my $which = $1;<br />
	foreach (split /\n/) {<br />
	    my @n = split /\s+/;<br />
	    push @{$map{$which}}, { D => $n[0], S => $n[1], L => $n[2] };<br />
	}<br />
</code></p>
<p>If a paragraph starts with some kind of map, delete that line of the paragraph, grabbing the map name out of it along the way.  Then split the rest of the paragraph into lines; split each line on whitespace (also could have done <code>my @n = /(\d+)/g</code> again but I&#8217;m fickle); and treat the <code>%map</code> hash element keyed by <code>$which</code> as an array, pushing an anonymous hash <code>{ }</code> onto it keyed mnemonically by <code>D</code>est, <code>S</code>ource, and <code>L</code>ength, allowing rapid retrieval of these values later by symbol rather than merely by ordinal position as they were given to us in the puzzle input.</p>
<h3>Mapping Values by Range Translations</h3>
<p><code>sub mapval {<br />
    my ($src, $lref) = @_;<br />
</code></p>
<p>I want to call this subroutine with parameters: a source (seed) value and an array (list) reference to a set of range translations.  Subroutine parameters arrive in the special array <code>@_</code>, so break them out to variables with (vaguely) meaningful names.</p>
<p><code>    my $mapped;<br />
    foreach my $href (@$lref) {<br />
	$mapped = $$href{D} - $$href{S} + $src<br />
		if $src >= $$href{S} &#038;&#038; $src <= $$href{S} + $$href{L} - 1;<br />
    }<br />
</code></p>
<p>Go through the list <code>@$lref</code> of range translations pointed at by the array reference, each of which is a hash reference.  If our seed value is within the range (between <code>$$href{S}</code> and <code>$$href{S} + $$href{L} - 1</code>), then map it by applying the offset between the translation's destination and source.</p>
<p>One could do a <code>last</code> here to avoid testing further translations; but there aren't many per map and each seed will only match one translation per map; so I didn't bother and let it cycle through testing the remaining ranges.</p>
<p><code>    return $mapped || $src;</code></p>
<p>If we applied a translation, return that, otherwise return the seed value.</p>
<h3>Clumsily Calling It All</h3>
<p><code>my $min;<br />
foreach my $seed (@seed) {<br />
    my $soil = mapval($seed, $map{"seed-to-soil"});<br />
    my $fert = mapval($soil, $map{"soil-to-fertilizer"});<br />
    my $water = mapval($fert, $map{"fertilizer-to-water"});<br />
    my $light = mapval($water, $map{"water-to-light"});<br />
    my $temp = mapval($light, $map{"light-to-temperature"});<br />
    my $humid = mapval($temp, $map{"temperature-to-humidity"});<br />
    my $loc = mapval($humid, $map{"humidity-to-location"});<br />
</code></p>
<p>Since I'm never going to need these maps again, this was an oddly careful way of setting them up and using them.</p>
<p>I could of course have written this as a deeply-nested series of subroutine calls instead of sequentially capturing all of the intermediate values; but I was expecting to have to debug this (didn't) and wanted the intermediate values at hand for inspection.</p>
<h3>Full Program</h3>
<p><code>#!/usr/bin/perl</p>
<p>use warnings;<br />
use strict;</p>
<p>sub getinput;<br />
sub mapval;</p>
<p>my (@seed, %map);<br />
getinput;</p>
<p>my $min;<br />
foreach my $seed (@seed) {<br />
    my $soil = mapval($seed, $map{"seed-to-soil"});<br />
    my $fert = mapval($soil, $map{"soil-to-fertilizer"});<br />
    my $water = mapval($fert, $map{"fertilizer-to-water"});<br />
    my $light = mapval($water, $map{"water-to-light"});<br />
    my $temp = mapval($light, $map{"light-to-temperature"});<br />
    my $humid = mapval($temp, $map{"temperature-to-humidity"});<br />
    my $loc = mapval($humid, $map{"humidity-to-location"});</p>
<p>    $min = $loc if !defined $min || $loc < $min;<br />
}</p>
<p>print "minimum location $min\n";</p>
<p>sub getinput {<br />
    $/ = "\n\n";</p>
<p>    while (<>) {<br />
	chomp;<br />
	@seed = /(\d+)/g, next if /^seeds/;</p>
<p>	s/(\S+)\s+map:\n//; my $which = $1;<br />
	foreach (split /\n/) {<br />
	    my @n = split /\s+/;<br />
	    push @{$map{$which}}, { D => $n[0], S => $n[1], L => $n[2] };<br />
	}<br />
    }<br />
}</p>
<p>sub mapval {<br />
    my ($src, $lref) = @_;</p>
<p>    my $mapped;<br />
    foreach my $href (@$lref) {<br />
	$mapped = $$href{D} - $$href{S} + $src<br />
		if $src >= $$href{S} &#038;&#038; $src <= $$href{S} + $$href{L} - 1;<br />
    }</p>
<p>    #print "$src:\t", $mapped ? $mapped : "unmapped", "\n";<br />
    return $mapped || $src;<br />
}<br />
</code></p>
]]></content:encoded>
			<wfw:commentRss>http://www.neufeld.newton.ks.us/electronics/?feed=rss2&#038;p=2326</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>AoC 2023 D4P2:  Caching Coefficients of Future List Elements</title>
		<link>http://www.neufeld.newton.ks.us/electronics/?p=2321</link>
		<comments>http://www.neufeld.newton.ks.us/electronics/?p=2321#comments</comments>
		<pubDate>Tue, 05 Dec 2023 00:38:30 +0000</pubDate>
		<dc:creator>Keith Neufeld</dc:creator>
				<category><![CDATA[2023]]></category>
		<category><![CDATA[Advent of Code]]></category>

		<guid isPermaLink="false">http://www.neufeld.newton.ks.us/electronics/?p=2321</guid>
		<description><![CDATA[Day 4 part 2 asks us to find the number of winning entries on each line and use that to duplicate the succeeding n lines; and a duplicated line with winning entries multi-duplicates its succeeding lines; all with a promise not to overflow the end of the input list; and then count the total number [...]]]></description>
			<content:encoded><![CDATA[<p>Day 4 part 2 asks us to find the number of winning entries on each line and use that to duplicate the succeeding n lines; and a duplicated line with winning entries multi-duplicates its succeeding lines; all with a promise not to overflow the end of the input list; and then count the total number of instances that occurred.</p>
<p>It would be vaguely entertaining to implement this using a queue of the coefficients of upcoming lines, or using recursion; but I chose simply to build an array of the multipliers that I prepopulate for lines I haven&#8217;t seen yet.</p>
<p><span id="more-2321"></span></p>
<p><code>    my $wins = grep { my $num = $_; grep { $_ == $num } @winning }<br />
	    split(/\s+/, $mine);<br />
</code></p>
<p>The program begins the same as part 1; but instead of advancing the value of the wins by a particular algorithm, simply capture the count of winning numbers.</p>
<p><code>    #   Add the physical instance of this card to however many we've earned.<br />
    my $instances = ++ $cards[$. - 1];<br />
    $total += $instances;<br />
</code></p>
<p>However many duplicates of this row/card have been earned by previous results, increment it by one for this actual row; set that as the number of instances of this row; and add that to the total instances for the puzzle answer.</p>
<p><code>    #   If this was a winner, clone future cards.<br />
    if ($wins) {<br />
	$cards[$_] += $instances foreach ($. .. $. + $wins - 1);<br />
    }<br />
</code></p>
<p>If we had any winners, then add the number of instances of the current card to the instance count of the appropriate next few cards.</p>
<p><code>( num .. num )</code> is a Perl range operator that generates a list of values counting from the lower to the upper.  (Use with caution on potentially-large lists, though this may have been optimized.)  <code>$.</code> is the current line number of the input <em>but it&#8217;s 1-based and Perl&#8217;s array is 0-based</em>.  So the current line of input <code>$.</code> is in array element <code>$. - 1</code>; therefore array element <code>$.</code> holds the number of (extra) instances of the <em>next</em> line of input and element <code>$. + $wins - 1</code> holds the number of (extra) instances of <code>$wins</code> rows after the current.</p>
<h3>Full Program</h3>
<p><code>#!/usr/bin/perl</p>
<p>use warnings;<br />
use strict;</p>
<p>my ($total, @cards);</p>
<p>my $numlistre = qr/(?:\d+\s+)*\d+/;</p>
<p>while (<>) {<br />
    my ($winning, $mine) = /($numlistre)\s+\|\s+($numlistre)/<br />
	    or die "didn't parse line $.:\n$_";<br />
    my (@winning) = split(/\s+/, $winning);</p>
<p>    my $wins = grep { my $num = $_; grep { $_ == $num } @winning }<br />
	    split(/\s+/, $mine);</p>
<p>    #   Add the physical instance of this card to however many we've earned.<br />
    my $instances = ++ $cards[$. - 1];<br />
    $total += $instances;</p>
<p>    #   If this was a winner, clone future cards.<br />
    if ($wins) {<br />
	$cards[$_] += $instances foreach ($. .. $. + $wins - 1);<br />
    }<br />
}</p>
<p>print "total: $total\n";<br />
</code></p>
]]></content:encoded>
			<wfw:commentRss>http://www.neufeld.newton.ks.us/electronics/?feed=rss2&#038;p=2321</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>AoC 2023 D4P1:  List Searching</title>
		<link>http://www.neufeld.newton.ks.us/electronics/?p=2314</link>
		<comments>http://www.neufeld.newton.ks.us/electronics/?p=2314#comments</comments>
		<pubDate>Mon, 04 Dec 2023 23:49:57 +0000</pubDate>
		<dc:creator>Keith Neufeld</dc:creator>
				<category><![CDATA[2023]]></category>
		<category><![CDATA[Advent of Code]]></category>

		<guid isPermaLink="false">http://www.neufeld.newton.ks.us/electronics/?p=2314</guid>
		<description><![CDATA[Day 4&#8242;s problem asks us to find, on each line of input, how many members of the second list are members of the first list: Card 1: 41 48 83 86 17 &#124; 83 86 6 31 17 9 48 53 Card 2: 13 32 20 16 61 &#124; 61 30 68 82 17 32 [...]]]></description>
			<content:encoded><![CDATA[<p><a href="https://adventofcode.com/2023/day/4">Day 4&#8242;s problem</a> asks us to find, on each line of input, how many members of the second list are members of the first list:</p>
<p><code>Card 1: 41 48 83 86 17 | 83 86  6 31 17  9 48 53<br />
Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19<br />
Card 3:  1 21 53 59 44 | 69 82 63 72 16 21 14  1<br />
Card 4: 41 92 73 84 69 | 59 84 76 51 58  5 54 83<br />
Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36<br />
Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11<br />
</code></p>
<p><a href="https://www.oreilly.com/library/view/perl-cookbook/1565922433/ch04s09.html">O&#8217;Reilly&#8217;s <em>Perl Cookbook</em></a> has concise code for finding the union and intersection of two lists, but it requires that each list has unduplicated entries.  I <em>suspect</em> that&#8217;s going to be the case here but I&#8217;m not sure I should presume, so I&#8217;ll do my own thing.</p>
<p><span id="more-2314"></span></p>
<p><code>my $numlistre = qr/(?:\d+\s+)*\d+/;</code></p>
<p>Each line of input has two lists of numbers on it that I&#8217;ll want to capture when parsing the line; and I could write the regular expression for capturing a list of numbers twice; but instead I&#8217;ll write it once (outside the loop, for efficiency) and put it in a variable.</p>
<p>This is zero or more sets of ( one or more digits followed by one or more spaces ) and then one more set of digits.  To repeat <code>*</code> the digits-and-spaces, they need to be enclosed in <code>( )</code>; but normally that captures and saves the contents and at this point we want to capture the entire list of numbers, not individual numbers in the list.  <code>(?:</code> starts a non-capturing block that just groups the contents and doesn&#8217;t capture.</p>
<p><code>    my ($winning, $mine) = /($numlistre)\s+\|\s+($numlistre)/<br />
	    or die "didn't parse line $.:\n$_";<br />
</code></p>
<p>Don&#8217;t even bother matching the card number; just grab the two lists of numbers (as text).</p>
<p><code>    my (@winning) = split(/\s+/, $winning);</code></p>
<p>Split the string of winning numbers on whitespace and save the result in an array.</p>
<p><code>    my $value = 0;<br />
    $value = $value ? 2 * $value : 1<br />
	    foreach grep { my $num = $_; grep { $_ == $num } @winning }<br />
	    split(/\s+/, $mine);</p>
<p>    $sum += $value;<br />
</code></p>
<p>Working backwards:  Split the string of my numbers into a list, just as the winning numbers were split previously.  Do something (<code>grep</code>) with that list.  For each element of the list returned by <code>grep</code>, advance the value of this line &#8212; double it if it&#8217;s already non-zero, or advance it to 1 if it&#8217;s zero.</p>
<p>Now the <code>grep</code>, working from the outside in.  Perl&#8217;s <code>grep</code> filters a list based on arbitrary criteria, returning the list of matching elements.  So the outer <code>grep</code> loops through all of &#8220;my&#8221; numbers and returns the list of ones matching the condition inside the block.</p>
<p>In the block, <code>grep</code> sets <code>$_</code> to each element being examined.  Since I&#8217;m nesting <code>grep</code>s, I cache the current value of the outer <code>grep</code> in <code>$num</code>.  I then filter the list <code>@winning</code> of winning numbers looking for any that are <code>$num</code> &#8212; in other words, does the number I&#8217;m looking at from my list appear in the list of winning numbers?</p>
<p>If this number from my list appears in the list of winning numbers, then the inner grep will return a list containing just that number, or the scalar 1, as the result of the condition block.  Either evaluates as true, so this number from my list will be included in the result of the outer <code>grep</code>.  The outer <code>grep</code> thus returns the list of my numbers that are winners; and the <code>foreach</code> loop cycles through them advancing the value of the current ticket.</p>
<h3>Full Program</h3>
<p><code>#!/usr/bin/perl</p>
<p>use warnings;<br />
use strict;</p>
<p>my $sum;</p>
<p>my $numlistre = qr/(?:\d+\s+)+\d+/;</p>
<p>while (<>) {<br />
    my ($winning, $mine) = /($numlistre)\s+\|\s+($numlistre)/<br />
	    or die "didn't parse line $.:\n$_";<br />
    my (@winning) = split(/\s+/, $winning);</p>
<p>    my $value = 0;<br />
    $value = $value ? 2 * $value : 1<br />
	    foreach grep { my $num = $_; grep { $_ == $num } @winning }<br />
	    split(/\s+/, $mine);</p>
<p>    $sum += $value;<br />
}</p>
<p>print "sum: $sum\n";<br />
</code></p>
]]></content:encoded>
			<wfw:commentRss>http://www.neufeld.newton.ks.us/electronics/?feed=rss2&#038;p=2314</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>AoC 2023 D3P2: 2D Adjacencies Again</title>
		<link>http://www.neufeld.newton.ks.us/electronics/?p=2309</link>
		<comments>http://www.neufeld.newton.ks.us/electronics/?p=2309#comments</comments>
		<pubDate>Sun, 03 Dec 2023 18:04:28 +0000</pubDate>
		<dc:creator>Keith Neufeld</dc:creator>
				<category><![CDATA[2023]]></category>
		<category><![CDATA[Advent of Code]]></category>

		<guid isPermaLink="false">http://www.neufeld.newton.ks.us/electronics/?p=2309</guid>
		<description><![CDATA[Day 3 part 2 asks us to find only numbers adjacent to * characters, and only when exactly two numbers are adjacent to * characters. I could trivially update my flood fill to prime the stencil only with * characters and then it would find only numbers adjacent to them; but I&#8217;d still have to [...]]]></description>
			<content:encoded><![CDATA[<p>Day 3 part 2 asks us to find only numbers adjacent to * characters, and only when exactly two numbers are adjacent to * characters.</p>
<p>I could trivially update my flood fill to prime the stencil only with * characters and then it would find only numbers adjacent to them; but I&#8217;d still have to write new code to count the quantity of numbers adjacent to * characters; and by the time I&#8217;ve done that, I might as well use it in the main loop also.</p>
<p>Because</p>
<p><code><br />
..123...<br />
...*....<br />
456.....<br />
</code></p>
<p>that star has a whole lot of <em>digits</em> adjacent to it but only two <em>numbers</em>.</p>
<p>New approach:  Look for stars; find adjacent digits; immediately find the full string of digits and cache it; flag every position filled by those digits so it&#8217;s not picked up again in this particular scan for neighbors; count the cache length.</p>
<p><span id="more-2309"></span></p>
<p>I&#8217;m no longer using subroutines <code>prime</code> and <code>iterate</code> nor a separate <code>@dst</code> array.  The code to read the input is unchanged from part 1.</p>
<h3>Debugging Output</h3>
<p><code>my $debug = 0;<br />
...<br />
if ($debug) { print join("", @{$_}), "\n" foreach @input; print "\n"; }<br />
</code></p>
<p>I&#8217;ve added debugging, which perhaps Perl has developed a more elegant or idiomatic way to do since I learned it sometime around 1992.  If I&#8217;ve turned on debugging, then dump the input back out by iterating through each line of the array, joining all of the characters of the line, and printing that, plus an extra blank line.  This is handy during testing because I haven&#8217;t memorized the input and today I&#8217;m working on a single laptop screen.</p>
<h3>Loop Through Cells</h3>
<p><code>my $sum;<br />
foreach my $row (0 .. $h - 1) {<br />
    foreach my $col (0 .. $w - 1) {<br />
	next unless $input[$row][$col] eq "*";<br />
	print "($row, $col)\n" if $debug;<br />
</code></p>
<p>Loop through the rows of the grid and the columns of each row.  If the cell doesn&#8217;t contain a *, move on.  If debugging, print the coordinates of the current cell (after finding that it has a *).</p>
<h3>Loop Through Neighbors</h3>
<p><code>	my (@neighboringnums, %seen);<br />
	foreach my $n (@neighbor) {<br />
	    my $rtst = $row + ${$n}[1];<br />
	    next if $rtst < 0 || $rtst >= $h;<br />
	    my $ctst = $col + ${$n}[0];<br />
	    next if $ctst < 0 || $ctst >= $w;<br />
</code></p>
<p>Declare a list to hold neighboring numbers that we find and a hash to flag cells containing digits that are part of numbers we&#8217;ve already got and shan&#8217;t examine again.</p>
<p>As before, loop through the coordinates of neighbor offsets, ensuring that they don&#8217;t fall outside the grid.</p>
<p><code>	    next if $seen{$rtst}{$ctst};<br />
	    next unless isdigit($input[$rtst][$ctst]);<br />
</code></p>
<p>If we&#8217;ve flagged this neighbor cell as part of a number that we&#8217;ve already seen adjacent to this *, then skip it.</p>
<p>If it&#8217;s not a digit, then skip it.</p>
<h3>Extract the Number</h3>
<p><code>	    #   Found an adjoining digit that we haven't seen yet.<br />
	    #   Extract the whole number.  First, find its left edge.<br />
	    my $nx = $ctst;<br />
	    -- $nx while $nx > 0 &#038;&#038; isdigit($input[$rtst][$nx - 1]);<br />
	    print "\t($nx, $col)\n" if $debug;<br />
</code></p>
<p>(<code>$ctst</code>, <code>$rtst</code>) are the coordinates of a newly-discovered digit adjacent to a *.  Get the whole number that it&#8217;s part of.</p>
<p>Set the number&#8217;s x coordinate <code>$nx</code> to the x coordinate of the digit we just found.  Now decrement that position (move left) as long as we&#8217;re not passing the left edge of the array and as long as the cell to our left contains a digit.</p>
<p>Once we get there, if debugging, print these coordinates of the left edge of this number (string of digits), one tab indented.</p>
<p><code>	    #   Now build the number and mark it seen by this gear.<br />
	    my $num;<br />
	    do {<br />
		$num .= $input[$rtst][$nx];<br />
		++ $seen{$rtst}{$nx};<br />
	    } while $nx < $w - 1 &#038;&#038; isdigit($input[$rtst][++$nx]);<br />
	    print "\t\t[$num]\n" if $debug;</p>
<p>	    push @neighboringnums, $num;<br />
</code></p>
<p>We're at the beginning of a string of one or more digits, at least one of which is adjacent to a *.  Accumulate those digits into the string <code>$num</code> and mark their positions not to be examined again while searching neighbors of this *.</p>
<p>Append the current digit to <code>$num</code>.  Mark this position as already used ... and although I narrate here in (x, y) coordinates, maintain the (row, column) convention used by <code>@input</code>'s indices even in the <code>%seen</code> hash.  Keep appending cells as long as we're not past the right edge of the grid and the next character to the right is another digit.</p>
<p>If debugging, print the resulting number string, two tabs indented.</p>
<p>Push that number on the list of numbers (not just digits) found neighboring this *.</p>
<h3>Two Neighboring Numbers?</h3>
<p><code>	$sum += $neighboringnums[0] * $neighboringnums[1] if<br />
		@neighboringnums == 2;<br />
</code></p>
<p>If we found two neighboring numbers, add their product to our running sum.</p>
<p>Here, the array-to-scalar context of <code>@neighboringnums</code> is caused by the scalar comparison to the scalar 2 and scalar context needn't be specified explicitly.  It's idiomatic in Perl only to specify context explicitly when the array is in an array-or-scalar context (argument to a multi-argument function like <code>print</code>) or when the code is so dense that the meaning would be unclear.</p>
<h3>Coulda Done Part 1 This Way</h3>
<p>Obviously.  Search for neighbors of symbols and extract full numbers immediately.  Move <code>%seen</code> outside the outermost cell loop so that each number only gets picked up once.</p>
<h3>Full Program</h3>
<p><code>#!/usr/bin/perl</p>
<p>use warnings;<br />
use strict;</p>
<p>sub isdigit;				#   missing from POSIX on my system ?!</p>
<p>my @input;</p>
<p>my @neighbor = ( [-1, -1], [0, -1], [1, -1],<br />
	[1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0] );</p>
<p>my $debug = 0;</p>
<p>#   Slurp the entire input into a two-dimensional array.<br />
chomp, push(@input, [ split(//, $_) ]) while <>;<br />
my $h = scalar @input; my $w = scalar @{$input[0]};<br />
print "$w x $h\n\n";</p>
<p>if ($debug) { print join("", @{$_}), "\n" foreach @input; print "\n"; }</p>
<p>my $sum;<br />
foreach my $row (0 .. $h - 1) {<br />
    foreach my $col (0 .. $w - 1) {<br />
	next unless $input[$row][$col] eq "*";<br />
	print "($row, $col)\n" if $debug;</p>
<p>	my (@neighboringnums, %seen);<br />
	foreach my $n (@neighbor) {<br />
	    my $rtst = $row + ${$n}[1];<br />
	    next if $rtst < 0 || $rtst >= $h;<br />
	    my $ctst = $col + ${$n}[0];<br />
	    next if $ctst < 0 || $ctst >= $w;</p>
<p>	    next if $seen{$rtst}{$ctst};<br />
	    next unless isdigit($input[$rtst][$ctst]);</p>
<p>	    #   Found an adjoining digit that we haven't seen yet.<br />
	    #   Extract the whole number.  First, find its left edge.<br />
	    my $nx = $ctst;<br />
	    -- $nx while $nx > 0 &#038;&#038; isdigit($input[$rtst][$nx - 1]);<br />
	    print "\t($nx, $col)\n" if $debug;</p>
<p>	    #   Now build the number and mark it seen by this gear.<br />
	    my $num;<br />
	    do {<br />
		$num .= $input[$rtst][$nx];<br />
		++ $seen{$rtst}{$nx};<br />
	    } while $nx < $w - 1 &#038;&#038; isdigit($input[$rtst][++$nx]);<br />
	    print "\t\t[$num]\n" if $debug;</p>
<p>	    push @neighboringnums, $num;<br />
	} # neighbor</p>
<p>	$sum += $neighboringnums[0] * $neighboringnums[1] if<br />
		@neighboringnums == 2;<br />
    } # col<br />
} # row</p>
<p>print "sum is $sum\n";</p>
<p>sub isdigit {<br />
    return $_[0] =~ /^\d$/;<br />
}<br />
</code></p>
]]></content:encoded>
			<wfw:commentRss>http://www.neufeld.newton.ks.us/electronics/?feed=rss2&#038;p=2309</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>AoC 2023 D3P1: 2D Adjacencies</title>
		<link>http://www.neufeld.newton.ks.us/electronics/?p=2303</link>
		<comments>http://www.neufeld.newton.ks.us/electronics/?p=2303#comments</comments>
		<pubDate>Sun, 03 Dec 2023 17:22:24 +0000</pubDate>
		<dc:creator>Keith Neufeld</dc:creator>
				<category><![CDATA[2023]]></category>
		<category><![CDATA[Advent of Code]]></category>

		<guid isPermaLink="false">http://www.neufeld.newton.ks.us/electronics/?p=2303</guid>
		<description><![CDATA[Day 3&#8242;s first problem asks us to find numbers that are horizontally, vertically, or diagonally adjacent to symbols in input like this (with dots being blank space): 467..114.. ...*...... ..35..633. ......#... 617*...... .....+.58. ..592..... ......755. ...$.*.... .664.598.. As the sample logic states, in the sample input, only 114 and 58 aren&#8217;t adjacent to any symbols. [...]]]></description>
			<content:encoded><![CDATA[<p><a href="https://adventofcode.com/2023/day/3">Day 3&#8242;s first problem</a> asks us to find numbers that are horizontally, vertically, or diagonally adjacent to symbols in input like this (with dots being blank space):</p>
<p><code>467..114..<br />
...*......<br />
..35..633.<br />
......#...<br />
617*......<br />
.....+.58.<br />
..592.....<br />
......755.<br />
...$.*....<br />
.664.598..<br />
</code></p>
<p>As the sample logic states, in the sample input, only 114 and 58 aren&#8217;t adjacent to any symbols.</p>
<p>I thought for a while about a couple of ways to do this and settled on a flood fill.  This was an incorrect choice that happened to work on my particular input but is not a proper solution, so I got unreasonably lucky.</p>
<p><span id="more-2303"></span></p>
<p>Flood filling is an algorithm, commonly used in computer paint programs (or at least it used to be), for filling a region of cells bounded by a closed curve of some marker condition with a new condition &#8212; in other words, filling the inside of a shape you just drew.</p>
<p>I use it here like this:  Imagine covering that input grid with an opaque piece of paper.  Punch holes in it where the symbols are and put it back on top of the grid; all you can see are symbols.  Wiggle your stencil around and get a peek at what surrounds the symbols &#8212; anywhere you find a digit adjacent to a hole, punch out the stencil above that digit also and put it back over the grid.  Then wiggle some more looking for digits adjacent to already-uncovered digits and punch holes over those.  Keep repeating until you don&#8217;t see any more adjacent digits and don&#8217;t punch any more holes; then you&#8217;re done.</p>
<p>This will find digits that are adjacent to symbols and then &#8220;uncover&#8221; the rest of the numbers of which those digits are members.</p>
<p>It will also <em>incorrectly</em> find digits vertically and diagonally adjacent to other digits, even when the newly-found digits aren&#8217;t adjacent to a symbol:</p>
<p><code>.....1..<br />
...234..<br />
567...*.<br />
........<br />
</code></p>
<p>Here, <code>1</code> and <code>567</code> shouldn&#8217;t be selected, but my flood fill will include them.  That&#8217;s wrong; and that&#8217;s how my program works; and I find myself very surprised that the actual input didn&#8217;t contain any number placements that would trigger this error.</p>
<p>So &#8230; let&#8217;s talk about how I wrote this; and then in part 2 you can see that (for other reasons) I had to change my approach to something that would have been more appropriate here.</p>
<h3>Subroutines</h3>
<p><code>sub prime;<br />
sub iterate;<br />
sub isdigit;				#   missing from POSIX on my system ?!<br />
</code></p>
<p>I&#8217;m going to use some subroutines in this program.  In Perl, you can call a subroutine without having defined it first by using the syntax <code>&#038;iterate</code>; or you can define it first and use the syntax <code>iterate</code>.  Today I felt like having a reminder of my list of subroutines and using the latter syntax, so I define them at the top.</p>
<p>The third subroutine definition bears some explanation.  I&#8217;m going to want a quick way to check whether a character is a digit, and normally one would write:</p>
<p><code>use POSIX qw(isdigit);</p>
<p>if (isdigit($var)) { ... }<br />
</code></p>
<p>but the MacBook on which I wrote today&#8217;s programs says</p>
<p><code>"isdigit" is not exported by the POSIX module</code></p>
<p>???!  So I made my own:</p>
<p><code>sub isdigit {<br />
    return $_[0] =~ /^\d$/;<br />
}<br />
</code></p>
<h3>Global Arrays</h3>
<p><code>my (@input, @dst);</code></p>
<p>I&#8217;m going to store the input in a two-dimensional array and I need a second two-dimensional array for the flood-filled copy.  I declare them here as global variables.  Global variables aren&#8217;t normally a great programming practice but here I&#8217;m going to share them between multiple subroutines; and because that&#8217;s all these subroutines are doing, there&#8217;s no point in passing the arrays as parameters to the subs.  Think of these as lexically-scoped variables shared among functions in foolib.c that aren&#8217;t defined-extern in foolib.h so they&#8217;re not visible from main.c, only here I haven&#8217;t bothered to make a separate foolib.c .</p>
<p><code>my @neighbor = ( [-1, -1], [0, -1], [1, -1],<br />
	[1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0] );<br />
</code></p>
<p>I make a list of the (xoffset, yoffset) coordinates for the eight neighbors of a cell.  <code>@neighbor</code> is an array of arrays &#8212; technically an array of array references, but in Perl we don&#8217;t talk about that as much as we have to in C(++).  The <code>( )</code> presents a list to initialize <code>@neighbor</code> and each <code>[ ]</code> within it creates a list and returns its reference to become a member of <code>@neighbor</code>.  Later we can access these as <code>$neighbor[3][0]</code> for the x offset and <code>$neighbor[3][1]</code> for the y offset.</p>
<h3>Read the Input</h3>
<p><code>#   Slurp the entire input into a two-dimensional array.<br />
chomp, push(@input, [ split(//) ]) while <>;<br />
my $h = scalar @input; my $w = scalar @{$input[0]};<br />
print "$w x $h\n\n";<br />
</code></p>
<p>Perl offers syntax variants to write very compact code that&#8217;s easily read by the fluent and which contributes to its reputation for unintelligibility among everyone else.</p>
<p><code>... while condition;</code></p>
<p>is equivalent to</p>
<p><code>while (condition) { ... }</code></p>
<p>&#8211; it executes the statement or block as long as the condition is true.  The condition <code>&lt;&gt;</code>, which I use in every AoC program, is a built-in that reads the next line of input, stashes it in the special variable <code>$_</code> that&#8217;s the default for operators that don&#8217;t specify a variable on which to operate, and returns true until eof and then returns false.  So this will loop through the input, stashing each line in <code>$_</code>.</p>
<p>Each line of input has a newline at the end and <code>split</code> will blithely treat that like any other character, building it into my array.  I don&#8217;t want that; so <code>chomp</code> discards any line-separator present in a string and doesn&#8217;t discard anything if no line separator is present.</p>
<p>Then split the line into separate characters (<code>split(//)</code> splits between characters; and without a parameter for which string to split, it defaults to splitting the current line of input, <code>$_</code>); encapsulate that as a list/array and return the array reference; and <code>push</code> that onto the end of the list of rows of input.  The comma operator allows two (or more) operations in sequence and has a very poor order of precedence compared to arithmetic, logical, and list operators but higher than loop commands; so it allows me to <code>chomp</code> and <code>push</code> still using the one-line loop syntax.</p>
<p>Having read the entire input file into an array in one line of code, grab the height (number of rows) and width (number of elements of the first [zeroeth] row), then print those out as a reassurance that the program is doing something sane.  <code>scalar</code> causes an arbitrary data type to be interpreted in scalar context and an array in scalar context returns the number of elements in the array.  And an instance where we do have to worry about array references:  <code>$input[0]</code> is an array ref, not an array; so we must cast it (rather than dereference as in C) to an array <code>@{$arrayref}</code> in order to apply the scalar cast to get its length.</p>
<h3>Subroutine Calls</h3>
<p><code>prime;<br />
iterate;<br />
</code></p>
<p>Call the previously-defined subroutines to <code>prime</code> my flood fill and <code>iterate</code> my flood fill, whatever those may entail.</p>
<h3>Calculate the Result</h3>
<p><code>my $sum;<br />
foreach my $row (@dst) {<br />
    $sum += $_ foreach join("", @{$row}) =~ /(\d+)/g;<br />
}<br />
print "sum is $sum\n";<br />
</code></p>
<p>For every row of the <code>@dst</code> destination array; and within that, for every something or other, add it to <code>$sum</code>, then print that.</p>
<p>From the inside out, <code>$row</code> is an array ref, not an array, so <code>@{$row}</code> cast it as an array.  Join the elements of that array into a string with no field separators added.  To that string, apply a regular expression that <code>(\d+)</code> looks for and saves strings of digits, <code>/g</code> as many of them as there may be in the string (regex modifier mnemonic: look a<strong>g</strong>ain).  Iterate through all of those saved results and for each one of them, add the intrinsic loop variable <code>$_</code> to the sum.</p>
<h3>Prime the Stencil</h3>
<p><code>#   Copy everything except digits from input array to destination array,<br />
#   leaving dots in all blank positions of both arrays.<br />
sub prime() {<br />
    foreach my $row (0 .. $h - 1) {<br />
	foreach my $col (0 .. $w - 1) {<br />
	    my $in = $input[$row][$col];<br />
	    $dst[$row][$col] = isdigit($in) ? "." : $in;<br />
	    $input[$row][$col] = "." unless isdigit($in);<br />
	}<br />
    }<br />
}<br />
</code></p>
<p>I&#8217;m retaining the puzzle author&#8217;s selection of using dot to indicate a blank.  For my flood fill, I&#8217;m going to destructively scan <code>@input</code> and move matching parts of it to <code>@dst</code>; so that starts here by &#8220;creating the initial stencil&#8221; &#8212; priming the initial contents of <code>@dst</code>.</p>
<p>Loop through all the cells of <code>@input</code>  For each cell, if it&#8217;s a digit, don&#8217;t copy/move it to <code>@dst</code>; populate with a dot (blank space) instead.  For any other character, move it over, replacing it in <code>@input</code> with a dot (which might be what was there to begin with).</p>
<p>Now <code>@dst</code> is primed with all of the symbols and nothing else.</p>
<h3>Iterate Flood Filling</h3>
<p><code>#   Move digits from input array to destination array that are adjacent<br />
#   to anything in destination array until nothing more is moving.<br />
sub iterate() {<br />
    my $changing;<br />
    do {<br />
	$changing = 0;<br />
</code></p>
<p>Recall that we&#8217;re going to keep &#8220;wiggling the stencil&#8221; looking for adjacent digits until we don&#8217;t find any more.  I&#8217;m tracking that in the variable <code>$changing</code>, which needs to be defined outside the loop so it can be checked in the loop condition, and cleared at the beginning of each iteration of the loop.  This outer loop is multiple iterations of &#8220;wiggling the stencil.&#8221;</p>
<p><code>	foreach my $row (0 .. $h - 1) {<br />
	    cell: foreach my $col (0 .. $w - 1) {<br />
		my $in = $input[$row][$col];<br />
		next unless isdigit($in);<br />
</code></p>
<p>Each time we wiggle the stencil, we need to look at whether any digits are adjacent to &#8220;holes&#8221; in the stencil.  We can do this two ways:  look at every digit in the grid and see whether it&#8217;s adjacent to a hole or look at every hole in the stencil and see whether it has digits adjacent to it.  I chose the former.</p>
<p>So loop through the grid and skip on to the next cell if we&#8217;re not looking at a digit.</p>
<p><code>		foreach my $n (@neighbor) {<br />
		    my $rtst = $row + ${$n}[1];<br />
		    next if $rtst < 0 || $rtst >= $h;<br />
		    my $ctst = $col + ${$n}[0];<br />
		    next if $ctst < 0 || $ctst >= $w;<br />
</code></p>
<p>Having found a digit in <code>@input</code> that we haven&#8217;t determined to be adjacent to a &#8220;hole in our template&#8221; and moved over to <code>@dst</code> yet, look at all of the cells surrounding our digit.  Loop through the list of (xoffset, yoffset) pairs for all eight neighbor adjacencies; for each, add the offsets to the current coordinates to get the coordinates (<code>$ctst</code>, <code>$rtst</code>) of the <code>@dst</code> stencil cell that we want to test/examine.  If the offsets take us out of bounds of the grid, skip this neighbor position and move on to the next.</p>
<p><code>		    if ($dst[$rtst][$ctst] ne ".") {	#   have neighbor<br />
			$dst[$row][$col] = $in;<br />
			$input[$row][$col] = ".";<br />
			++ $changing;<br />
			next cell;<br />
		    }<br />
</code></p>
<p>For a neighbor position that&#8217;s within the grid, recall that the only things left behind in <code>@input</code> are digits that we don&#8217;t yet know are adjacent to symbols or other digits.  So if a neighbor position is non-empty, then we&#8217;re interested in it and we want to move it from <code>@input</code> to <code>@dst</code>.  Set the cell of <code>@dst</code> to that value; clear that cell of <code>@input</code>; note that we&#8217;ve experienced a change on this iteration of wiggling the stencil; and stop examining other neighbors of this digit because we&#8217;ve already vetted and moved it.</p>
<p><code>		    } # if neighbor<br />
		} # neighbor<br />
	    } # col<br />
	} # row<br />
    } while $changing;<br />
</code></p>
<p>Keep looping through neighbors of the current maybe-digit cell, and columns of the row, and rows of <code>@input</code>, and iterations of wiggling the stencil, as long as we&#8217;re still finding new digits to move over.</p>
<h3>Reminder That This Is Wrong; How to Fix</h3>
<p>Again, this code picks up digits that it shouldn&#8217;t, due to inappropriate use of flood filling, specifically digits vertically and diagonally adjacent to other digits rather than symbols.</p>
<p>This could be remedied within a flood fill algorithm by renaming <code>@neighbor</code> to <code>@symbolneighbor</code> and then making a shorter <code>@digitneighbor</code> that only contains left and right offsets <code>( [-1, 0], [1, 0])</code>.  Then either:</p>
<ul>
<li>Retain the approach of scanning cells of <code>@input</code> for remaining digits; scan their <code>@symbolneighbor</code> neighbors for symbols; and scan their <code>@digitneighbor</code> neighbors for digits.</li>
<li>Change to scanning <code>@dst</code> for populated cells; scan the <code>@symbolneighbor</code> neighbors of symbols and the <code>@digitneighbor</code> neighbors of digits in <code>@dst</code> for digits in <code>@input</code>.</li>
</ul>
<p>The latter strikes me as cleaner and more intelligible.</p>
<h3>Full Program</h3>
<p><code>#!/usr/bin/perl</p>
<p>use warnings;<br />
use strict;</p>
<p>sub prime;<br />
sub iterate;<br />
sub isdigit;				#   missing from POSIX on my system ?!</p>
<p>my (@input, @dst);</p>
<p>my @neighbor = ( [-1, -1], [0, -1], [1, -1],<br />
	[1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0] );</p>
<p>#   Slurp the entire input into a two-dimensional array.<br />
chomp, push(@input, [ split(//, $_) ]) while <>;<br />
my $h = scalar @input; my $w = scalar @{$input[0]};<br />
print "$w x $h\n\n";</p>
<p>prime;<br />
iterate;</p>
<p>my $sum;<br />
foreach my $row (@dst) {<br />
    $sum += $_ foreach join("", @{$row}) =~ /(\d+)/g;<br />
}<br />
print "sum is $sum\n";</p>
<p>#   Copy everything except digits from input array to destination array,<br />
#   leaving dots in all blank positions of both arrays.<br />
sub prime() {<br />
    foreach my $row (0 .. $h - 1) {<br />
	foreach my $col (0 .. $w - 1) {<br />
	    my $in = $input[$row][$col];<br />
	    $dst[$row][$col] = isdigit($in) ? "." : $in;<br />
	    $input[$row][$col] = "." unless isdigit($in);<br />
	}<br />
    }<br />
}</p>
<p>#   Move digits from input array to destination array that are adjacent<br />
#   to anything in destination array until nothing more is moving.<br />
sub iterate() {<br />
    my $changing;<br />
    do {<br />
	$changing = 0;</p>
<p>	foreach my $row (0 .. $h - 1) {<br />
	    cell: foreach my $col (0 .. $w - 1) {<br />
		my $in = $input[$row][$col];<br />
		next unless isdigit($in);</p>
<p>		foreach my $n (@neighbor) {<br />
		    my $rtst = $row + ${$n}[1];<br />
		    next if $rtst < 0 || $rtst >= $h;<br />
		    my $ctst = $col + ${$n}[0];<br />
		    next if $ctst < 0 || $ctst >= $w;</p>
<p>		    if ($dst[$rtst][$ctst] ne ".") {	#   have neighbor<br />
			$dst[$row][$col] = $in;<br />
			$input[$row][$col] = ".";<br />
			++ $changing;<br />
			next cell;<br />
		    } # if neighbor<br />
		} # neighbor<br />
	    } # col<br />
	} # row<br />
    } while $changing;<br />
}</p>
<p>sub isdigit {<br />
    return $_[0] =~ /^\d$/;<br />
}<br />
</code></p>
]]></content:encoded>
			<wfw:commentRss>http://www.neufeld.newton.ks.us/electronics/?feed=rss2&#038;p=2303</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
