<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://manoamaro.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://manoamaro.github.io/" rel="alternate" type="text/html" hreflang="en" /><updated>2026-11-03T07:25:39+00:00</updated><id>https://manoamaro.github.io/feed.xml</id><title type="html">Manoel Amaro</title><subtitle>Manoel Amaro is a Software Engineer / Tech Lead at Erste Digital in Vienna, Austria, with an M.Sc. in Computer Science. Backend engineering, projects, and notes.</subtitle><author><name>Manoel de Albuquerque Lira Amaro</name></author><entry><title type="html">Applying the scientific method to software engineering</title><link href="https://manoamaro.github.io/2026/10/18/scientific-method-in-software-engineering.html" rel="alternate" type="text/html" title="Applying the scientific method to software engineering" /><published>2026-10-18T00:00:00+00:00</published><updated>2026-10-18T00:00:00+00:00</updated><id>https://manoamaro.github.io/2026/10/18/scientific-method-in-software-engineering</id><content type="html" xml:base="https://manoamaro.github.io/2026/10/18/scientific-method-in-software-engineering.html"><![CDATA[<p>When we discuss a technical problem at work, it is easy to jump straight to a solution. Someone suggests a library, another person proposes a new service, and soon we are comparing implementations before agreeing on what needs to improve.</p>

<p>I think the scientific method gives us a useful way to organise these decisions. Observe the problem, research what is already known, propose an explanation, and test it. Then use the results to decide what to do next.</p>

<p>Software engineering also involves deadlines, budgets, and product decisions. We won’t resolve every question with an experiment. But we can make our assumptions visible, look for evidence, and explain how that evidence supports our choices.</p>

<h3 id="start-with-an-observation">Start with an observation</h3>

<p>For example, imagine a team investigating a slow endpoint. This is a hypothetical example, but the situation should be familiar.</p>

<p>“The endpoint is slow” is a starting observation. “We need a cache” is already a proposed solution. Between those two statements, there are several things we still need to understand.</p>

<p>Which requests are slow? Under what load? Where does the time go? Is the problem recent? What response time do users actually need?</p>

<p>A useful problem statement might be:</p>

<blockquote>
  <p>During peak traffic, the product endpoint exceeds our agreed response-time target. We need to identify the bottleneck and reduce response time while preserving the required freshness of product information.</p>
</blockquote>

<p>Now we have something to investigate. We also have a constraint: returning an old price quickly might not count as an improvement.</p>

<p>Before changing anything, I would record a baseline. That means measuring the current behaviour under known conditions: request volume, latency distribution, errors, resource use, and relevant traces. An average alone can hide a small group of requests taking much longer, so the measurements should reflect the experience we care about.</p>

<h3 id="research-before-implementing">Research before implementing</h3>

<p>Once the problem is clearer, the next question is what we already know about solving it.</p>

<p>I would start inside the system. Existing code, traces, query plans, previous incidents, and earlier design decisions can explain why the current implementation behaves as it does. A decision that looks strange today may have addressed a constraint that still exists.</p>

<p>Then I would look at external sources: official documentation, relevant standards, research papers, and detailed accounts of comparable implementations. The goal is to understand the available approaches and the conditions under which they work.</p>

<p>For our endpoint, that could mean researching database indexing, query batching, connection pooling, and caching. Discovering that the database already provides a suitable feature could save us from building and maintaining another service.</p>

<p>However, finding a solution in a respected source doesn’t establish that it fits our workload. A benchmark with different data, hardware, or consistency requirements is evidence about that benchmark. We still need to investigate whether its conclusions apply here.</p>

<h3 id="map-the-alternatives-against-the-problem">Map the alternatives against the problem</h3>

<p>Research becomes more useful when we turn it into a comparison. Otherwise, we can collect links for days without getting closer to a decision.</p>

<p>For the endpoint example, an initial map might look like this:</p>

<table>
  <thead>
    <tr>
      <th>Approach</th>
      <th>When it might help</th>
      <th>What I would verify</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Add or change an index</td>
      <td>The query scans too much data</td>
      <td>Query plan, read latency, write cost, and storage</td>
    </tr>
    <tr>
      <td>Batch repeated queries</td>
      <td>One request makes many similar database calls</td>
      <td>Call count, total request time, and result correctness</td>
    </tr>
    <tr>
      <td>Cache responses</td>
      <td>Requests repeat and some staleness is acceptable</td>
      <td>Hit rate, freshness, invalidation, and failure behaviour</td>
    </tr>
    <tr>
      <td>Keep the current design</td>
      <td>The observed behaviour already meets the requirement</td>
      <td>Representative measurements and remaining capacity</td>
    </tr>
  </tbody>
</table>

<p>These are hypotheses to investigate, not conclusions about an actual system.</p>

<p>The best approach depends on our objective and constraints. A small query change might meet the target with less operational work than introducing a distributed cache. Another workload might justify the cache. We should be able to explain the difference using the requirements and evidence available to us.</p>

<p>I also want the comparison to include implementation effort, ongoing maintenance, and how easily we can reverse the change. Performance is only one part of the decision.</p>

<h3 id="make-the-hypothesis-testable">Make the hypothesis testable</h3>

<p>“Caching is faster” is too broad to guide an experiment. A more useful hypothesis is:</p>

<blockquote>
  <p>Repeated reads account for enough of this endpoint’s database time that a response cache can bring latency within our target, while keeping data freshness within the agreed limit.</p>
</blockquote>

<p>This statement connects an expected cause to an observable result. It also gives us ways to find out that we are wrong. Perhaps requests rarely repeat. Perhaps the expensive part is an external call. Perhaps acceptable freshness leaves too little time for useful cache reuse.</p>

<p>Before running the test, we should agree on what success means. In this example, that includes a latency target, an acceptable error rate, a freshness limit, and resource costs. Choosing those criteria after seeing the results makes it too easy to favour the outcome we wanted.</p>

<p>The <a href="https://itl.nist.gov/div898/handbook/pri/section1/pri11.htm">NIST explanation of experimental design</a> describes planning experiments around objectives and selected factors so that the results support valid conclusions. For an engineering investigation, that means deciding what question a test should answer before collecting numbers.</p>

<h3 id="apply-the-smallest-useful-experiment">Apply the smallest useful experiment</h3>

<p>I would first build enough to test the hypothesis. A query-plan comparison or a focused prototype might answer the question before a full implementation is necessary.</p>

<p>The comparison needs conditions we can describe and reproduce. We should record the code version, configuration, dataset, workload, and measurement procedure. For a cache, both warm and cold behaviour matter. A test where every request hits the cache cannot tell us what happens after a restart or during a change in traffic.</p>

<p>We also need to account for other changes. Comparing yesterday’s peak traffic with today’s quiet period doesn’t isolate the effect of our implementation. A controlled load test can help, although its conclusions are limited by how well the workload represents production. Repeated runs help reveal variation; they don’t repair an unrepresentative test.</p>

<p>A small improvement within normal measurement variation is weak evidence. A promising result should include enough observations to assess that variation, rather than just the fastest run.</p>

<p>If the experiment doesn’t support the hypothesis, that is useful information. We can reject the approach, revise the explanation, or improve the test. Continuing because we have already spent time on the prototype would make the decision harder to defend.</p>

<h3 id="verify-the-complete-behaviour">Verify the complete behaviour</h3>

<p>Once an approach looks suitable, implementation still needs to preserve the system’s requirements.</p>

<p>For caching, that means testing invalidation, stale data, failures, concurrency, and recovery, as well as the fast path. A latency improvement doesn’t compensate for returning incorrect results.</p>

<p>Production also introduces conditions that our test environment may miss. Where the system supports it, a limited rollout lets us evaluate a change before exposing everyone to it. Google’s <a href="https://sre.google/workbook/canarying-releases/">chapter on canarying releases</a> describes comparing a partial deployment with a control and choosing representative traffic, duration, and metrics.</p>

<p>A successful canary increases confidence within the conditions it observed. We should still monitor the wider rollout and have clear rollback criteria.</p>

<h3 id="build-an-argument-that-others-can-inspect">Build an argument that others can inspect</h3>

<p>At some point, we need to explain the decision to colleagues. I find a technical argument more useful when it connects four things:</p>

<ol>
  <li><strong>The claim:</strong> what we recommend changing.</li>
  <li><strong>The evidence:</strong> the observations, experiments, and sources supporting it.</li>
  <li><strong>The reasoning:</strong> why that evidence supports this choice for our system.</li>
  <li><strong>The limits:</strong> what remains uncertain and what would make us reconsider.</li>
</ol>

<p>For example, “the documentation says this feature reduces database calls” supports an explanation of how the feature works. Measurements from our workload are needed to support a claim about how much it improves our endpoint. Neither statement alone tells us whether the trade-offs are acceptable.</p>

<p>Facts, interpretations, and preferences should be distinguishable. A trace showing repeated queries is an observation. Saying those queries are the main bottleneck is an interpretation to check. Accepting some staleness in exchange for lower latency is a product decision.</p>

<p>Sources should be specific enough for someone else to inspect: the documentation version, benchmark setup, query plan, or experiment report. A link attached to a claim is useful only if it actually supports that claim. We should also include results that weaken our recommendation, rather than selecting only the favourable ones.</p>

<p>Disagreement becomes more productive when we can identify whether we disagree about the evidence, the explanation, or the trade-offs. A colleague may accept our measurements and still reasonably prefer a simpler implementation.</p>

<h3 id="keep-the-reasoning-after-the-decision">Keep the reasoning after the decision</h3>

<p>A short decision record can preserve the problem, alternatives considered, evidence, chosen approach, and remaining risks. It should also say what would trigger a review: a different workload, a new requirement, or an assumption that turns out to be false.</p>

