<?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</title>
	<atom:link href="http://www.neufeld.newton.ks.us/electronics/?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>ESP-WROOM-32 with ST7789 LCD (Wiring, Adafruit_GFX and Adafruit_ST77* Libraries, and Arduino Code)</title>
		<link>http://www.neufeld.newton.ks.us/electronics/?p=2400</link>
		<comments>http://www.neufeld.newton.ks.us/electronics/?p=2400#comments</comments>
		<pubDate>Fri, 18 Apr 2025 00:10:55 +0000</pubDate>
		<dc:creator>Keith Neufeld</dc:creator>
				<category><![CDATA[Arduino]]></category>

		<guid isPermaLink="false">http://www.neufeld.newton.ks.us/electronics/?p=2400</guid>
		<description><![CDATA[I&#8217;m wanting to play with a medium-resolution LCD screen attached to something that I can program from the Arduino IDE and I happen to have some &#8220;AITRIP&#8221;-branded ESP-WROOM-32 modules on hand. Connecting an ST7789-driven LCD to one of them involved filling in a number of gaps in the documentation I was able to find, so [...]]]></description>
			<content:encoded><![CDATA[<p>I&#8217;m wanting to play with a medium-resolution LCD screen attached to something that I can program from the Arduino IDE and I happen to have some &#8220;AITRIP&#8221;-branded ESP-WROOM-32 modules on hand.  Connecting an ST7789-driven LCD to one of them involved filling in a number of gaps in the documentation I was able to find, so let me write it all down in one place in case it&#8217;s useful to someone else, including Future Keith.</p>
<p><a href="http://www2.neufeld.newton.ks.us/images/electronics/2025/04/17/IMG_20250417_184837_0648.jpg"><img src="http://www2.neufeld.newton.ks.us/images/electronics/2025/04/17/IMG_20250417_184837_0648_mid.jpg" alt="ESP-WROOM-32 driving Waveshare 2.0-inch LCD" /></a></p>
<p>The LCD I bought is a <a href="https://www.waveshare.com/wiki/2inch_LCD_Module">Waveshare 2.0&#8243; LCD</a> using an ST7789V driver chip, a variant of a popular (<em>the</em> popular?) LCD driver.</p>
<p><span id="more-2400"></span></p>
<h3>Wiring</h3>
<p>The first puzzle was how to wire the LCD to the ESP32.</p>
<p><a href="http://www2.neufeld.newton.ks.us/images/electronics/2025/04/17/AITRIP_ESP-WROOM-32_pinouts.jpg"><img src="http://www2.neufeld.newton.ks.us/images/electronics/2025/04/17/AITRIP_ESP-WROOM-32_pinouts.jpg" alt="AITRIP ESP-WROOM-32 module pinouts" /></a></p>
<p>Different ESP32 module manufacturers provide different module pin counts and I suspect (though haven&#8217;t verified) different pinouts for the same pin counts; so I&#8217;m specifically referencing the pinouts for the module I&#8217;m using.</p>
<p>I found a couple of forum posts &#8212; unfortunately didn&#8217;t save the links &#8212; indicating that the LCD can run on 5V or 3.3V power and that it gets grumpy if the data lines are at a different voltage than its power.  This is easily handled &#8212; power the LCD from the ESP32&#8242;s 3.3V regulator.  So first wire the power connections:</p>
<table border=1 cellspacing=0 cellpadding=5>
<tr>
<th>LCD Pin</th>
<th>ESP32 Pin</th>
<th>Signal</th>
</tr>
<tr>
<td>VCC</td>
<td>VDD 3V3</td>
<td>3.3V regulated power</td>
</tr>
<tr>
<td>GND</td>
<td>GND</td>
<td>ground</td>
</tr>
</table>
<p>Next, the LCD uses SPI, so wire the data and clock lines.  The ESP32 has labeled pins for these so I used them; though I saw a forum post that the ESP32&#8242;s hardware SPI can be matrixed to any digital I/O pin, so it&#8217;s possible that these could be reassigned elsewhere.</p>
<table border=1 cellspacing=0 cellpadding=5>
<tr>
<th>LCD Pin</th>
<th>ESP32 Pin</th>
<th>Signal</th>
</tr>
<tr>
<td>DIN</td>
<td>VSPI MOSI aka GPIO 23</td>
<td>SPI master out / slave in aka <br/> PICO (peripheral in / controller out)</td>
</tr>
<tr>
<td>CLK</td>
<td>VSPI SCK aka GPIO 18</td>
<td>SPI clock</td>
</tr>
</table>
<p>Then three LCD pins can be wired to any available I/O, so I picked pins near the others I was already using:</p>
<table border=1 cellspacing=0 cellpadding=5>
<tr>
<th>LCD Pin</th>
<th>ESP32 Pin</th>
<th>Signal</th>
</tr>
<tr>
<td>CS</td>
<td>GPIO 1</td>
<td>SPI chip select</td>
</tr>
<tr>
<td>DC</td>
<td>GPIO 3</td>
<td>LCD data / command select</td>
</tr>
<tr>
<td>RST</td>
<td>GPIO 21</td>
<td>LCD module reset</td>
</tr>
</table>
<p>And finally the backlight enable pin, which <a href="https://files.waveshare.com/upload/e/ee/2inch_LCD_Module_SchDoc.pdf">the schematic</a> shows is pulled high / active so is superfluous unless you want to blank or PWM the backlight:</p>
<table border=1 cellspacing=0 cellpadding=5>
<tr>
<th>LCD Pin</th>
<th>ESP32 Pin</th>
<th>Signal</th>
</tr>
<tr>
<td>BL</td>
<td>GPIO 17</td>
<td>backlight control (active high)</td>
</tr>
</table>
<h3>Arduino Libraries</h3>
<p>Waveshare provides their own ST7789 and LCD graphics libraries but I overlooked those, so for what seemed like expedience I loaded the <a href="https://github.com/adafruit/Adafruit-ST7735-Library">Adafruit ST77* library</a> and <a href="https://github.com/adafruit/Adafruit-GFX-Library">Adafruit GFX library</a>.</p>
<p>I could not find, in <a href="https://cdn-learn.adafruit.com/downloads/pdf/adafruit-gfx-graphics-library.pdf">the documentation</a> or <a href="https://learn.adafruit.com/adafruit-1-44-color-tft-with-micro-sd-socket/wiring-and-test">tutorials</a>, the syntax for some fundamentals &#8212; to wit, I didn&#8217;t find documentation stating the syntax to instantiate a display object, nor documentation of (the correct) syntax to initialize the display object.  (I ran across what in retrospect incidentally sort of demonstrated these; but I would like documentation to expressly tell me how to use what I&#8217;m trying to use.)</p>
<p>The libraries come with example code and by examining <code>Examples from Custom Libraries</code> / <code>Adafruit ST7735 and ST7789 Library</code> / <code>graphicstest_st7789</code>, and with the help of <a href="https://forum.arduino.cc/t/esp32-wroom-32d-with-ic-st7789-tft/1343907/5">a forum post</a>, I was able first to get the graphics demo to run on my display and second to write my own code.</p>
<p>The library needs to know where things got wired up:</p>
<p><code>#define TFT_CS (1)    //  yellow wire<br />
#define TFT_RST (21)  //  brown wire<br />
#define TFT_DC (3)    //  blue wire<br />
</code></p>
<p>Then to be initialized with the right dimensions for this LCD:</p>
<p> <code>  // OR use this initializer (uncomment) if using a 2.0" 320x240 TFT:<br />
  tft.init(240, 320);           // Init ST7789 320x240<br />
</code></p>
<p>And then the demo works.</p>
<h3>My Demo</h3>
<p>Putting it all together into my own test:</p>
<p><code>#include &lt;Adafruit_GFX.h&gt;<br />
#include &lt;Adafruit_ST7789.h&gt;</p>
<p>#define TFT_CS (1)    //  yellow wire<br />
#define TFT_RST (21)  //  brown wire<br />
#define TFT_DC (3)    //  blue wire</p>
<p>#define PIN_BACKLIGHT (17)  //  grey wire</p>
<p>Adafruit_ST7789 mylcd = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);</p>
<p>void setup() {<br />
  pinMode(PIN_BACKLIGHT, OUTPUT);<br />
  digitalWrite(PIN_BACKLIGHT, HIGH);</p>
<p>  mylcd.init(240, 320);<br />
  mylcd.setRotation(1);<br />
  mylcd.fillScreen(ST77XX_BLACK);</p>
<p>  //mylcd.drawLine(0, 0, 25, 25, 65535);</p>
<p>  mylcd.setCursor(0, 0);<br />
  mylcd.setTextColor(65535);<br />
  mylcd.setTextSize(3);<br />
  mylcd.print("hello, world");<br />
}</p>
<p>void loop() {<br />
}<br />
</code></p>
]]></content:encoded>
			<wfw:commentRss>http://www.neufeld.newton.ks.us/electronics/?feed=rss2&#038;p=2400</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>ESP32 Modules in Breadboards</title>
		<link>http://www.neufeld.newton.ks.us/electronics/?p=2394</link>
		<comments>http://www.neufeld.newton.ks.us/electronics/?p=2394#comments</comments>
		<pubDate>Mon, 14 Apr 2025 22:56:08 +0000</pubDate>
		<dc:creator>Keith Neufeld</dc:creator>
				<category><![CDATA[Arduino]]></category>

		<guid isPermaLink="false">http://www.neufeld.newton.ks.us/electronics/?p=2394</guid>
		<description><![CDATA[A frequently-heard refrain is that ESP32 modules are so inconvenient because with their 1.0&#8243; spacing between header rows, in the standard installation, their footprint covers all but one horizontal row of holes on a breadboard; and kids these days like using fly-wire-style breadboard jumpers rather than trace-style breadboard jumpers (which would work underneath it). This [...]]]></description>
			<content:encoded><![CDATA[<p>A frequently-heard refrain is that ESP32 modules are so inconvenient because with their 1.0&#8243; spacing between header rows, in the standard installation, their footprint covers all but one horizontal row of holes on a breadboard; and kids these days like using fly-wire-style breadboard jumpers rather than trace-style breadboard jumpers (which would work underneath it).</p>
<p>This should be regarded as irritating rather than intractable.  I present here two workarounds and leave the discovery of more as an exercise for the reader.</p>
<p><a href="http://www2.neufeld.newton.ks.us/images/electronics/2025/04/14/IMG_20250414_173039_0627_crop.jpg"><img src="http://www2.neufeld.newton.ks.us/images/electronics/2025/04/14/IMG_20250414_173039_0627_crop_mid.jpg" alt="ESP32 module installed on modular breadboard system" /></a></p>
<p>Option 1, remove one power-strip row from a modular breadboard and dovetail-pin that breadboard onto another.  Install the ESP module over the new &#8220;power gutter&#8221; and revel in the luxury of plenty of rows for connecting jumpers.</p>
<p><a href="http://www2.neufeld.newton.ks.us/images/electronics/2025/04/14/IMG_20250414_173931_0636_crop.jpg"><img src="http://www2.neufeld.newton.ks.us/images/electronics/2025/04/14/IMG_20250414_173931_0636_crop_mid.jpg" alt="ESP32 module installed on modified breadboard system" /></a></p>
<p>Option 2, SAW THROUGH THAT and revel in the luxury of plenty of rows for connecting jumpers.</p>
<p>Footnote:  Use a hacksaw.  Its finer teeth cut without snagging like a wood-cutting saw&#8217;s teeth would do; I don&#8217;t want to contemplate the kind of workholding it would take for me to feel safe using a tablesaw or circular saw on this; and you would be so startled the moment a wood-cutting bandsaw&#8217;s teeth first engage in the edge of the plastic and the blade leaps toward you.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.neufeld.newton.ks.us/electronics/?feed=rss2&#038;p=2394</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>ESP32-WROOM-32 in Arduino IDE</title>
		<link>http://www.neufeld.newton.ks.us/electronics/?p=2380</link>
		<comments>http://www.neufeld.newton.ks.us/electronics/?p=2380#comments</comments>
		<pubDate>Mon, 14 Apr 2025 20:13:11 +0000</pubDate>
		<dc:creator>Keith Neufeld</dc:creator>
				<category><![CDATA[Arduino]]></category>

		<guid isPermaLink="false">http://www.neufeld.newton.ks.us/electronics/?p=2380</guid>
		<description><![CDATA[Back in 2021 I documented the process of installing and selecting support for a particular ESP8266 into the Arduino IDE, in case it would help anyone else and as a reference for the next time I needed to do it myself. Well, here I am back again doing the same to bootstrap myself on the [...]]]></description>
			<content:encoded><![CDATA[<p>Back in 2021 I <a href="/?p=1919">documented the process of installing and selecting support for a particular ESP8266 into the Arduino IDE</a>, in case it would help anyone else and as a reference for the next time I needed to do it myself.  Well, here I am back again doing the same to bootstrap myself on the ESP-WROOM-32 aka ESP32-WROOM-32.</p>
<p><a href="http://www2.neufeld.newton.ks.us/images/electronics/2025/04/14/IMG_20250414_171256_0612_crop.jpg"><img src="http://www2.neufeld.newton.ks.us/images/electronics/2025/04/14/IMG_20250414_171256_0612_crop_mid.jpg" alt="ESP-WROOM-32 board" /></a></p>
<p>My refresher on the basics came from <a href="https://samueladesola.medium.com/how-to-set-up-esp32-wroom-32-b2100060470c">https://samueladesola.medium.com/how-to-set-up-esp32-wroom-32-b2100060470c</a>, although he had to go through some steps I didn&#8217;t and vice-versa.</p>
<p><a href="http://www2.neufeld.newton.ks.us/images/electronics/2025/04/14/Arduino-board-manager-Espressif-URL.png"><img src="http://www2.neufeld.newton.ks.us/images/electronics/2025/04/14/Arduino-board-manager-Espressif-URL.png" alt="Adding Espressif URL to Arduino IDE board manager list" /></a></p>
<p>First, go to <code>File</code> / <code>Preferences</code> / <code>Additional boards manager URLs</code> and add<br />
<code>https://dl.espressif.com/dl/package_esp32_index.json</code> to indicate another repository to search for board definitions.</p>
<p><a href="http://www2.neufeld.newton.ks.us/images/electronics/2025/04/14/Arduino-board-manager-add-boards.png"><img src="http://www2.neufeld.newton.ks.us/images/electronics/2025/04/14/Arduino-board-manager-add-boards.png" alt="Arduino IDE board manager" /></a></p>
<p>Then <code>Tools</code> / <code>Board</code> / <code>Boards Manager...</code> to pick an available board bundle, start typing <code>esp32</code>, and click to install <code>esp32 by Espressif Systems</code> .</p>
<p>I&#8217;m installing this on my Linux workstation so I had to give myself permission to access the USB port, which was:</p>
<p><code>sudo usermod -aG dialout neufeld</code></p>
<p>To make that group change take effect, I was only supposed to need to logout; but doing so added me to the entry in <code>/etc/group</code> but didn&#8217;t activate the group membership when I ran <code>id</code> or tried to access the device file.  I had an OS update to apply anyway so I rebooted and all was well.</p>
<p><a href="http://www2.neufeld.newton.ks.us/images/electronics/2025/04/14/Arduino-ESP32-board-selection.png"><img src="http://www2.neufeld.newton.ks.us/images/electronics/2025/04/14/Arduino-ESP32-board-selection.png" alt="Picking ESP32 Dev Module from Arduino IDE board selection list" /></a></p>
<p>Back in the Arduino IDE, <code>Select Other Board and Port</code>, start typing <code>esp32 dev</code> into the board search, and pick <code>ESP32 Dev Module</code> when it popped up.</p>
<p><a href="http://www2.neufeld.newton.ks.us/images/electronics/2025/04/14/Arduino-board-selection-not-found.png"><img src="http://www2.neufeld.newton.ks.us/images/electronics/2025/04/14/Arduino-board-selection-not-found.png" alt="Arduino IDE board selection list with nothing picked" /></a></p>
<p>Weirdly, even with the USB serial port selected (and accessible), it complains that no boards were found &#8212; but it works.</p>
<p>Finally, <code>File</code> / <code>Examples</code> / <code>01.Basics</code> / <code>Blink</code> to make sure I can compile and upload code to the correct board.</p>
<p><a href="http://www2.neufeld.newton.ks.us/images/electronics/2025/04/14/Arduino-blink-define-LEDBUILTIN.png"><img src="http://www2.neufeld.newton.ks.us/images/electronics/2025/04/14/Arduino-blink-define-LEDBUILTIN.png" alt="Defining LED_BUILTIN for Arduino Blink sketch on board that doesn't define it" /></a></p>
<p>Blink relies on an LED_BUILTIN macro that&#8217;s not defined in this Espressif board spec.  <a href="https://forum.arduino.cc/t/esp-32-wroom-32-pin-allocation/1228704">This forum post</a> suggested pin 2 for the built-in LED on this board and adding</p>
<p><code>#define LED_BUILTIN (2)</code></p>
<p>did the trick.  Compile, upload, and I have a blinking blue LED next to the red power LED.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.neufeld.newton.ks.us/electronics/?feed=rss2&#038;p=2380</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<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>
	</channel>
</rss>