<p>The amount of investigation should match the consequences. A small, reversible change may need a focused check and a short explanation. A costly migration or a change to critical data deserves more evidence. We also need a stopping point: enough confidence to make the decision, with an appropriate way to detect and respond if it is wrong.</p>

<p>For me, this is the practical value of scientific thinking at work. It gives us a way to move from an initial idea to a decision that colleagues can examine, test, and improve. We can still use experience and intuition to suggest solutions, while letting evidence determine how much confidence those solutions deserve.</p>]]></content><author><name>Manoel de Albuquerque Lira Amaro</name></author><category term="software engineering" /><category term="scientific method" /><category term="research" /><category term="decision making" /><summary type="html"><![CDATA[How researching existing solutions, testing assumptions, and arguing from evidence can improve the way we implement solutions at work.]]></summary></entry><entry><title type="html">Dynamic Programming - Solving the same problem only once</title><link href="https://manoamaro.github.io/2026/03/15/dynamic-programming-in-go.html" rel="alternate" type="text/html" title="Dynamic Programming - Solving the same problem only once" /><published>2026-03-15T00:00:00+00:00</published><updated>2026-03-15T00:00:00+00:00</updated><id>https://manoamaro.github.io/2026/03/15/dynamic-programming-in-go</id><content type="html" xml:base="https://manoamaro.github.io/2026/03/15/dynamic-programming-in-go.html"><![CDATA[<p>Some coding challenges look manageable until we start exploring all the possible answers. For each choice there are more choices, and soon the program is doing far more work than we expected. Sometimes, a large part of that work is solving the same smaller problem again and again.</p>

<p><strong>Dynamic programming</strong> helps us avoid that repetition. In this post I’m going to explain what it is, when it helps, and how it appears in a few of my Advent of Code solutions in Go.</p>

<h3 id="what-is-dynamic-programming">What is dynamic programming?</h3>

<p>The idea is to describe a problem using smaller problems, save their answers, and reuse those answers when we need them again. Instead of exploring the same branches repeatedly, we calculate each distinct subproblem once.</p>

<p>There are two useful ways to do this:</p>

<ul>
  <li><strong>Top-down, with memoization:</strong> start with the original problem, solve smaller problems recursively, and cache the results.</li>
  <li><strong>Bottom-up, with tabulation:</strong> start with the simplest answers and fill a table in an order where each answer’s dependencies are already available.</li>
</ul>

<p>These are two ways to evaluate the same relationship between problems. MIT’s <a href="https://ocw.mit.edu/courses/6-s095-programming-for-the-puzzled-january-iap-2018/8706beaa535711f0af45ce2e86f3c9c2_MIT6_S095IAP18_Puzzle_11.pdf">introduction to dynamic programming</a> gives another explanation of overlapping subproblems and memoization.</p>

<p>Recursion by itself isn’t dynamic programming. If every recursive call solves a different problem, adding a cache might only use more memory. The important part is finding repeated work with reusable answers.</p>

<h3 id="an-example-building-a-design-from-patterns">An example: building a design from patterns</h3>

<p>In <a href="https://adventofcode.com/2024/day/19">Advent of Code 2024, Day 19</a>, we have a collection of towel patterns and want to know whether we can join them to form a design. Each pattern can be used repeatedly. My <a href="https://github.com/manoamaro/advent-of-code/blob/main/cmd/2024/19/main.go">solution</a> also counts how many arrangements produce each design.</p>

<p>Let’s use a smaller example with patterns <code class="language-plaintext highlighter-rouge">a</code> and <code class="language-plaintext highlighter-rouge">aa</code>. How many ways can we build <code class="language-plaintext highlighter-rouge">aaaa</code>?</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>a + a + a + a
a + a + aa
a + aa + a
aa + a + a
aa + aa
</code></pre></div></div>

<p>There are five ways. We could find them by trying every possible first pattern, then doing the same for what remains.</p>

<p>If we start with <code class="language-plaintext highlighter-rouge">a</code>, the remaining design is <code class="language-plaintext highlighter-rouge">aaa</code>. If we start with <code class="language-plaintext highlighter-rouge">aa</code>, it is <code class="language-plaintext highlighter-rouge">aa</code>. But solving <code class="language-plaintext highlighter-rouge">aaa</code> also leads to <code class="language-plaintext highlighter-rouge">aa</code>, so we have reached the same smaller problem through two different choices.</p>

<p>That remaining design is our <strong>state</strong>: the information needed to describe a subproblem. With the available patterns fixed, the number of ways to build <code class="language-plaintext highlighter-rouge">aa</code> doesn’t depend on how we arrived there.</p>

<p>Now we can define the rule, or <strong>recurrence</strong>:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ways(remaining) = sum of ways(remaining after removing a matching prefix)
ways("") = 1
</code></pre></div></div>

<p>Why does the empty design have one way? Because consuming the entire design means we completed one valid arrangement. Returning zero there would erase every successful branch when we add the counts together.</p>

<h3 id="a-top-down-solution-in-go">A top-down solution in Go</h3>

<p>Here is a standalone adaptation of the approach in my Day 19 solution. I’ve replaced the repository’s set and map helpers with a slice and a standard Go map to keep the example self-contained. It needs <code class="language-plaintext highlighter-rouge">import "strings"</code>.</p>

<figure class="highlight"><pre><code class="language-go" data-lang="go"><span class="k">func</span> <span class="n">countDesign</span><span class="p">(</span><span class="n">design</span> <span class="kt">string</span><span class="p">,</span> <span class="n">patterns</span> <span class="p">[]</span><span class="kt">string</span><span class="p">)</span> <span class="kt">uint64</span> <span class="p">{</span>
	<span class="n">memo</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">(</span><span class="k">map</span><span class="p">[</span><span class="kt">int</span><span class="p">]</span><span class="kt">uint64</span><span class="p">)</span>

	<span class="k">var</span> <span class="n">ways</span> <span class="k">func</span><span class="p">(</span><span class="kt">int</span><span class="p">)</span> <span class="kt">uint64</span>
	<span class="n">ways</span> <span class="o">=</span> <span class="k">func</span><span class="p">(</span><span class="n">start</span> <span class="kt">int</span><span class="p">)</span> <span class="kt">uint64</span> <span class="p">{</span>
		<span class="k">if</span> <span class="n">start</span> <span class="o">==</span> <span class="nb">len</span><span class="p">(</span><span class="n">design</span><span class="p">)</span> <span class="p">{</span>
			<span class="k">return</span> <span class="m">1</span>
		<span class="p">}</span>
		<span class="k">if</span> <span class="n">result</span><span class="p">,</span> <span class="n">ok</span> <span class="o">:=</span> <span class="n">memo</span><span class="p">[</span><span class="n">start</span><span class="p">];</span> <span class="n">ok</span> <span class="p">{</span>
			<span class="k">return</span> <span class="n">result</span>
		<span class="p">}</span>

		<span class="k">var</span> <span class="n">total</span> <span class="kt">uint64</span>
		<span class="k">for</span> <span class="n">_</span><span class="p">,</span> <span class="n">pattern</span> <span class="o">:=</span> <span class="k">range</span> <span class="n">patterns</span> <span class="p">{</span>
			<span class="k">if</span> <span class="n">pattern</span> <span class="o">!=</span> <span class="s">""</span> <span class="o">&amp;&amp;</span> <span class="n">strings</span><span class="o">.</span><span class="n">HasPrefix</span><span class="p">(</span><span class="n">design</span><span class="p">[</span><span class="n">start</span><span class="o">:</span><span class="p">],</span> <span class="n">pattern</span><span class="p">)</span> <span class="p">{</span>
				<span class="n">total</span> <span class="o">+=</span> <span class="n">ways</span><span class="p">(</span><span class="n">start</span> <span class="o">+</span> <span class="nb">len</span><span class="p">(</span><span class="n">pattern</span><span class="p">))</span>
			<span class="p">}</span>
		<span class="p">}</span>
		<span class="n">memo</span><span class="p">[</span><span class="n">start</span><span class="p">]</span> <span class="o">=</span> <span class="n">total</span>
		<span class="k">return</span> <span class="n">total</span>
	<span class="p">}</span>

	<span class="k">return</span> <span class="n">ways</span><span class="p">(</span><span class="m">0</span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>

<p>For example, <code class="language-plaintext highlighter-rouge">countDesign("aaaa", []string{"a", "aa"})</code> returns <code class="language-plaintext highlighter-rouge">5</code>.</p>

<p>Here <code class="language-plaintext highlighter-rouge">start</code> is the byte position of the remaining suffix. Instead of storing that suffix as a string key, we store its position in the fixed design. The puzzle uses ASCII characters, so byte positions are sufficient.</p>

<p>Every accepted pattern moves <code class="language-plaintext highlighter-rouge">start</code> forward. Empty patterns are ignored because they wouldn’t consume anything and would make the recursion call itself with the same state. The example assumes the patterns are unique; duplicate entries would count the same choice twice. My original parser puts them in a set.</p>

<p>The map lookup uses <code class="language-plaintext highlighter-rouge">result, ok</code> because zero is a valid cached answer. A suffix that cannot be built is worth remembering too.</p>

<p>For the yes-or-no question, we can use a similar function returning <code class="language-plaintext highlighter-rouge">bool</code>, combine branches with OR, and stop at the first success. That is what the separate <code class="language-plaintext highlighter-rouge">isPossible</code> function in my repository does. Counting needs to add every successful branch.</p>

<h3 id="how-much-work-does-it-save">How much work does it save?</h3>

<p>With patterns <code class="language-plaintext highlighter-rouge">a</code> and <code class="language-plaintext highlighter-rouge">aa</code>, the uncached recursion follows a Fibonacci-like branching pattern. The number of calls grows exponentially with the design length, even though many calls ask about the same suffix.</p>

<p>Let <strong>N</strong> be the design’s length in bytes, <strong>P</strong> the number of patterns, and <strong>L</strong> the longest pattern length. The memoized example has at most N + 1 states. Each state checks P patterns, and a prefix comparison can inspect up to L bytes.</p>

<p>That gives an upper bound of <strong>O(N × P × L)</strong> time, assuming expected constant-time map operations and fixed-width arithmetic. The memo and recursion stack use <strong>O(N)</strong> auxiliary space. We are counting arrangements without constructing all of them.</p>

<p>This is a useful way to analyse a dynamic programming solution: count the states, then estimate the work per state. A cache doesn’t automatically make an algorithm linear.</p>

<p>The example uses <code class="language-plaintext highlighter-rouge">uint64</code>, which can still overflow if the number of arrangements is too large. For unrestricted counts we would need overflow checks or arbitrary-precision integers, and the arithmetic cost would also change.</p>

<h3 id="the-same-problem-bottom-up">The same problem, bottom-up</h3>

<p>We can also fill a table without recursion. Let <code class="language-plaintext highlighter-rouge">dp[start]</code> mean the number of ways to build the suffix beginning at <code class="language-plaintext highlighter-rouge">start</code>.</p>

<figure class="highlight"><pre><code class="language-go" data-lang="go"><span class="k">func</span> <span class="n">countDesignBottomUp</span><span class="p">(</span><span class="n">design</span> <span class="kt">string</span><span class="p">,</span> <span class="n">patterns</span> <span class="p">[]</span><span class="kt">string</span><span class="p">)</span> <span class="kt">uint64</span> <span class="p">{</span>
	<span class="n">dp</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">([]</span><span class="kt">uint64</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">design</span><span class="p">)</span><span class="o">+</span><span class="m">1</span><span class="p">)</span>
	<span class="n">dp</span><span class="p">[</span><span class="nb">len</span><span class="p">(</span><span class="n">design</span><span class="p">)]</span> <span class="o">=</span> <span class="m">1</span>

	<span class="k">for</span> <span class="n">start</span> <span class="o">:=</span> <span class="nb">len</span><span class="p">(</span><span class="n">design</span><span class="p">)</span> <span class="o">-</span> <span class="m">1</span><span class="p">;</span> <span class="n">start</span> <span class="o">&gt;=</span> <span class="m">0</span><span class="p">;</span> <span class="n">start</span><span class="o">--</span> <span class="p">{</span>
		<span class="k">for</span> <span class="n">_</span><span class="p">,</span> <span class="n">pattern</span> <span class="o">:=</span> <span class="k">range</span> <span class="n">patterns</span> <span class="p">{</span>
			<span class="k">if</span> <span class="n">pattern</span> <span class="o">!=</span> <span class="s">""</span> <span class="o">&amp;&amp;</span> <span class="n">strings</span><span class="o">.</span><span class="n">HasPrefix</span><span class="p">(</span><span class="n">design</span><span class="p">[</span><span class="n">start</span><span class="o">:</span><span class="p">],</span> <span class="n">pattern</span><span class="p">)</span> <span class="p">{</span>
				<span class="n">dp</span><span class="p">[</span><span class="n">start</span><span class="p">]</span> <span class="o">+=</span> <span class="n">dp</span><span class="p">[</span><span class="n">start</span><span class="o">+</span><span class="nb">len</span><span class="p">(</span><span class="n">pattern</span><span class="p">)]</span>
			<span class="p">}</span>
		<span class="p">}</span>
	<span class="p">}</span>

	<span class="k">return</span> <span class="n">dp</span><span class="p">[</span><span class="m">0</span><span class="p">]</span>
<span class="p">}</span></code></pre></figure>

<p>The assumptions about unique patterns and integer size are the same as before.</p>

<p>We work backwards because every matching nonempty pattern leads to a larger position. Its answer must already be in the table before we use it.</p>

<p>For our example, the table ends up like this:</p>

<table>
  <thead>
    <tr>
      <th>Position</th>
      <th>Remaining design</th>
      <th>Ways</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>4</td>
      <td>empty</td>
      <td>1</td>
    </tr>
    <tr>
      <td>3</td>
      <td><code class="language-plaintext highlighter-rouge">a</code></td>
      <td>1</td>
    </tr>
    <tr>
      <td>2</td>
      <td><code class="language-plaintext highlighter-rouge">aa</code></td>
      <td>2</td>
    </tr>
    <tr>
      <td>1</td>
      <td><code class="language-plaintext highlighter-rouge">aaa</code></td>
      <td>3</td>
    </tr>
    <tr>
      <td>0</td>
      <td><code class="language-plaintext highlighter-rouge">aaaa</code></td>
      <td>5</td>
    </tr>
  </tbody>
</table>

<p>Both versions use the same recurrence and have the same time bound. The bottom-up version avoids the growing call stack and uses a slice for direct indexing. The top-down version can skip states which are never reached from the starting problem.</p>

<h3 id="choosing-the-right-state-hot-springs">Choosing the right state: Hot Springs</h3>

<p>A cache is only correct if its key contains everything that can change the answer.</p>

<p>My <a href="https://github.com/manoamaro/advent-of-code/blob/main/cmd/2023/12/main.go">2023 Day 12 solution</a> is a useful example. In <a href="https://adventofcode.com/2023/day/12">Hot Springs</a>, a row contains working springs (<code class="language-plaintext highlighter-rouge">.</code>), damaged springs (<code class="language-plaintext highlighter-rouge">#</code>), and unknown positions (<code class="language-plaintext highlighter-rouge">?</code>). A list of group sizes describes the required consecutive runs of damaged springs. We need to count the arrangements which satisfy both pieces of information.</p>

<p>In that solution, the recursive function receives the remaining row and the remaining groups. This is its actual cache lookup:</p>

<figure class="highlight"><pre><code class="language-go" data-lang="go"><span class="n">key</span> <span class="o">:=</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Sprintf</span><span class="p">(</span><span class="s">"%s-%v"</span><span class="p">,</span> <span class="n">input</span><span class="p">,</span> <span class="n">groups</span><span class="p">)</span>
<span class="k">if</span> <span class="n">val</span><span class="p">,</span> <span class="n">ok</span> <span class="o">:=</span> <span class="n">memo</span><span class="p">[</span><span class="n">key</span><span class="p">];</span> <span class="n">ok</span> <span class="p">{</span>
	<span class="k">return</span> <span class="n">val</span>
<span class="p">}</span></code></pre></figure>

<p>Caching only the remaining row would be wrong. For example, a row containing one <code class="language-plaintext highlighter-rouge">?</code> has one valid arrangement when the remaining group is <code class="language-plaintext highlighter-rouge">[1]</code>, but none when it is <code class="language-plaintext highlighter-rouge">[2]</code>.</p>

<p>The function either treats the next position as working, when allowed, or places the next complete damaged group. Placing a group also checks its length and separator before advancing past it. Because each call starts outside a partially placed group, the remaining row and groups are sufficient to describe the state.</p>

<p>That last detail matters. A version which consumes one spring at a time might also need the length of the currently open damaged run in its cache key. The right state depends on how we define the subproblem.</p>

<p>My implementation formats strings for the key. Another representation would use a struct containing the row position and group index, with a fresh cache for each fixed row and group list. That would avoid formatting the remaining values on each lookup.</p>

<h3 id="when-should-i-use-it">When should I use it?</h3>

<p>When a problem involves counting possibilities, finding a minimum cost, or deciding whether some arrangement is possible, I look for a few things:</p>

<ol>
  <li>Can I describe a smaller version of the problem with a compact state?</li>
  <li>Can I calculate its answer from smaller states, with clear base cases?</li>
  <li>Do different choices reach the same states?</li>
  <li>Are there few enough distinct states to store and process?</li>
</ol>

<p>For optimisation problems, we also need to know that the best answer can be built from the appropriate best subproblem answers. This is usually called <strong>optimal substructure</strong>. A locally attractive choice isn’t enough to justify discarding the other choices.</p>

<p>Dynamic programming isn’t always the right tool. Merge sort splits into separate subproblems without the same repeated-state benefit. Breadth-first search is a direct fit for shortest paths in an unweighted graph. And if every state includes an arbitrary subset of N items, the cache may still contain exponentially many states.</p>

<p>There is one more practical detail in my Day 19 solution: the cache is cleared at the start of each part and shared across designs using the same pattern set. A cached suffix answer can be reused there because the patterns stay fixed. If the patterns changed, the suffix alone would no longer be a sufficient key. The standalone functions above avoid that issue by creating a fresh table for every call.</p>

<p>What helps me recognise dynamic programming is asking what a recursive call actually needs to know. In Day 19, that is the remaining design. In Day 12, it is the remaining row and groups. Once that state is clear, I can write the recurrence, check the base cases, and decide how to save the answers.</p>]]></content><author><name>Manoel de Albuquerque Lira Amaro</name></author><category term="algorithms" /><category term="dynamic programming" /><category term="go" /><category term="advent of code" /><summary type="html"><![CDATA[A practical introduction to dynamic programming, memoization, and bottom-up tables, with Go examples from my Advent of Code solutions.]]></summary></entry><entry><title type="html">Turning Advent of Code solutions into a small Go toolkit</title><link href="https://manoamaro.github.io/2025/12/01/go-advent-of-code-toolkit.html" rel="alternate" type="text/html" title="Turning Advent of Code solutions into a small Go toolkit" /><published>2025-12-01T00:00:00+00:00</published><updated>2025-12-01T00:00:00+00:00</updated><id>https://manoamaro.github.io/2025/12/01/go-advent-of-code-toolkit</id><content type="html" xml:base="https://manoamaro.github.io/2025/12/01/go-advent-of-code-toolkit.html"><![CDATA[<p>Solving an Advent of Code puzzle usually starts with reading some input, converting it into something useful, and calling the solution. After doing this for several days, I ended up with a collection of helpers around my Go solutions. The interesting part is deciding which pieces belong in that shared code and which ones should stay with the puzzle.</p>

<p>In this post I’m going to explain how my <a href="https://github.com/manoamaro/advent-of-code">Advent of Code repository</a> is organised, and a few things I would still improve. It’s a small toolkit for my own workflow, so some choices favour convenience over a more general design.</p>

<h3 id="one-command-for-each-day">One command for each day</h3>

<p>Each solution is a separate Go command. The folders follow the year and day, with zero-padded day numbers:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cmd/
  aoc/             # Runs a selected year and day
  generator/       # Creates the starting files for a day
  2024/
    01/
    02/
    05/
internal/
  aoc/             # Challenge runner and input handling
pkg/
  grid/
  queue/
  set/
  sliceutil/
</code></pre></div></div>

<p>For example, from the repository root, I can run a solution directly:</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">go run ./cmd/2024/05</code></pre></figure>

<p>This keeps each puzzle independent. Its parsing, types, and solution functions can all live in the same folder, without registering every day in one large application.</p>

<p>There is also a small command that selects the folder for me:</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">go run ./cmd/aoc <span class="nt">-y</span> 2024 <span class="nt">-d</span> 05 <span class="nt">-p</span> 1 <span class="nt">--debug</span></code></pre></figure>

<p>The <a href="https://github.com/manoamaro/advent-of-code/blob/main/cmd/aoc/main.go">command</a> formats the day with <code class="language-plaintext highlighter-rouge">fmt.Sprintf("%02d", day)</code>, builds a path such as <code class="language-plaintext highlighter-rouge">./cmd/2024/05</code>, and runs it with <code class="language-plaintext highlighter-rouge">os/exec</code>. It passes the supported flags to the day command and connects its standard input, output, and error streams to the terminal.</p>

<p>It isn’t loading the solution dynamically into the same process. It’s starting another <code class="language-plaintext highlighter-rouge">go run</code> command. For this workflow, that is enough, and I can still run any day directly when I want to debug it.</p>

<h3 id="separating-input-from-the-solution">Separating input from the solution</h3>

<p>The shared runner connects three functions: an input processor and a solver for each part. These are the types used by the runner:</p>

<figure class="highlight"><pre><code class="language-go" data-lang="go"><span class="k">type</span> <span class="n">InputProcessor</span><span class="p">[</span><span class="n">T</span> <span class="n">any</span><span class="p">]</span> <span class="k">func</span><span class="p">(</span><span class="kt">string</span><span class="p">)</span> <span class="n">T</span>

<span class="k">type</span> <span class="n">Solver</span><span class="p">[</span><span class="n">T</span> <span class="n">any</span><span class="p">,</span> <span class="n">R</span> <span class="n">comparable</span><span class="p">]</span> <span class="k">func</span><span class="p">(</span><span class="n">T</span><span class="p">)</span> <span class="n">R</span></code></pre></figure>

<p>Here, <code class="language-plaintext highlighter-rouge">T</code> is the parsed input type, and <code class="language-plaintext highlighter-rouge">R</code> is the result type. Both parts use the same types. The <code class="language-plaintext highlighter-rouge">comparable</code> constraint allows the runner’s sample checks to compare an answer with the expected value using <code class="language-plaintext highlighter-rouge">==</code>.</p>

<p>For a simple puzzle, the input can remain a string. The <a href="https://github.com/manoamaro/advent-of-code/blob/main/cmd/generator/day.tmpl">generator’s template</a> starts with this structure, with the actual year and day filled in:</p>

<figure class="highlight"><pre><code class="language-go" data-lang="go"><span class="k">var</span> <span class="n">challenge</span> <span class="o">=</span> <span class="n">aoc</span><span class="o">.</span><span class="n">New</span><span class="p">(</span><span class="m">2024</span><span class="p">,</span> <span class="m">5</span><span class="p">,</span> <span class="n">aoc</span><span class="o">.</span><span class="n">StringProcessor</span><span class="p">,</span> <span class="n">part1</span><span class="p">,</span> <span class="n">part2</span><span class="p">)</span>

<span class="k">func</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">challenge</span><span class="o">.</span><span class="n">Run</span><span class="p">()</span>
<span class="p">}</span>

<span class="k">func</span> <span class="n">part1</span><span class="p">(</span><span class="n">input</span> <span class="kt">string</span><span class="p">)</span> <span class="kt">int</span> <span class="p">{</span>
    <span class="k">return</span> <span class="m">0</span>
<span class="p">}</span>

<span class="k">func</span> <span class="n">part2</span><span class="p">(</span><span class="n">input</span> <span class="kt">string</span><span class="p">)</span> <span class="kt">int</span> <span class="p">{</span>
    <span class="k">return</span> <span class="m">0</span>
<span class="p">}</span></code></pre></figure>

<p>Those zeroes are just the starting point for the solution. For a more complex puzzle, I can replace <code class="language-plaintext highlighter-rouge">StringProcessor</code> with a parser that returns a custom struct, and change the solver arguments to that type. The runner also has processors for lines, integers, and grids, so I don’t need to repeat those conversions every time.</p>

<p>One detail is that the runner processes the raw input separately for each part. So, if part one sorts a slice or changes a grid, part two gets a newly parsed value rather than that modified value. This costs another parsing pass, but makes the two parts easier to reason about. It assumes the parser itself creates fresh data instead of returning shared mutable state.</p>

<h3 id="getting-input-without-downloading-it-every-time">Getting input without downloading it every time</h3>

<p>The <a href="https://github.com/manoamaro/advent-of-code/blob/main/internal/aoc/utils.go">input helper</a> first checks a local file such as <code class="language-plaintext highlighter-rouge">.cache/input_2024_5.txt</code>. If it finds non-empty cached content, it returns that content. Otherwise, it loads local configuration, reads <code class="language-plaintext highlighter-rouge">AOC_SESSION</code>, requests the input from Advent of Code, and saves the response in the cache.</p>

<p>The session value stays in local configuration. Neither <code class="language-plaintext highlighter-rouge">.env</code> nor <code class="language-plaintext highlighter-rouge">.cache/</code> belongs in the repository.</p>

<p>I can also provide a file explicitly:</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">go run ./cmd/aoc <span class="nt">-y</span> 2024 <span class="nt">-d</span> 05 <span class="nt">--input</span> path/to/input.txt</code></pre></figure>

<p>The outer command turns <code class="language-plaintext highlighter-rouge">--input</code> into the positional file argument understood by the day runner. This bypasses the input download and is useful when trying a small example or reproducing a parsing problem.</p>

<p>There are still rough edges in this helper. It should check the HTTP response status before saving the body, and handle cache-write errors explicitly. Otherwise, an error response could end up being treated as puzzle input. A cache is useful, but it also needs to be clear which responses are safe to keep.</p>

<h3 id="generating-the-repetitive-part">Generating the repetitive part</h3>

<p>Starting another day also involves creating a folder and connecting the same functions. For that, I use:</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">go run ./cmd/generator <span class="nt">-y</span> 2024 <span class="nt">-d</span> 5</code></pre></figure>

<p>The <a href="https://github.com/manoamaro/advent-of-code/blob/main/cmd/generator/main.go">generator</a> uses an embedded Go text template to create <code class="language-plaintext highlighter-rouge">cmd/2024/05/main.go</code>. If the folder already exists, it skips it instead of overwriting the solution.</p>

<p>This only prepares the structure. It doesn’t generate a solution or know how the input should be parsed. I still decide those things after reading the puzzle.</p>

<h3 id="keeping-the-helpers-small">Keeping the helpers small</h3>

<p>The reusable packages cover things like sets, queues, grids, slice operations, and mathematical helpers. The challenge runner lives under <code class="language-plaintext highlighter-rouge">internal/aoc</code>, while older solutions can still use a compatibility wrapper at <code class="language-plaintext highlighter-rouge">pkg/aoc</code>.</p>

<p>That wrapper helps with moving the code without changing every old solution at once. It doesn’t turn the internal runner into a standalone library for other projects; the wrapper still depends on this repository’s internal package.</p>

<p>I find it useful to keep the boundary practical. A queue or a set can be shared across many puzzles. A struct describing one day’s ordering rules probably belongs next to that day’s solution. Having a shared package isn’t a reason to move every small function into it.</p>

<h3 id="samples-and-automated-tests">Samples and automated tests</h3>

<p>The runner provides <code class="language-plaintext highlighter-rouge">TestPart1</code> and <code class="language-plaintext highlighter-rouge">TestPart2</code> methods for checking sample files. The <code class="language-plaintext highlighter-rouge">--test</code> flag enables those checks when the day command calls them. This is convenient while working through a puzzle, but it isn’t the same as running Go’s test suite.</p>

<p>In the current implementation, a failed sample check writes an error to the log. It doesn’t fail a <code class="language-plaintext highlighter-rouge">testing.T</code> or guarantee a non-zero process exit. Also, <code class="language-plaintext highlighter-rouge">--test</code> doesn’t replace the normal input run; the day program still calls <code class="language-plaintext highlighter-rouge">Run()</code>. To work without a network request, I need a local input file or an existing cache as well.</p>

<p>For repeatable checks, the helper packages have ordinary <code class="language-plaintext highlighter-rouge">_test.go</code> files. The repository’s <a href="https://github.com/manoamaro/advent-of-code/blob/main/.github/workflows/ci.yml">CI workflow</a> runs vet, tests, and a build of the outer runner. Locally, the Makefile provides the same shortcuts:</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">make vet
make <span class="nb">test
</span>make run <span class="nv">y</span><span class="o">=</span>2024 <span class="nv">d</span><span class="o">=</span>05 <span class="nv">p</span><span class="o">=</span>1 <span class="nv">debug</span><span class="o">=</span>1 <span class="nv">input</span><span class="o">=</span>path/to/input.txt</code></pre></figure>

<p>Before running these commands, I check the Go version required by <code class="language-plaintext highlighter-rouge">go.mod</code> and install the dependencies with <code class="language-plaintext highlighter-rouge">go mod download</code>. The source file is the reference for the required toolchain when documentation and configuration differ.</p>

<h3 id="what-i-would-improve-next">What I would improve next</h3>

<p>The next improvements I would make are stricter argument validation, HTTP and cache error handling, and sample checks that can actually fail an automated run. The runner also recovers panics by logging them, so its exit status needs attention before I rely on it as a reliable signal of success.</p>

<p>Even with those limitations, the structure keeps the repetitive work in one place: choosing a day, loading input, parsing it, and running each part. The puzzle-specific code stays in its own folder. That’s the balance I want from this toolkit: enough shared code to make the next solution easier to start, while leaving room to solve each problem differently.</p>]]></content><author><name>Manoel de Albuquerque Lira Amaro</name></author><category term="go" /><category term="advent of code" /><category term="tooling" /><category term="testing" /><summary type="html"><![CDATA[How I organised my Advent of Code solutions in Go, with a shared runner, input processors, cached inputs, and reusable helpers.]]></summary></entry><entry><title type="html">From YAML automation to an ESPHome component</title><link href="https://manoamaro.github.io/2025/05/25/from-yaml-automation-to-an-esphome-component.html" rel="alternate" type="text/html" title="From YAML automation to an ESPHome component" /><published>2025-05-25T00:00:00+00:00</published><updated>2025-05-25T00:00:00+00:00</updated><id>https://manoamaro.github.io/2025/05/25/from-yaml-automation-to-an-esphome-component</id><content type="html" xml:base="https://manoamaro.github.io/2025/05/25/from-yaml-automation-to-an-esphome-component.html"><![CDATA[<p>A watering system sounds like a small automation. Read the soil moisture, turn on a pump, wait a few seconds, and turn it off again. Then come the other conditions: how often should it run? Is it too cold? Is there enough light? What happens if I disable it while the pump is running?</p>

<p>In my <a href="https://github.com/manoamaro/esp32_watering_system">ESP32 watering system</a>, these decisions started in ESPHome YAML. As the configuration grew, I started moving the pump behaviour into a component.</p>

<p>This is still an experiment. The component has the outline of the controller, but it doesn’t yet replace everything in the YAML version. I think that makes it a useful example: moving code is the easy part; deciding what the component should own takes more thought.</p>

<h3 id="starting-with-a-yaml-package">Starting with a YAML package</h3>

<p>The original configuration uses a package for each pump. Substitutions such as <code class="language-plaintext highlighter-rouge">${id}</code> and <code class="language-plaintext highlighter-rouge">${pin}</code> let me include the same package with different identifiers and hardware pins.</p>

<p>So reuse was already possible. I didn’t need a component just to add another pump.</p>

<p>The package brings together the output, a valve, settings, sensor checks, scripts, and LVGL display controls. For example, this is the expression deciding whether automatic watering should be active:</p>

<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">return</span> <span class="n">id</span><span class="p">(</span><span class="n">pump_</span><span class="err">$</span><span class="p">{</span><span class="n">id</span><span class="p">}</span><span class="n">_enabled_switch</span><span class="p">).</span><span class="n">state</span> <span class="o">&amp;&amp;</span>
       <span class="o">!</span><span class="n">id</span><span class="p">(</span><span class="n">pump_</span><span class="err">$</span><span class="p">{</span><span class="n">id</span><span class="p">}</span><span class="n">_is_soil_wet</span><span class="p">).</span><span class="n">state</span> <span class="o">&amp;&amp;</span>
       <span class="n">id</span><span class="p">(</span><span class="n">pump_</span><span class="err">$</span><span class="p">{</span><span class="n">id</span><span class="p">}</span><span class="n">_has_light</span><span class="p">).</span><span class="n">state</span> <span class="o">&amp;&amp;</span>
       <span class="o">!</span><span class="n">id</span><span class="p">(</span><span class="n">pump_</span><span class="err">$</span><span class="p">{</span><span class="n">id</span><span class="p">}</span><span class="n">_is_cold</span><span class="p">).</span><span class="n">state</span> <span class="o">&amp;&amp;</span>
       <span class="n">id</span><span class="p">(</span><span class="n">pump_</span><span class="err">$</span><span class="p">{</span><span class="n">id</span><span class="p">}</span><span class="n">_is_time</span><span class="p">).</span><span class="n">state</span><span class="p">;</span></code></pre></figure>

<p>Each condition has a fairly simple meaning. The pump must be enabled, the soil must need water, the light and temperature must be suitable, and the watering window must be open.</p>

<p>The harder part is following what happens around that expression. A setting changes a display widget. A binary sensor opens a valve. A script handles a manual run. Another expression calculates the countdown until the next run.</p>

<p>YAML is still describing the system, but understanding the pump now means following several entities and callbacks through a large file.</p>

<h3 id="choosing-the-component-boundary">Choosing the component boundary</h3>

<p>What I want is a pump controller that knows when to start and stop. The display should show its state and change its settings, without also being responsible for the watering rules.</p>

<p>There are three different concerns here:</p>

<ul>
  <li>Hardware configuration: which output drives the pump and where sensor readings come from.</li>
  <li>Pump behaviour: whether watering is allowed, when it starts, and when it ends.</li>
  <li>Presentation: switches, labels, countdowns, and controls on the display.</li>
</ul>

<p>The component is where I want the second part to live. YAML still connects the pieces, and ESPHome entities still expose the controls.</p>

<h3 id="loading-the-component-locally">Loading the component locally</h3>

<p>ESPHome supports <a href="https://esphome.io/components/external_components/">external components</a> from local directories as well as Git repositories. For this experiment, the component lives next to the node configuration:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>esphome/
  watering_system_v2.yaml
  components/
    pump/
      __init__.py
      pump.h
      pump.cpp
</code></pre></div></div>

<p>The configuration loads that directory with:</p>

<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><span class="na">external_components</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">source</span><span class="pi">:</span>
      <span class="na">type</span><span class="pi">:</span> <span class="s">local</span>
      <span class="na">path</span><span class="pi">:</span> <span class="s">components</span></code></pre></figure>

<p>This lets me work on the YAML and component together. It doesn’t mean that the component is part of ESPHome itself, or that another device will have it available without the same source files.</p>

<h3 id="why-there-is-python-in-an-esp32-component">Why there is Python in an ESP32 component</h3>

<p>The Python file doesn’t run on the ESP32. It describes the configuration and generates the C++ wiring during the build. The C++ class contains the runtime behaviour. ESPHome’s <a href="https://developers.esphome.io/architecture/components/">component architecture documentation</a> explains this split through <code class="language-plaintext highlighter-rouge">CONFIG_SCHEMA</code> and <code class="language-plaintext highlighter-rouge">to_code</code>.</p>

<p>For example, the pump needs an existing floating-point output. Its schema declares that relationship:</p>

<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="n">cv</span><span class="p">.</span><span class="nc">Required</span><span class="p">(</span><span class="n">CONF_PUMP_OUTPUT</span><span class="p">):</span> <span class="n">cv</span><span class="p">.</span><span class="nf">use_id</span><span class="p">(</span><span class="n">output</span><span class="p">.</span><span class="n">FloatOutput</span><span class="p">),</span></code></pre></figure>

<p>Then <code class="language-plaintext highlighter-rouge">to_code</code> resolves the configured ID and passes the output to the pump:</p>

<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="n">pump_output</span> <span class="o">=</span> <span class="k">await</span> <span class="n">cg</span><span class="p">.</span><span class="nf">get_variable</span><span class="p">(</span><span class="n">pump_config</span><span class="p">[</span><span class="n">CONF_PUMP_OUTPUT</span><span class="p">])</span>
<span class="n">cg</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="n">var</span><span class="p">.</span><span class="nf">set_pump_output</span><span class="p">(</span><span class="n">pump_output</span><span class="p">))</span></code></pre></figure>

<p>The component doesn’t need to know the GPIO pin or how that output was configured. It receives an output that it can control.</p>

<p>The same file creates number entities for settings such as duration, frequency, and maximum soil moisture. For example, the duration is exposed in seconds:</p>

<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="n">duration_number_var</span> <span class="o">=</span> <span class="k">await</span> <span class="n">number</span><span class="p">.</span><span class="nf">new_number</span><span class="p">(</span>
    <span class="n">pump_config</span><span class="p">[</span><span class="n">CONF_RUN_DURATION</span><span class="p">],</span>
    <span class="n">min_value</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
    <span class="n">max_value</span><span class="o">=</span><span class="mi">60</span> <span class="o">*</span> <span class="mi">10</span><span class="p">,</span>
    <span class="n">step</span><span class="o">=</span><span class="mi">5</span><span class="p">,</span>
<span class="p">)</span>
<span class="n">cg</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="n">var</span><span class="p">.</span><span class="nf">set_duration_number</span><span class="p">(</span><span class="n">duration_number_var</span><span class="p">))</span></code></pre></figure>

<p>These are excerpts from the prototype, rather than a complete component definition. There is still a mismatch to fix: several settings are optional in the schema but are accessed unconditionally in <code class="language-plaintext highlighter-rouge">to_code</code>. Either they need to be required, or the implementation needs to handle their absence.</p>

<h3 id="moving-the-decisions-into-c">Moving the decisions into C++</h3>

<p>The current <code class="language-plaintext highlighter-rouge">Pump::loop()</code> checks whether the pump is enabled and automatic mode is active. It then checks the schedule and reads the soil, light, and temperature values.</p>

<p>The central condition is recognisable from the YAML version:</p>

<figure class="highlight"><pre><code class="language-cpp" data-lang="cpp"><span class="k">if</span> <span class="p">(</span><span class="n">curr_soil</span> <span class="o">&lt;</span> <span class="n">max_moisture</span> <span class="o">&amp;&amp;</span> <span class="n">curr_light</span> <span class="o">&gt;</span> <span class="n">min_light</span> <span class="o">&amp;&amp;</span>
    <span class="n">curr_temperature</span> <span class="o">&gt;</span> <span class="n">min_temperature</span> <span class="o">&amp;&amp;</span> <span class="n">now</span> <span class="o">&lt;=</span> <span class="n">end_run</span><span class="p">)</span> <span class="p">{</span>
  <span class="c1">// Start the output if the pump isn't already running.</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
  <span class="c1">// Stop the output and update the running state.</span>
<span class="p">}</span></code></pre></figure>

<p>This is an abbreviated excerpt. The useful change is that the watering decision and its resulting state now have a home in one class.</p>

<p>The controller uses <code class="language-plaintext highlighter-rouge">millis()</code> to check time as the loop runs, instead of waiting inside the function for an entire watering cycle. But that introduces a scheduling decision too. The YAML version uses wall-clock timestamps; the prototype uses device uptime. A restart changes the meaning of the schedule, so I still need to decide what should happen after boot.</p>

<h3 id="the-migration-isnt-finished">The migration isn’t finished</h3>

<p>Putting the logic in C++ doesn’t automatically make it correct. Looking at the prototype makes a few problems quite visible.</p>

<p>For example, the duration and frequency are converted to milliseconds and stored in <code class="language-plaintext highlighter-rouge">int16_t</code>. A signed 16-bit integer only holds values up to 32,767. One minute is already 60,000 milliseconds, so it cannot represent even that interval correctly. The allowed settings go much further than one minute.</p>

<p>The timing needs an appropriate unsigned type, explicit initial state, and elapsed-time comparisons that account for the counter wrapping. Changing the type alone wouldn’t settle the scheduling behaviour.</p>

<p>Stopping also needs to be an explicit operation. At the moment, disabling the pump makes the loop return early. That doesn’t turn off an output which was already running. Turning automatic mode off has a similar gap.</p>

<p>I want those transitions to go through the same stop operation, so the hardware output and the reported running state stay together.</p>

<p>There are also differences from the original package. The YAML checks an upper light limit, controls pump power, and provides a manual-run script. The new loop doesn’t reproduce all of that yet. The example node also supplies fixed sensor values, which are useful scaffolding but don’t demonstrate watering from live readings.</p>

<p>Before treating this as a replacement, I would check the behaviour with controlled inputs: dry and wet soil, unsuitable temperature, a completed watering interval, disabling during a run, invalid sensor readings, and restarting the device. Then the output and timing need to be verified on hardware.</p>

<h3 id="what-i-want-to-keep-in-yaml">What I want to keep in YAML</h3>

<p>I still want YAML to describe the device: its pins, sensors, display, and the connections between them. Packages remain useful for sharing that configuration.</p>

<p>The reason for introducing a component is more specific. When I change a watering rule, I want to find it in the pump controller. When I change a label or rearrange the screen, I want that to stay a display change.</p>

<p>The prototype hasn’t reached that point completely, but it gives the remaining work a clearer shape. I can review the pump’s behaviour as a controller, then connect it to the rest of the device.</p>]]></content><author><name>Manoel de Albuquerque Lira Amaro</name></author><category term="esphome" /><category term="esp32" /><category term="cpp" /><category term="home automation" /><summary type="html"><![CDATA[Moving the behaviour of an ESP32 watering system from YAML packages into an ESPHome component, and what still needs work.]]></summary></entry><entry><title type="html">Understand Kotlin Flow Coroutines by Implementing Server-Sent Events (SSE)</title><link href="https://manoamaro.github.io/2020/04/12/understand-kotlin-flow-coroutines-sse.html" rel="alternate" type="text/html" title="Understand Kotlin Flow Coroutines by Implementing Server-Sent Events (SSE)" /><published>2020-04-12T00:00:00+00:00</published><updated>2020-04-12T00:00:00+00:00</updated><id>https://manoamaro.github.io/2020/04/12/understand-kotlin-flow-coroutines-sse</id><content type="html" xml:base="https://manoamaro.github.io/2020/04/12/understand-kotlin-flow-coroutines-sse.html"><![CDATA[<p>I originally published this article on Medium on April 12, 2020. It explains how I used Kotlin Flow and coroutines to listen to the Hacker News updates stream and process story IDs.</p>

<p><a href="https://medium.com/@manoel.al.amaro/understand-kotlin-flow-coroutines-by-implementing-server-side-sent-sse-9e190ff5f24f">Read the article on Medium</a>.</p>]]></content><author><name>Manoel de Albuquerque Lira Amaro</name></author><category term="kotlin" /><category term="coroutines" /><category term="flow" /><category term="server-sent events" /><category term="android" /><summary type="html"><![CDATA[My article on using Kotlin Flow and coroutines to listen to the Hacker News Server-Sent Events stream.]]></summary></entry><entry><title type="html">Big O - A brief explanation</title><link href="https://manoamaro.github.io/2016/02/21/big-o-a-brief-explanation.html" rel="alternate" type="text/html" title="Big O - A brief explanation" /><published>2016-02-21T00:00:00+00:00</published><updated>2016-02-21T00:00:00+00:00</updated><id>https://manoamaro.github.io/2016/02/21/big-o-a-brief-explanation</id><content type="html" xml:base="https://manoamaro.github.io/2016/02/21/big-o-a-brief-explanation.html"><![CDATA[<p>One of the most important concepts that I have seen in coding challenges (and some companies use these challenges for hiring) is <strong>Big-O</strong>. Basically, it helps us describe how the time or space an algorithm needs grows as its input gets bigger. In this post I’m going to explain the concept with a few examples of how to estimate it.</p>

<h3 id="what-is-big-o-time-and-space-complexity">What is Big-O? Time and Space Complexity</h3>

<p>Big-O describes an upper bound on growth as the input gets large. It doesn’t tell us the exact number of seconds or bytes an algorithm will use: those depend on the implementation and the machine. Instead, it helps us compare how the amount of work or memory changes with the input size. We should also say which case we’re considering; the examples below describe the worst case unless mentioned otherwise.</p>

<p>Take these two functions, for example:</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">sum_range</span><span class="p">(</span><span class="n">start</span><span class="p">,</span> <span class="n">finish</span><span class="p">)</span>
  <span class="n">result</span> <span class="o">=</span> <span class="mi">0</span>
  <span class="k">for</span> <span class="n">i</span> <span class="k">in</span> <span class="p">(</span><span class="n">start</span><span class="o">...</span><span class="n">finish</span><span class="p">)</span>
    <span class="n">result</span> <span class="o">+=</span> <span class="n">i</span>
  <span class="k">end</span>
  <span class="k">return</span> <span class="n">result</span>
<span class="k">end</span>

<span class="k">def</span> <span class="nf">sum</span><span class="p">(</span><span class="n">num1</span><span class="p">,</span> <span class="n">num2</span><span class="p">)</span>
  <span class="k">return</span> <span class="n">num1</span> <span class="o">+</span> <span class="n">num2</span>
<span class="k">end</span></code></pre></figure>

<p>The first function sums the integers from <code class="language-plaintext highlighter-rouge">start</code> up to, but not including, <code class="language-plaintext highlighter-rouge">finish</code>. If <code class="language-plaintext highlighter-rouge">finish &gt;= start</code>, let <strong>N</strong> be the size of the range, <code class="language-plaintext highlighter-rouge">finish - start</code>. The loop runs N times, so its time complexity is <strong>O(N)</strong>. The second function performs one addition regardless of the range size, so we describe its time complexity as <strong>O(1)</strong>, or constant time. For these examples, I’m treating arithmetic on the numbers as constant-time operations.</p>

<p><em>Space complexity</em> is a similar concept, but it describes how the memory needed by an algorithm grows. Both functions above use a fixed number of local variables, so their <strong>auxiliary space</strong> is <strong>O(1)</strong>. Auxiliary space is the extra space used by the algorithm, excluding the input and, when stated separately, the output.</p>

<p>Now take this recursive function:</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># Calculate the sum of integers from 0 to max, for max &gt;= 0</span>
<span class="k">def</span> <span class="nf">sum_recursive</span><span class="p">(</span><span class="n">max</span><span class="p">)</span>
  <span class="k">return</span> <span class="mi">0</span> <span class="k">if</span> <span class="n">max</span> <span class="o">==</span> <span class="mi">0</span>
  <span class="k">return</span> <span class="n">max</span> <span class="o">+</span> <span class="n">sum_recursive</span><span class="p">(</span><span class="n">max</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span>
<span class="k">end</span></code></pre></figure>

<p>If N is <code class="language-plaintext highlighter-rouge">max</code>, the function makes N + 1 calls, including the call for zero. Its time complexity is <strong>O(N)</strong>. Each unfinished call also occupies a stack frame, so the auxiliary space is <strong>O(N)</strong>. In practice, a large input can exceed Ruby’s call-stack limit; an iterative version avoids that growing stack.</p>

<h3 id="estimating-big-o">Estimating Big-O</h3>

<p><strong>O(N)</strong> and <strong>O(1)</strong> are not the only possibilities. For example, the algorithm below prints every ordered pair of values from an array:</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">print_pairs</span><span class="p">(</span><span class="n">array</span><span class="p">)</span>
  <span class="k">for</span> <span class="n">i</span> <span class="k">in</span> <span class="p">(</span><span class="mi">0</span><span class="o">...</span><span class="n">array</span><span class="p">.</span><span class="nf">size</span><span class="p">)</span>
    <span class="k">for</span> <span class="n">j</span> <span class="k">in</span> <span class="p">(</span><span class="mi">0</span><span class="o">...</span><span class="n">array</span><span class="p">.</span><span class="nf">size</span><span class="p">)</span>
      <span class="nb">puts</span> <span class="s2">"</span><span class="si">#{</span><span class="n">array</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="si">}</span><span class="s2">, </span><span class="si">#{</span><span class="n">array</span><span class="p">[</span><span class="n">j</span><span class="p">]</span><span class="si">}</span><span class="s2">"</span>
    <span class="k">end</span>
  <span class="k">end</span>
<span class="k">end</span></code></pre></figure>

<p>For every one of the N items, the inner loop runs N times. So, it prints N × N pairs and has <strong>O(N²)</strong> time complexity. This includes pairs with the same element and both orders of different elements. The loop itself uses <strong>O(1)</strong> auxiliary space, assuming each value is printed without storing all pairs. The output has N² pairs; storing them would require <strong>O(N²)</strong> space. Actual printing time can also depend on the length of each value’s text.</p>

<p>Having two loops doesn’t always mean quadratic time. If the loops run one after the other, we add their work instead of multiplying it:</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">print_twice</span><span class="p">(</span><span class="n">array</span><span class="p">)</span>
  <span class="k">for</span> <span class="n">item</span> <span class="k">in</span> <span class="n">array</span>
    <span class="nb">puts</span> <span class="n">item</span>
  <span class="k">end</span>
  <span class="k">for</span> <span class="n">item</span> <span class="k">in</span> <span class="n">array</span>
    <span class="nb">puts</span> <span class="n">item</span>
  <span class="k">end</span>
<span class="k">end</span></code></pre></figure>

<p>Each loop runs N times, giving <strong>O(N + N)</strong>, or <strong>O(N)</strong> after dropping the constant factor. On the other hand, if two independent arrays have sizes N and M, one pass over each is <strong>O(N + M)</strong>. A nested loop that visits every combination is <strong>O(N × M)</strong>; we can’t assume the arrays have the same size.</p>

<p>A loop inside another loop isn’t automatically quadratic either. We need to count how many times the inner work actually runs. For example, if each step cuts the remaining search range in half, there are <strong>O(log N)</strong> steps. Binary search does this on a sorted array: it checks the middle value and continues in at most one half. Iterative binary search uses <strong>O(1)</strong> auxiliary space.</p>

<h3 id="a-prefix-sum-example">A prefix sum example</h3>

<p>Let’s go back to an array and calculate its prefix sums:</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">prefix_sum</span><span class="p">(</span><span class="n">array</span><span class="p">)</span>
  <span class="n">result</span> <span class="o">=</span> <span class="no">Array</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">array</span><span class="p">.</span><span class="nf">size</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
  <span class="k">for</span> <span class="n">i</span> <span class="k">in</span> <span class="p">(</span><span class="mi">1</span><span class="o">..</span><span class="n">array</span><span class="p">.</span><span class="nf">size</span><span class="p">)</span>
    <span class="n">result</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">result</span><span class="p">[</span><span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span> <span class="o">+</span> <span class="n">array</span><span class="p">[</span><span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span>
  <span class="k">end</span>
  <span class="k">return</span> <span class="n">result</span>
<span class="k">end</span></code></pre></figure>

<p>For <code class="language-plaintext highlighter-rouge">[2, 3, 1]</code>, this returns <code class="language-plaintext highlighter-rouge">[0, 2, 5, 6]</code>. Each entry contains the sum of the elements before that index. The function allocates an array of N + 1 values and fills it in one pass, so its time complexity is <strong>O(N)</strong> and its output space is <strong>O(N)</strong>. Apart from the returned array, it uses <strong>O(1)</strong> auxiliary space.</p>

<p>Why is this useful? Once we have the prefix sums, the sum of an inclusive range from <code class="language-plaintext highlighter-rouge">left</code> to <code class="language-plaintext highlighter-rouge">right</code> is <code class="language-plaintext highlighter-rouge">result[right + 1] - result[left]</code>. Each range query then takes <strong>O(1)</strong> time. For M queries, building the array and answering them takes <strong>O(N + M)</strong> time, plus <strong>O(N)</strong> space for the prefix array and <strong>O(M)</strong> if we store the answers. I used the same idea for counting nucleotides in my <a href="/2015/10/27/codility-challenge-genomic-range-query.html">Genomic Range Query post</a>.</p>

<h3 id="a-few-things-to-remember">A few things to remember</h3>

<p>When estimating complexity, first decide what N represents and which case you are analysing. Count how often the work runs, add consecutive parts, and multiply for nested work when the inner part runs in full each time. Then simplify the expression: <strong>O(2N + 3)</strong> becomes <strong>O(N)</strong>, and <strong>O(N² + N)</strong> becomes <strong>O(N²)</strong> as N grows.</p>

<p>Big-O is an upper bound, so saying an algorithm is O(N²) doesn’t by itself prove that it always needs quadratic time. It also doesn’t replace measurement: constants, memory use, input shape, and implementation details can matter for real inputs. I find it most useful as a way to understand how a solution will scale before testing it.</p>

<h4 id="references">References</h4>

<ul>
  <li><a href="https://ocw.mit.edu/courses/6-006-introduction-to-algorithms-fall-2011/resources/recitation-1-asymptotic-complexity-peak-finding/">MIT OpenCourseWare: Asymptotic Complexity</a></li>
  <li><a href="https://www.crackingthecodinginterview.com/">Cracking the Coding Interview, 6th Edition</a></li>
  <li><a href="https://www.cengage.com/c/introduction-to-the-theory-of-computation-3e-sipser/">Introduction to the Theory of Computation</a></li>
</ul>]]></content><author><name>Manoel de Albuquerque Lira Amaro</name></author><category term="computer science" /><category term="big o" /><category term="time" /><category term="space" /><category term="algorithms" /><category term="complexity" /><summary type="html"><![CDATA[A practical introduction to Big-O, time and space complexity, and prefix sums using Ruby examples.]]></summary></entry><entry><title type="html">Codility Challenge: Genomic Range Query</title><link href="https://manoamaro.github.io/2015/10/27/codility-challenge-genomic-range-query.html" rel="alternate" type="text/html" title="Codility Challenge: Genomic Range Query" /><published>2015-10-27T00:00:00+00:00</published><updated>2015-10-27T00:00:00+00:00</updated><id>https://manoamaro.github.io/2015/10/27/codility-challenge-genomic-range-query</id><content type="html" xml:base="https://manoamaro.github.io/2015/10/27/codility-challenge-genomic-range-query.html"><![CDATA[<p>This was one of the toughest challenges for me. My first solution passed all the tests, but after analysing it more closely, I realised it probably wasn’t the intended approach.</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">solution</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="nb">p</span><span class="p">,</span> <span class="n">q</span><span class="p">)</span>
  <span class="n">nucleotides</span> <span class="o">=</span> <span class="p">{</span>
      <span class="s1">'A'</span> <span class="o">=&gt;</span> <span class="mi">1</span><span class="p">,</span>
      <span class="s1">'C'</span> <span class="o">=&gt;</span> <span class="mi">2</span><span class="p">,</span>
      <span class="s1">'G'</span> <span class="o">=&gt;</span> <span class="mi">3</span><span class="p">,</span>
      <span class="s1">'T'</span> <span class="o">=&gt;</span> <span class="mi">4</span>
  <span class="p">}</span>
  <span class="n">result</span> <span class="o">=</span> <span class="p">[]</span>
  <span class="k">for</span> <span class="n">i</span> <span class="k">in</span> <span class="mi">1</span><span class="o">..</span><span class="nb">p</span><span class="p">.</span><span class="nf">size</span> <span class="k">do</span>
    <span class="n">slice</span> <span class="o">=</span> <span class="n">s</span><span class="p">[</span><span class="nb">p</span><span class="p">[</span><span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span><span class="o">..</span><span class="n">q</span><span class="p">[</span><span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]]</span>
    <span class="n">nucleotides</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="o">|</span>
      <span class="k">if</span> <span class="n">slice</span><span class="p">.</span><span class="nf">include?</span> <span class="n">key</span>
        <span class="n">result</span> <span class="o">&lt;&lt;</span> <span class="n">value</span>
        <span class="k">break</span>
      <span class="k">end</span>
    <span class="k">end</span>
  <span class="k">end</span>
  <span class="k">return</span> <span class="n">result</span>
<span class="k">end</span></code></pre></figure>

<p>The original Codility result link is no longer available. The <a href="https://app.codility.com/programmers/lessons/5-prefix_sums/genomic_range_query/">Genomic Range Query challenge</a> is still available.</p>

<p>My first solution didn’t use the prefix-sum technique that I think the lesson was teaching. Its time complexity is O(4 × M × N), where N is the string length and M is the number of queries. I tried to find a prefix-sum solution on my own, but couldn’t work one out. After researching and analysing several existing solutions, I came up with this implementation.</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">solution</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="nb">p</span><span class="p">,</span> <span class="n">q</span><span class="p">)</span>

  <span class="n">n</span> <span class="o">=</span> <span class="n">s</span><span class="p">.</span><span class="nf">size</span>
  <span class="n">prefix_sums</span> <span class="o">=</span> <span class="no">Hash</span><span class="p">.</span><span class="nf">new</span> <span class="p">{</span> <span class="o">|</span><span class="n">h</span><span class="p">,</span><span class="n">k</span><span class="o">|</span> <span class="n">h</span><span class="p">[</span><span class="n">k</span><span class="p">]</span> <span class="o">=</span> <span class="no">Array</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">n</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="p">}</span>
  <span class="n">nucleotides</span> <span class="o">=</span> <span class="p">{</span>
      <span class="s1">'A'</span> <span class="o">=&gt;</span> <span class="mi">1</span><span class="p">,</span>
      <span class="s1">'C'</span> <span class="o">=&gt;</span> <span class="mi">2</span><span class="p">,</span>
      <span class="s1">'G'</span> <span class="o">=&gt;</span> <span class="mi">3</span><span class="p">,</span>
      <span class="s1">'T'</span> <span class="o">=&gt;</span> <span class="mi">4</span>
  <span class="p">}</span>

  <span class="n">nucleotides</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="o">|</span>
    <span class="k">for</span> <span class="n">i</span> <span class="k">in</span> <span class="mi">1</span><span class="o">..</span><span class="n">n</span>
      <span class="n">prefix_sums</span><span class="p">[</span><span class="n">key</span><span class="p">][</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">prefix_sums</span><span class="p">[</span><span class="n">key</span><span class="p">][</span><span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span>
      <span class="n">prefix_sums</span><span class="p">[</span><span class="n">key</span><span class="p">][</span><span class="n">i</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span> <span class="k">if</span> <span class="n">s</span><span class="p">[</span><span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span> <span class="o">==</span> <span class="n">key</span>
    <span class="k">end</span>
  <span class="k">end</span>

  <span class="n">result</span> <span class="o">=</span> <span class="p">[]</span>

  <span class="k">for</span> <span class="n">i</span> <span class="k">in</span> <span class="p">(</span><span class="mi">1</span><span class="o">..</span><span class="nb">p</span><span class="p">.</span><span class="nf">size</span><span class="p">)</span> <span class="k">do</span>
    <span class="n">nucleotides</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="o">|</span>
      <span class="k">if</span> <span class="n">prefix_sums</span><span class="p">[</span><span class="n">key</span><span class="p">][</span><span class="nb">p</span><span class="p">[</span><span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]]</span> <span class="o">!=</span> <span class="n">prefix_sums</span><span class="p">[</span><span class="n">key</span><span class="p">][</span><span class="n">q</span><span class="p">[</span><span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span>
        <span class="n">result</span> <span class="o">&lt;&lt;</span> <span class="n">value</span>
        <span class="k">break</span>
      <span class="k">end</span>
    <span class="k">end</span>
  <span class="k">end</span>

  <span class="k">return</span> <span class="n">result</span>
<span class="k">end</span></code></pre></figure>

<p>Now a brief explanation about this algorithm. The first part calculates prefix sums. Instead of keeping a separate variable for each letter, it creates a hash that stores an array of sums for each nucleotide.</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">solution</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="nb">p</span><span class="p">,</span> <span class="n">q</span><span class="p">)</span>

  <span class="n">n</span> <span class="o">=</span> <span class="n">s</span><span class="p">.</span><span class="nf">size</span>
  <span class="n">prefix_sums</span> <span class="o">=</span> <span class="no">Hash</span><span class="p">.</span><span class="nf">new</span> <span class="p">{</span> <span class="o">|</span><span class="n">h</span><span class="p">,</span><span class="n">k</span><span class="o">|</span> <span class="n">h</span><span class="p">[</span><span class="n">k</span><span class="p">]</span> <span class="o">=</span> <span class="no">Array</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">n</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="p">}</span>
  <span class="n">nucleotides</span> <span class="o">=</span> <span class="p">{</span>
      <span class="s1">'A'</span> <span class="o">=&gt;</span> <span class="mi">1</span><span class="p">,</span>
      <span class="s1">'C'</span> <span class="o">=&gt;</span> <span class="mi">2</span><span class="p">,</span>
      <span class="s1">'G'</span> <span class="o">=&gt;</span> <span class="mi">3</span><span class="p">,</span>
      <span class="s1">'T'</span> <span class="o">=&gt;</span> <span class="mi">4</span>
  <span class="p">}</span>

  <span class="n">nucleotides</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="o">|</span>
    <span class="k">for</span> <span class="n">i</span> <span class="k">in</span> <span class="mi">1</span><span class="o">..</span><span class="n">n</span>
      <span class="n">prefix_sums</span><span class="p">[</span><span class="n">key</span><span class="p">][</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">prefix_sums</span><span class="p">[</span><span class="n">key</span><span class="p">][</span><span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span>
      <span class="n">prefix_sums</span><span class="p">[</span><span class="n">key</span><span class="p">][</span><span class="n">i</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span> <span class="k">if</span> <span class="n">s</span><span class="p">[</span><span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span> <span class="o">==</span> <span class="n">key</span>
    <span class="k">end</span>
  <span class="k">end</span></code></pre></figure>

<p>The hash is created with a block that is called whenever a missing key is accessed. It creates an array of the required size, initialized to zero. After that, for each nucleotide in the hash, an array of prefix sums is created for each letter. Using the string of the example (CAGCCTA), the hash of prefix sums will be like this:</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="p">{</span>
  <span class="s2">"A"</span><span class="o">=&gt;</span><span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">],</span>
  <span class="s2">"C"</span><span class="o">=&gt;</span><span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">3</span><span class="p">],</span>
  <span class="s2">"G"</span><span class="o">=&gt;</span><span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">],</span>
  <span class="s2">"T"</span><span class="o">=&gt;</span><span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">]</span>
<span class="p">}</span></code></pre></figure>

<p>As you can see, when there is a difference between two values in an array, there is a letter in there. For example, in the position 2 (0 index based) there is a letter “G”, because <code class="language-plaintext highlighter-rouge">prefix_sums["G"][2 + 1] != prefix_sums["G"][2]</code>. Now it’s easier to check whether a given range contains a nucleotide.</p>

<p>The rest of the algorithm builds the result iterating over the <code class="language-plaintext highlighter-rouge">p</code> and <code class="language-plaintext highlighter-rouge">q</code> (<code class="language-plaintext highlighter-rouge">p.size == q.size</code>). For each query, the algorithm checks the nucleotides in order of impact factor, from the lowest (<code class="language-plaintext highlighter-rouge">A</code>) to the highest (<code class="language-plaintext highlighter-rouge">T</code>), to find the first one present in the range. If a letter is found (as explained before, if there is a difference between the prefix sums array in given indexes), simply push this result to the <code class="language-plaintext highlighter-rouge">result</code> array and go to the next range.</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">result</span> <span class="o">=</span> <span class="p">[]</span>

<span class="k">for</span> <span class="n">i</span> <span class="k">in</span> <span class="p">(</span><span class="mi">1</span><span class="o">..</span><span class="nb">p</span><span class="p">.</span><span class="nf">size</span><span class="p">)</span> <span class="k">do</span>
  <span class="n">nucleotides</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">key</span><span class="p">,</span> <span class="n">value</span><span class="o">|</span>
    <span class="k">if</span> <span class="n">prefix_sums</span><span class="p">[</span><span class="n">key</span><span class="p">][</span><span class="nb">p</span><span class="p">[</span><span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]]</span> <span class="o">!=</span> <span class="n">prefix_sums</span><span class="p">[</span><span class="n">key</span><span class="p">][</span><span class="n">q</span><span class="p">[</span><span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span>
      <span class="n">result</span> <span class="o">&lt;&lt;</span> <span class="n">value</span>
      <span class="k">break</span>
    <span class="k">end</span>
  <span class="k">end</span>
<span class="k">end</span>

<span class="k">return</span> <span class="n">result</span></code></pre></figure>

<p>This reduces the time complexity from O(4 × M × N) to O(4 × N + 4 × M). Ignoring constant factors, that is O(N + M), which matches the expected complexity.</p>

<p>Other Codility challenges (the original shared solution links are no longer available):</p>
<ul>
  
  <li><a href="https://app.codility.com/programmers/lessons/1-iterations/binary_gap/">Binary Gap</a></li>
  
  <li><a href="https://app.codility.com/programmers/trainings/4/str_symmetry_point/">Str Symmetry Point</a></li>
  
  <li><a href="https://app.codility.com/programmers/lessons/5-prefix_sums/count_div/">Count Div</a></li>
  
  <li><a href="https://app.codility.com/programmers/lessons/4-counting_elements/max_counters/">Max Counters</a></li>
  
  <li><a href="https://app.codility.com/programmers/lessons/3-time_complexity/frog_jmp/">Frog Jump</a></li>
  
</ul>]]></content><author><name>Manoel de Albuquerque Lira Amaro</name></author><category term="codility" /><category term="challenge" /><category term="genomicRangeQuery" /><category term="solution" /><category term="ruby" /><summary type="html"><![CDATA[A solution to the Genomic Range Query problem, covering how DNA prefix sums can be used to answer multiple queries efficiently.]]></summary></entry></feed>