<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[The Algorithmic Angle]]></title><description><![CDATA[Puzzle Writeups showing the process]]></description><link>https://korff.dev</link><generator>RSS for Node</generator><lastBuildDate>Wed, 15 Apr 2026 13:34:57 GMT</lastBuildDate><atom:link href="https://korff.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Introducing Latinum - A small LaTeX equation editor]]></title><description><![CDATA[I often needed a quick way to write and export LaTeX equations to png, but most of the sites out there are filled with ads, pop-ups, or require an account. I just wanted a text box and a way to export.
Apparently that was too much to ask, so I made m...]]></description><link>https://korff.dev/introducing-latinum-a-small-latex-equation-editor</link><guid isPermaLink="true">https://korff.dev/introducing-latinum-a-small-latex-equation-editor</guid><category><![CDATA[latex]]></category><category><![CDATA[launch]]></category><dc:creator><![CDATA[David]]></dc:creator><pubDate>Sun, 07 Dec 2025 12:23:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1765109249313/88e6f137-9a06-46b7-af52-dc90d5608f3d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I often needed a quick way to write and export LaTeX equations to png, but most of the sites out there are filled with ads, pop-ups, or require an account. I just wanted a text box and a way to export.</p>
<p>Apparently that was too much to ask, so I made my own.</p>
<p><strong>Try it:</strong> <a target="_blank" href="https://latinum.korff.dev">latinum.korff.dev</a><br /><strong>Code:</strong> <a target="_blank" href="https://github.com/addelec/latinum">github.com/addelec/latinum</a></p>
<h2 id="heading-features">Features</h2>
<ul>
<li><p><strong>PWA support</strong>: You can “install” it and use it offline</p>
</li>
<li><p><strong>Web Share support</strong>: You can share the editor link directly</p>
</li>
<li><p><strong>Clipboard fallback on Firefox</strong>: Firefox somehow still doesn’t properly support the Web Share API, so there I resort to copying the link to the clipboard</p>
</li>
<li><p><strong>No ads and no accounts</strong>: It’s literally just a text input and a couple of buttons</p>
</li>
</ul>
<p>And that’s it.</p>
<p>I was annoyed that this didn’t exist, so I made it. If you have ideas or find bugs, feel free to open an issue on GitHub.</p>
]]></content:encoded></item><item><title><![CDATA[Jane Street puzzle writeup: Shut the Box]]></title><description><![CDATA[Overview:
Problem summary:
We must mark each cell of the 20×20 grid as either inside or outside.
To help us the grid contains some special cells with the following rules:
Arrow cells:
Arrow cells are per definition outside.
Arrow cells can have one o...]]></description><link>https://korff.dev/jane-street-puzzle-writeup-shut-the-box</link><guid isPermaLink="true">https://korff.dev/jane-street-puzzle-writeup-shut-the-box</guid><category><![CDATA[puzzle]]></category><category><![CDATA[Write Up]]></category><category><![CDATA[janestreet]]></category><dc:creator><![CDATA[David]]></dc:creator><pubDate>Mon, 01 Dec 2025 11:00:29 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1764331087691/391d7f93-272f-433a-8db6-9d168b52068f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-overview">Overview:</h1>
<h2 id="heading-problem-summary">Problem summary:</h2>
<p>We must mark each cell of the 20×20 grid as either inside or outside.</p>
<p>To help us the grid contains some special cells with the following rules:</p>
<h3 id="heading-arrow-cells">Arrow cells:</h3>
<p>Arrow cells are per definition outside.</p>
<p>Arrow cells can have one or more arrows, these arrow point in cardinal directions.</p>
<p>Arrow cells force the first inside cells in the pointing-directions to be equidistant from the arrow cell, and no closer inside cell can exist in any direction.</p>
<h3 id="heading-numbered-cells">Numbered cells:</h3>
<p>Numbered cells are per definition inside.</p>
<p>Numbered cells contain a number, this number specifies the total count of inside cells in the 3×3 neighborhood centered around the cell itself (so a maximum of 9 = itself + 8 neighbors).</p>
<h3 id="heading-global-constraints">Global constraints:</h3>
<p>Every inside cell must be connected to every other inside cell.</p>
<p>Every outside cell must be connected to the edge of the grid (so it can be “cut away”).</p>
<h2 id="heading-definitions">Definitions:</h2>
<ul>
<li><p>We define a cell to be inside if this cell is part of the final box</p>
</li>
<li><p>We define a cell to be outside if this cell is not part of the final box</p>
</li>
<li><p>We use “the shape“ or “shape” to refer to the 2 dimensional shape to be folded into a box</p>
</li>
<li><p>We use “arrow distance“ to refer to the distance between an arrow cell and its corresponding closest inside cells</p>
</li>
</ul>
<h1 id="heading-solving">Solving:</h1>
<p>You could solve the puzzle using two different methods.</p>
<p>The first being purely visual using <a target="_blank" href="https://excalidraw.com/">excalidraw</a> or writing a custom tool using something like pygame.</p>
<p>The second being solving the puzzle algorithmically, the simplest approach here is trying to convert the constraints of the puzzle into boolean expressions and using a sat-solver like Z3 to satisfy these constraints.</p>
<h2 id="heading-z3-powered-solver">Z3-Powered solver</h2>
<p>So how does one convert the different puzzle constraints?</p>
<p>We start by modelling the grid as an array of integers (“g_{x}_{y}”) with the constraint of each int being &gt;= 0 and &lt;= 1.</p>
<p>I used integers rather than booleans because summing a 3×3 neighborhood becomes trivial with integers.</p>
<h3 id="heading-the-neighborhood-constraint">The <strong>neighborhood-constraint</strong>:</h3>
<p>As specified the neighborhood constraint is rather trivial.</p>
<pre><code class="lang-python"><span class="hljs-comment"># Constraints for the neighbor counts</span>
<span class="hljs-keyword">for</span> (r,c), val <span class="hljs-keyword">in</span> numbers.items(): <span class="hljs-comment"># Numbers is an array of positions of number-cells</span>
    neighs = []
    <span class="hljs-keyword">for</span> dr <span class="hljs-keyword">in</span> (<span class="hljs-number">-1</span>,<span class="hljs-number">0</span>,<span class="hljs-number">1</span>):
        <span class="hljs-keyword">for</span> dc <span class="hljs-keyword">in</span> (<span class="hljs-number">-1</span>,<span class="hljs-number">0</span>,<span class="hljs-number">1</span>):
            rr, cc = r+dr, c+dc
            <span class="hljs-keyword">if</span> is_in_grid(rr, cc):
                neighs.append(grid[rr][cc])
    s.add(Sum(neighs) == val) <span class="hljs-comment"># s is the z3 solver</span>
</code></pre>
<p>This code just loops through all numbers and sets the constraint that the amount of inside cells in the 3×3 area must equal the number value.</p>
<h3 id="heading-the-arrow-constraint">The arrow-constraint:</h3>
<p>The arrow constraint was more tricky to implement.</p>
<p>The basic idea is to generate a distance expression in each direction with nested if conditions.</p>
<pre><code class="lang-python"><span class="hljs-comment"># Arrow constraints</span>
<span class="hljs-comment"># Helper to get distance expression by building a ray of Ifs</span>
<span class="hljs-comment"># It walks backwards from the edge to the cell, building an If chain</span>
<span class="hljs-comment"># the chain reads if the current cell is inside, return distance, else (if chain)</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_dist_expr</span>(<span class="hljs-params">row, col, dx, dy</span>):</span>

    <span class="hljs-comment"># Build a list of cells along the specified direction</span>
    cells = []
    curr_row, curr_col = row + dx, col + dy
    <span class="hljs-keyword">while</span> is_in_grid(curr_row, curr_col):
        cells.append((curr_row, curr_col))
        curr_row += dx
        curr_col += dy

    <span class="hljs-comment"># Start with an arbitrary large distance</span>
    if_chain = <span class="hljs-number">999</span>

    <span class="hljs-comment"># Iterate backwards to build the If chain, otherwise the last element would have priority</span>
    <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(len(cells)<span class="hljs-number">-1</span>, <span class="hljs-number">-1</span>, <span class="hljs-number">-1</span>):
        rr, cc = cells[i]
        dist = i + <span class="hljs-number">1</span>
        <span class="hljs-comment"># If the cell is inside, return distance, else continue the chain</span>
        if_chain = If(grid[rr][cc] == <span class="hljs-number">1</span>, dist, if_chain)
    <span class="hljs-keyword">return</span> if_chain

dir_map = {<span class="hljs-string">"NORTH"</span>: (<span class="hljs-number">-1</span>, <span class="hljs-number">0</span>), <span class="hljs-string">"SOUTH"</span>: (<span class="hljs-number">1</span>, <span class="hljs-number">0</span>), <span class="hljs-string">"EAST"</span>: (<span class="hljs-number">0</span>, <span class="hljs-number">1</span>), <span class="hljs-string">"WEST"</span>: (<span class="hljs-number">0</span>, <span class="hljs-number">-1</span>)}

<span class="hljs-keyword">for</span> (r,c), dirs <span class="hljs-keyword">in</span> arrows.items():
    <span class="hljs-comment"># Define the target distance of the arrow</span>
    target = Int(<span class="hljs-string">f"arrow_target_<span class="hljs-subst">{r}</span>_<span class="hljs-subst">{c}</span>"</span>)

    s.add(target &gt; <span class="hljs-number">0</span>, target &lt; GRID_SIZE)

    <span class="hljs-keyword">for</span> d_name, (dx, dy) <span class="hljs-keyword">in</span> dir_map.items():
        dist_expr = get_dist_expr(r, c, dx, dy)

        <span class="hljs-comment"># If the arrow points in this direction, distance must equal target</span>
        <span class="hljs-comment"># Else distance must be greater than target</span>
        <span class="hljs-keyword">if</span> d_name <span class="hljs-keyword">in</span> dirs:
            s.add(dist_expr == target)
        <span class="hljs-keyword">else</span>:
            s.add(dist_expr &gt; target)
</code></pre>
<h3 id="heading-the-inside-connectivity-constraint">The inside-connectivity-constraint:</h3>
<p>This is the constraint that took by far the most time to implement, since I didn’t really know how to describe the idea of connectivity in expressions.</p>
<p>The approach ended up being:</p>
<ol>
<li><p>Selected one cell that is guaranteed to be inside (I simply used the first number cell), we will call it root</p>
</li>
<li><p>Create a new distance grid and set the distance of the root cell to 0</p>
</li>
<li><p>Now if a cell is connected it must have a neighbor with a distance of its own distance - 1</p>
</li>
</ol>
<pre><code class="lang-python"><span class="hljs-comment"># Constraint to force all inside cells to be connected</span>
<span class="hljs-comment"># Select the first number cell as root (since it is an inside cell per definition), so all inside cells must connect to it</span>
root_r, root_c = list(numbers.keys())[<span class="hljs-number">0</span>]

<span class="hljs-comment"># Idea:</span>
<span class="hljs-comment"># We check connectivity by essentially doing a flood fill with distance values</span>
<span class="hljs-comment"># Then if a cell is inside it must have a cardinal neighbor with dist = d-1 otherwise there does not exist a path to the root</span>

<span class="hljs-comment"># Create a new distance grid for connectivity</span>
d_in = [[Int(<span class="hljs-string">f"d_in_<span class="hljs-subst">{r}</span>_<span class="hljs-subst">{c}</span>"</span>) <span class="hljs-keyword">for</span> c <span class="hljs-keyword">in</span> range(COLS)] <span class="hljs-keyword">for</span> r <span class="hljs-keyword">in</span> range(ROWS)]

<span class="hljs-keyword">for</span> r <span class="hljs-keyword">in</span> range(ROWS):
    <span class="hljs-keyword">for</span> c <span class="hljs-keyword">in</span> range(COLS):
        <span class="hljs-comment"># If outside, dist is -1</span>
        s.add(Implies(grid[r][c] == <span class="hljs-number">0</span>, d_in[r][c] == <span class="hljs-number">-1</span>))

        <span class="hljs-comment"># If the cell is the root cell, set the distance to 0</span>
        <span class="hljs-keyword">if</span> (r,c) == (root_r, root_c):
            s.add(d_in[r][c] == <span class="hljs-number">0</span>)
            <span class="hljs-keyword">continue</span>

        <span class="hljs-comment"># Since the cell is not the root cell, its distance must be &gt; 0 if inside</span>
        s.add(Implies(grid[r][c] == <span class="hljs-number">1</span>, d_in[r][c] &gt; <span class="hljs-number">0</span>))

        <span class="hljs-comment"># Get the cardinal neighbors</span>
        neighs = []
        <span class="hljs-keyword">for</span> dr, dc <span class="hljs-keyword">in</span> [(<span class="hljs-number">0</span>,<span class="hljs-number">1</span>), (<span class="hljs-number">0</span>,<span class="hljs-number">-1</span>), (<span class="hljs-number">1</span>,<span class="hljs-number">0</span>), (<span class="hljs-number">-1</span>,<span class="hljs-number">0</span>)]:
            rr, cc = r+dr, c+dc
            <span class="hljs-keyword">if</span> is_in_grid(rr, cc):
                neighs.append(d_in[rr][cc])

        <span class="hljs-comment"># Since the cell is inside, there must exist a neighbor with dist = d-1</span>
        s.add(Implies(grid[r][c] == <span class="hljs-number">1</span>, Or([n == d_in[r][c] - <span class="hljs-number">1</span> <span class="hljs-keyword">for</span> n <span class="hljs-keyword">in</span> neighs])))
</code></pre>
<h3 id="heading-the-outside-connectivity-constraint">The outside-connectivity-constraint:</h3>
<p>The approach of outside-connectivity is similar to the process for inside connectivity. The only difference being is we set a cell to have a distance of 0 if it is on the edge of the grid.</p>
<pre><code class="lang-python"><span class="hljs-comment"># Connected to edge constraint</span>
<span class="hljs-comment"># Create a new distance grid for outside connectivity</span>
d_out = [[Int(<span class="hljs-string">f"d_out_<span class="hljs-subst">{r}</span>_<span class="hljs-subst">{c}</span>"</span>) <span class="hljs-keyword">for</span> c <span class="hljs-keyword">in</span> range(COLS)] <span class="hljs-keyword">for</span> r <span class="hljs-keyword">in</span> range(ROWS)]

<span class="hljs-comment"># Idea:</span>
<span class="hljs-comment"># If a cell is on the outside and on the boundary, dist = 0</span>
<span class="hljs-comment"># If a cell is outside and not on the boundary, there must exist a neighbor with dist = d-1</span>

<span class="hljs-keyword">for</span> r <span class="hljs-keyword">in</span> range(ROWS):
    <span class="hljs-keyword">for</span> c <span class="hljs-keyword">in</span> range(COLS):
        s.add(Implies(grid[r][c] == <span class="hljs-number">1</span>, d_out[r][c] == <span class="hljs-number">-1</span>))
        s.add(Implies(grid[r][c] == <span class="hljs-number">0</span>, d_out[r][c] &gt;= <span class="hljs-number">0</span>))

        is_boundary = (r == <span class="hljs-number">0</span> <span class="hljs-keyword">or</span> r == ROWS<span class="hljs-number">-1</span> <span class="hljs-keyword">or</span> c == <span class="hljs-number">0</span> <span class="hljs-keyword">or</span> c == COLS<span class="hljs-number">-1</span>)

        <span class="hljs-keyword">if</span> is_boundary:
            s.add(Implies(grid[r][c] == <span class="hljs-number">0</span>, d_out[r][c] == <span class="hljs-number">0</span>))
            <span class="hljs-keyword">continue</span>

        <span class="hljs-comment"># If outside and not boundary, dist &gt; 0</span>
        s.add(Implies(grid[r][c] == <span class="hljs-number">0</span>, d_out[r][c] &gt; <span class="hljs-number">0</span>))

        neighs = []
        <span class="hljs-keyword">for</span> dr, dc <span class="hljs-keyword">in</span> [(<span class="hljs-number">0</span>,<span class="hljs-number">1</span>), (<span class="hljs-number">0</span>,<span class="hljs-number">-1</span>), (<span class="hljs-number">1</span>,<span class="hljs-number">0</span>), (<span class="hljs-number">-1</span>,<span class="hljs-number">0</span>)]:
            rr, cc = r+dr, c+dc
            neighs.append(d_out[rr][cc])

        <span class="hljs-comment"># If the cell is outside, then there must exist a neighbor with dist = d-1</span>
        s.add(Implies(grid[r][c] == <span class="hljs-number">0</span>, Or([n == d_out[r][c] - <span class="hljs-number">1</span> <span class="hljs-keyword">for</span> n <span class="hljs-keyword">in</span> neighs])))
</code></pre>
<h3 id="heading-the-finished-grid">The finished grid</h3>
<p>After running the code there were 72 different solutions.</p>
<p>To produce a single “consensus” I combined these solutions cell-wise:</p>
<ul>
<li><p>If a cell was inside in all solutions → mark it as inside (green)</p>
</li>
<li><p>If a cell was outside in all solutions → mark it as outside (white)</p>
</li>
<li><p>Otherwise → mark it as unsure (grey)</p>
</li>
</ul>
<p>This resulted in the following grid:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1764328868611/aa6d839e-e4fe-44ca-be60-fc66e4342247.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-folding">Folding</h2>
<p>I experimented with automatic folding but ended up simply just folding a printed copy by hand.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">If you’ve implemented a folding algorithm or have resource on the topic, please email me at contact@korff.dev.</div>
</div>

<p>This was surprisingly straightforward, because of the rather distinct edge geometry making only some folds possible. I marked the unsure cells to remind me that they don’t have to be included.</p>
<p>After a half an hour of fiddling around with scissors and tape, I managed to fold the shape into a box, adding together the numbers on each face and multiplying the sum gave me the following solution: <strong>16414860</strong></p>
<h2 id="heading-code">Code</h2>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="5a6c60c0b2295dc9a2ae913ea3c469f6"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/Addelec/5a6c60c0b2295dc9a2ae913ea3c469f6" class="embed-card">https://gist.github.com/Addelec/5a6c60c0b2295dc9a2ae913ea3c469f6</a></div><p> </p>
<p>Or view to full code <a target="_blank" href="https://gist.github.com/Addelec/5a6c60c0b2295dc9a2ae913ea3c469f6">here</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Jane Street puzzle writeup: Robot Baseball]]></title><description><![CDATA[Overview:
This puzzle involved calculating the maximum probability q of a game of baseball played between two robots to reach „full-count“ (three balls and two strikes).
At each turn, the two robots (one as the pitcher and the other as the batter) si...]]></description><link>https://korff.dev/jane-street-puzzle-writeup-robot-baseball</link><guid isPermaLink="true">https://korff.dev/jane-street-puzzle-writeup-robot-baseball</guid><category><![CDATA[Write Up]]></category><category><![CDATA[janestreet]]></category><category><![CDATA[puzzle]]></category><dc:creator><![CDATA[David]]></dc:creator><pubDate>Sat, 01 Nov 2025 12:16:26 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1761566887424/9a377c85-7ae7-47e7-953a-9db644948ccb.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-overview">Overview:</h2>
<p>This puzzle involved calculating the maximum probability q of a game of baseball played between two robots to reach „full-count“ (three balls and two strikes).</p>
<p>At each turn, the two robots (one as the pitcher and the other as the batter) simultaneously make one of two choices:</p>
<ul>
<li><p>The pitcher can either throw a ball or a strike.</p>
</li>
<li><p>The batter can either swing or wait.</p>
</li>
</ul>
<p>A running tally of strikes and balls is also maintained.</p>
<p>This process leads to the following matrix:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td></td><td>Pitcher throws ball</td><td>Pitcher throws strike</td></tr>
</thead>
<tbody>
<tr>
<td>Batter swings</td><td>strikes + 1</td><td>home run with probability h otherwise strikes + 1</td></tr>
<tr>
<td>Batter waits</td><td>balls + 1</td><td>strikes + 1</td></tr>
</tbody>
</table>
</div><p>A game series concludes if any of the following conditions are met:</p>
<ul>
<li><p>The count of balls reaches 4 → Results in the batter gaining 1 point</p>
</li>
<li><p>The count of strikes reaches 3 → Results in the batter gaining 0 points</p>
</li>
<li><p>The batter hits a home run → Results in the batter gaining 4 points</p>
</li>
</ul>
<p>The goal of the batter is to maximize its score, while the pitcher aims to minimize it.</p>
<p>Furthermore, the robots use optimal strategies and h has been set to maximize the value of q.</p>
<h2 id="heading-approach">Approach:</h2>
<ol>
<li><p>Find the optimal strategy for the bots</p>
</li>
<li><p>Find a function for q that depends on h</p>
</li>
<li><p>Find the maximum value of this function</p>
</li>
</ol>
<h2 id="heading-the-bots-strategy">The bots strategy:</h2>
<p>Since there is no trivial best strategy I turned to the <a target="_blank" href="https://en.wikipedia.org/wiki/Nash_equilibrium">Nash equilibrium</a>. Essentially it is about the bots removing any possibility of being exploited by another strategy, that means the bots have to be statistically indifferent to every action of their opponent. The bots do this by choosing actions based on probabilities.</p>
<p>So we just need to compute the expected values of each actions combined with the opponents choice and solve them to be equal.</p>
<p>So let b be the probability of the batter swinging and p be the probability of the pitcher throwing a ball.</p>
<p>But how do we get the expected values?</p>
<p>The expected value change with the game state i.e. if the count of strikes has already reached 2 the expected value would obviously be less than if the count of balls has reached 3 instead.</p>
<p>Let’s start by solving the most trivial case 3 balls and 2 strikes. No matter what the bots chose the game must come to an end and the payoffs are known from the puzzle description. So lets construct a payoff-matrix.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td></td><td>Pitcher throws ball (<strong>p</strong>)</td><td>Pitcher throws strike (<strong>1-p</strong>)</td><td></td></tr>
</thead>
<tbody>
<tr>
<td>Batter swings (<strong>b</strong>)</td><td>0</td><td>h*4+(1-h)*0</td><td>\= (h*4)*(<strong>1-p</strong>)</td></tr>
<tr>
<td>Batter waits (<strong>1-b</strong>)</td><td>1</td><td>0</td><td>\= <strong>p</strong></td></tr>
<tr>
<td></td><td>\= 1*(<strong>1-b</strong>)</td><td>\= (h*4)*<strong>b</strong></td></tr>
</tbody>
</table>
</div><p>$$4 \cdot h \cdot (1-p) = p \;\qquad 1-b = 4 \cdot h \cdot b$$</p><p>$$p = \frac{4h}{1+4h} \qquad b = \frac{1}{1+4h}$$</p><p>Now we have function for p and b so we could compute the expected value of this game state dependent on h. However doing this for every game state is quite troublesome so this is a task best left to computers.</p>
<p>If we consider the various game states as nodes in a dependency graph, we get the following structure:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1760107233540/327e01ac-5926-4bf3-8a2c-fbbbc1e21809.png" alt class="image--center mx-auto" /></p>
<pre><code class="lang-python"><span class="hljs-comment"># If you are interested in the code, you will find the full code in the github gist</span>

nodes = []

root = Node(<span class="hljs-number">0</span>,<span class="hljs-number">0</span>)

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">build_graph</span>(<span class="hljs-params">root</span>):</span>

  <span class="hljs-comment"># If one extra ball is possible, create that child node</span>
  <span class="hljs-keyword">if</span> root.b != <span class="hljs-number">3</span>:
    child = Node(root.b + <span class="hljs-number">1</span>, root.s)


    <span class="hljs-keyword">if</span> child <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> nodes:
      nodes.append(child)
      root.b1 = child
      build_graph(child)
    <span class="hljs-keyword">else</span>:
      root.b1 = nodes[nodes.index(child)]

  <span class="hljs-comment"># If one extra strike is possible, create that child node</span>
  <span class="hljs-keyword">if</span> root.s != <span class="hljs-number">2</span>:
    child = Node(root.b, root.s + <span class="hljs-number">1</span>)

    <span class="hljs-keyword">if</span> child <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> nodes:
      nodes.append(child)
      root.s1 = child
      build_graph(child)
    <span class="hljs-keyword">else</span>:
      root.s1 = nodes[nodes.index(child)]

nodes.append(root)  
build_graph(root)


print(<span class="hljs-string">f"Graph nodes: <span class="hljs-subst">{len(nodes)}</span>"</span>)
</code></pre>
<h2 id="heading-finding-h-and-q">Finding h and q</h2>
<p>Now that we have p and b for every possible game state we have solved the game.</p>
<p>That alone would hardly be an entertaining viewing experience, so we need to find the value for h maximizing q.</p>
<p>I have done this by mapping every path from the 0,0-node to the 3,2-node.</p>
<pre><code class="lang-python"><span class="hljs-comment"># If you are interested in the code, you will find the full code in the github gist</span>
</code></pre>
<p>Now we have a function giving us q for a given h.</p>
<p>Now we just need to find the maximum within the Interval [0,1].</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> scipy.optimize <span class="hljs-keyword">import</span> minimize_scalar

res = minimize_scalar(<span class="hljs-keyword">lambda</span> h: -sum_func(h), bounds=(<span class="hljs-number">0</span>,<span class="hljs-number">1</span>), method=<span class="hljs-string">'bounded'</span>)

print(<span class="hljs-string">"h ="</span>, res.x)                <span class="hljs-comment"># h = 0.22697434289547633</span>
print(<span class="hljs-string">"q ="</span>, sum_func(res.x))      <span class="hljs-comment"># q = 0.2959679933709649</span>
</code></pre>
<p>And there we have it.</p>
<p>The maximal value for q is: <strong>0.2959679933</strong></p>
<h2 id="heading-full-code">Full code:</h2>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="b32cbeb93a422c5d7ffcdff9581aa496"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/Addelec/b32cbeb93a422c5d7ffcdff9581aa496" class="embed-card">https://gist.github.com/Addelec/b32cbeb93a422c5d7ffcdff9581aa496</a></div><p> </p>
<p><a target="_blank" href="https://gist.github.com/Addelec/b32cbeb93a422c5d7ffcdff9581aa496">View Github Gist</a></p>
]]></content:encoded></item><item><title><![CDATA[Jane Street puzzle writeup: Some Ones, Somewhere]]></title><description><![CDATA[Overview:
This puzzle involved extracting a sentence from a PDF file. The PDF contained images of mosaics arranged in a 3×3 grid, with some Scrabble letters on the sides. (Here’s the link to the PDF: https://www.janestreet.com/static/pdfs/puzzles/jun...]]></description><link>https://korff.dev/jane-street-puzzle-writeup-some-ones-somewhere</link><guid isPermaLink="true">https://korff.dev/jane-street-puzzle-writeup-some-ones-somewhere</guid><category><![CDATA[puzzle]]></category><category><![CDATA[Write Up]]></category><dc:creator><![CDATA[David]]></dc:creator><pubDate>Mon, 30 Jun 2025 22:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1752169949158/aa540a64-f302-4f37-a1cd-50038a0d772c.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-overview">Overview:</h2>
<p>This puzzle involved extracting a sentence from a PDF file. The PDF contained images of mosaics arranged in a 3×3 grid, with some Scrabble letters on the sides. (Here’s the link to the PDF: https://www.janestreet.com/static/pdfs/puzzles/june-2025-puzzle.pdf)</p>
<h2 id="heading-solving">Solving:</h2>
<p>Starting out, I removed the perspective from these images.</p>
<p>This allowed me to figure out the relative sizes of the squares. Originally, these sizes didn’t seem to suggest anything, but after multiplying them by the factor 45, a pattern began to emerge.</p>
<p>The sizes were now as follows:</p>
<ul>
<li><p>Grey: 9</p>
</li>
<li><p>Brown: 8</p>
</li>
<li><p>Yellow: 7</p>
</li>
<li><p>Light Blue: 6</p>
</li>
<li><p>Pink-Red: 5</p>
</li>
<li><p>Dark Blue: 4</p>
</li>
<li><p>Orange: 3</p>
</li>
<li><p>Green: 2</p>
</li>
</ul>
<p>It appears that a square with a side length of 1 is missing.</p>
<p>Now the next question: I assumed the original mosaic had to be recreated somehow, so how many of these squares am I allowed to use?</p>
<p>To figure that out I simply counted the squares on the board as well as the table next to it. This resulted in the same distribution as the sizes above.</p>
<p>To verify I multiplied all the sizes squared by their count to figure out if I could actually cover the grid.</p>
<p>The grid is 45×45 so in total it contains 2025 spaces. Calculating the area of all squares yields 2024 spaces. So we are one space short.</p>
<p>I assumed that this 1×1 square would have to be in line with some of the scrabble letters, so I could extract text. But this turned out to be false.</p>
<p>Nevertheless I created a small tool to aid with solving these grids.</p>
<p>An hour or so of trying and failing to solve the board to satisfy the constraints of this assumption. I gave up and just tried to solve the board.</p>
<p>This turned out to be more straightforward than expected, after less than half an hour I managed to solve all of the grids.</p>
<h3 id="heading-so-whats-next">So, what’s next?</h3>
<p>I had the solved boards but no clue on how to extract a sentence.</p>
<p>This was by far the most difficult part of the puzzle.</p>
<p>I tried to solve the boards differently, but was unsuccessful.</p>
<p>I tried drawing lines between the letters to see if the lines and spaces would intersect.</p>
<p>One out of these desperate attempts actually was successful.</p>
<p>I wrote down the alphabet and placed it next to the board (I was using Excalidraw) with the intention that crossing out the Scrabble letters would leave me with something interesting. That didn’t really happen but I found something else.</p>
<p>I noticed that the Scrabble letters and their spacing were perfectly aligned with the alphabet.</p>
<p>Below is an image showing the finished grids with the repeating alphabet next to the sides (this was created in Excel which would have made this discovery trivial).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752170078046/b1154261-e8a0-46da-923c-8740639478b7.png" alt class="image--center mx-auto" /></p>
<p>Now I just guessed that the letter from the left came first and the one from the top second.</p>
<p>This results in:</p>
<pre><code class="lang-plaintext">SU MO FC
UB ES IS
SQ UA RE

SUMOFCUBESISSQUARE
Solution: SUM OF CUBES IS SQUARE
</code></pre>
<p>Tool: <a target="_blank" href="https://static.korff.dev/blog/some-ones-somewhere">https://static.korff.dev/blog/some-ones-somewhere</a> (AI used for translating and some UI tweaks)</p>
<iframe src="https://static.korff.dev/blog/some-ones-somewhere" width="100%" height="1200"></iframe>]]></content:encoded></item><item><title><![CDATA[Jane Street puzzle writeup: Number Cross 5]]></title><description><![CDATA[Overview
This puzzle is about solving an 11 by 11 grid containing 9 different bordered regions.

Source: https://www.janestreet.com/puzzles/may-2025-update.png
Regions:
Start by inserting numbers 1-9 into the different regions. Not all numbers in the...]]></description><link>https://korff.dev/jane-street-puzzle-writeup-number-cross-5</link><guid isPermaLink="true">https://korff.dev/jane-street-puzzle-writeup-number-cross-5</guid><category><![CDATA[Write Up]]></category><category><![CDATA[puzzle]]></category><category><![CDATA[puzzle-solving]]></category><category><![CDATA[janestreet]]></category><dc:creator><![CDATA[David]]></dc:creator><pubDate>Mon, 02 Jun 2025 13:43:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1748871778824/51382910-f7e3-42dd-86c1-48f73c8f9e94.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-overview">Overview</h2>
<p>This puzzle is about solving an 11 by 11 grid containing 9 different bordered regions.</p>
<p><a target="_blank" href="https://www.janestreet.com/puzzles/number-cross-5-index/"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748857617874/f57c34ee-e444-44ea-9b25-9a603c49753f.png" alt class="image--center mx-auto" /></a></p>
<p>Source: <a target="_blank" href="https://www.janestreet.com/puzzles/may-2025-update.png">https://www.janestreet.com/puzzles/may-2025-update.png</a></p>
<h4 id="heading-regions">Regions:</h4>
<p>Start by inserting numbers 1-9 into the different regions. Not all numbers in the set 1-9 need to be used.</p>
<ol>
<li><p>Each region needs to contain the same digit throughout</p>
</li>
<li><p>Neighboring regions must contain different digits</p>
</li>
</ol>
<p>The same number can be reused in non-adjacent regions (Restriction 2)</p>
<h4 id="heading-tiles">Tiles:</h4>
<p>Next, tiles need to be placed into the grid.</p>
<ol>
<li><p>Tiles can’t be placed on yellow “locked” spaces</p>
</li>
<li><p>Tiles can’t be placed directly above or below another tile</p>
</li>
<li><p>Tiles need to be at least 2 spaces apart horizontally</p>
<ul>
<li><p>This ensures that each segment between tiles forms a valid number (minimum two digits)</p>
</li>
<li><p>Tiles can be placed directly next to the grid borders</p>
</li>
<li><p>Tiles cannot be placed one space away from the grid borders since this one space cannot form a valid number</p>
</li>
</ul>
</li>
</ol>
<p>When a tile is placed, it displaces the digit it covers. This displaced value is absorbed by its four orthogonal neighbors. A few additional constraints:</p>
<ul>
<li><p>No space can be incremented past 9</p>
</li>
<li><p>Yellow “locked” spaces cannot be changed =&gt; They always retain the original region-digit</p>
</li>
<li><p>As a result of this distribution-process two spaces in different regions are allowed to have the same digit</p>
</li>
</ul>
<p>After this operation is complete no numbers (formed between tiles) are allowed to repeat within the grid.</p>
<h4 id="heading-clues">Clues:</h4>
<p>Clues are written on each row. Each number (consecutive digits between tiles) needs to satisfy the given clue.</p>
<h4 id="heading-solution">Solution:</h4>
<p>Once the grid is complete and all clues are satisfied, the solution is the sum of the final numbers in the grid.</p>
<p>Full puzzle: <a target="_blank" href="https://www.janestreet.com/puzzles/number-cross-5-index/">https://www.janestreet.com/puzzles/number-cross-5-index/</a></p>
<h2 id="heading-preliminary-analysis">Preliminary Analysis</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748868311755/202ab50c-0326-4cae-a98a-2641fc8b0622.png" alt class="image--center mx-auto" /></p>
<p>A closer look at the puzzle allows for some early deductions:</p>
<ul>
<li><p><strong>The blue region has to be mapped to the number 2</strong>, since the locked spaces in the second row cannot be altered. Thus they must still have their original value and that value must satisfy the clue “product of digits is 20”. The prime factorization of 20 is <mark>2×2</mark>×5.</p>
</li>
<li><p><strong>This also helps to fix the tile positions in row 09: ◻️◻️⬛◻️◻️◻️⬛◻️◻️◻️⬛</strong><br />  Since spaces 4 and 5 are locked and cannot contain tiles, any number spanning those cells must begin and end with a 5 either before or after these locked cells.<br />  If space 3 were not a tile the number would stretch to at least four digits that is too long to produce a product of 20 using only the digits 2 and 5.<br />  Therefore, space 3 must be a tile to constrain the segment length. This logic also determines the placement of the remaining tiles in the row.</p>
</li>
<li><p><strong>Consequently, the red region must be either 4 or 5.</strong><br />  Since the clue for the first number is "product of digits is 20" and we already have 2 as one digit.<br />  The only two-digit combinations left are 4 × 5 or 5 × 4.</p>
</li>
<li><p><strong>The green region must then be 1.</strong><br />  This deduction results from looking at the next clue which is "product of digits is 25."<br />  The prime factorization of 25 is 5 × 5.<br />  Valid numbers for that product include 55, 551, 155, etc.<br />  Since we've already allocated 4 or 5 to red the only available digit that fits reasonably in this pattern and remains unallocated is 1. Assigning 1 to green allows the number groupings to work—while also ensuring compliance with the rule that no digits may repeat within the completed grid.</p>
</li>
</ul>
<h2 id="heading-algorithmic-solution">Algorithmic Solution</h2>
<h3 id="heading-overview-1">Overview</h3>
<p>Now with the preliminary analysis completed it is time to start thinking about the algorithm.</p>
<p>I opted for a relatively straight forward solution. That being:</p>
<h4 id="heading-pre-solving">Pre-Solving:</h4>
<ol>
<li><p><strong>Generate All Possible Tile Arrangements</strong></p>
</li>
<li><p><strong>Filter Legal Arrangements</strong><br /> For each row, discard any illegal configurations (tiles on locked spaces)</p>
</li>
</ol>
<h4 id="heading-solver">Solver:</h4>
<ol>
<li><p><strong>Generate Region-to-Digit Mappings</strong><br /> Enumerate all valid mappings of regions to digits 1–9 with respect to the adjacency rules.</p>
</li>
<li><p><strong>Iterate Over All Combinations</strong><br /> Apply all possible combinations of tile arrangements and mappings for the row</p>
</li>
<li><p><strong>Try to solve each number in the row</strong><br /> For each segment of digits between tiles:</p>
<ol>
<li><p>Identify the start and end of the number to solve</p>
</li>
<li><p>Identify the tiles below, left and right of the number</p>
</li>
<li><p>Identify which digits will be altered by each tile.</p>
</li>
<li><p>Try all possible combinations of tile-values</p>
</li>
<li><p>Check if the number is valid using the clue</p>
</li>
</ol>
</li>
<li><p><strong>Validate the Row</strong><br /> If all numbers in the row are valid append the row to a list of solved rows</p>
</li>
<li><h4 id="heading-repeat-for-all-rows">Repeat for all rows</h4>
</li>
</ol>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Note: Tiles above a number segment do not need to be explicitly checked since the algorithm processes rows from top to bottom. Any unresolved tile values from above can be deferred and distributed into tiles below, provided all constraints are respected.</div>
</div>

<h3 id="heading-generating-possible-tile-states">Generating possible tile states</h3>
<p>Possible tile arrangements are simply generated by counting in binary to 2048 (2^11) and rejecting non valid arrangements</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">bit_list</span>(<span class="hljs-params">n, length=<span class="hljs-number">11</span></span>):</span>
  bit_string = bin(n)[<span class="hljs-number">2</span>:] <span class="hljs-comment"># Cut off the "0b"</span>
  bit_string = bit_string[::<span class="hljs-number">-1</span>] <span class="hljs-comment"># Flip the string to convert to big endian</span>
  <span class="hljs-keyword">return</span> [int(digit) <span class="hljs-keyword">for</span> digit <span class="hljs-keyword">in</span> bit_string] + [<span class="hljs-number">0</span>] * (length - len(bit_string)) <span class="hljs-comment"># Add padding</span>

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">is_valid_arrangement</span>(<span class="hljs-params">tile_map</span>):</span>
  <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(len(tile_map)<span class="hljs-number">-1</span>):
    <span class="hljs-comment"># Skip if there is no tile</span>
    <span class="hljs-keyword">if</span> tile_map[i] == <span class="hljs-number">0</span>:
      <span class="hljs-keyword">continue</span>


    <span class="hljs-comment"># If a tile is at the second position, there is no space for two spaces</span>
    <span class="hljs-keyword">if</span> i == <span class="hljs-number">1</span>:
      <span class="hljs-keyword">return</span> <span class="hljs-literal">False</span>


    <span class="hljs-comment"># If a tile is at the second to last position, there is no space for two spaces</span>
    <span class="hljs-keyword">if</span> i == len(tile_map) - <span class="hljs-number">2</span>:
      <span class="hljs-keyword">return</span> <span class="hljs-literal">False</span>

    <span class="hljs-comment"># Check the offsets around the tile</span>
    <span class="hljs-keyword">for</span> offset <span class="hljs-keyword">in</span> [<span class="hljs-number">-2</span>, <span class="hljs-number">-1</span>, <span class="hljs-number">1</span>, <span class="hljs-number">2</span>]:
      <span class="hljs-keyword">if</span> i + offset &lt; <span class="hljs-number">0</span> <span class="hljs-keyword">or</span> i + offset &gt;= len(tile_map):
        <span class="hljs-keyword">continue</span>

      <span class="hljs-keyword">if</span> tile_map[i + offset] == <span class="hljs-number">1</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-literal">False</span>

  <span class="hljs-comment"># If there are no conflicts, the alignment is valid</span>
  <span class="hljs-keyword">return</span> <span class="hljs-literal">True</span>

arrangements = []

<span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(pow(<span class="hljs-number">2</span>,<span class="hljs-number">11</span>)):
  bits = bit_list(i)
  <span class="hljs-keyword">if</span> is_valid_arrangement(bits):
    arrangements.append(bits)
</code></pre>
<p>Continuing to generating all arrangements per row:</p>
<pre><code class="lang-python"><span class="hljs-comment"># Map of the locked spaces</span>
BOARD_MASK = [[<span class="hljs-number">0</span>] * <span class="hljs-number">11</span> <span class="hljs-keyword">for</span> _ <span class="hljs-keyword">in</span> range(<span class="hljs-number">11</span>)]

BOARD_MASK[<span class="hljs-number">1</span>][<span class="hljs-number">4</span>] = <span class="hljs-number">1</span>
BOARD_MASK[<span class="hljs-number">2</span>][<span class="hljs-number">4</span>] = <span class="hljs-number">1</span>
BOARD_MASK[<span class="hljs-number">2</span>][<span class="hljs-number">5</span>] = <span class="hljs-number">1</span>
BOARD_MASK[<span class="hljs-number">3</span>][<span class="hljs-number">5</span>] = <span class="hljs-number">1</span>
BOARD_MASK[<span class="hljs-number">4</span>][<span class="hljs-number">5</span>] = <span class="hljs-number">1</span>
BOARD_MASK[<span class="hljs-number">4</span>][<span class="hljs-number">6</span>] = <span class="hljs-number">1</span>
BOARD_MASK[<span class="hljs-number">5</span>][<span class="hljs-number">5</span>] = <span class="hljs-number">1</span>

BOARD_MASK[<span class="hljs-number">3</span>][<span class="hljs-number">1</span>] = <span class="hljs-number">1</span>
BOARD_MASK[<span class="hljs-number">4</span>][<span class="hljs-number">1</span>] = <span class="hljs-number">1</span>
BOARD_MASK[<span class="hljs-number">4</span>][<span class="hljs-number">2</span>] = <span class="hljs-number">1</span>

BOARD_MASK[<span class="hljs-number">7</span>][<span class="hljs-number">8</span>] = <span class="hljs-number">1</span>
BOARD_MASK[<span class="hljs-number">7</span>][<span class="hljs-number">9</span>] = <span class="hljs-number">1</span>
BOARD_MASK[<span class="hljs-number">8</span>][<span class="hljs-number">9</span>] = <span class="hljs-number">1</span>

BOARD_MASK[<span class="hljs-number">8</span>][<span class="hljs-number">4</span>] = <span class="hljs-number">1</span>
BOARD_MASK[<span class="hljs-number">9</span>][<span class="hljs-number">4</span>] = <span class="hljs-number">1</span>
BOARD_MASK[<span class="hljs-number">9</span>][<span class="hljs-number">3</span>] = <span class="hljs-number">1</span>

<span class="hljs-comment">#####################</span>

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">is_valid_alignment_with_row</span>(<span class="hljs-params">bits, row</span>):</span>
  <span class="hljs-comment"># Check if there is no 1 in the board mask</span>
  <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(len(bits)):
    <span class="hljs-keyword">if</span> bits[i] == <span class="hljs-number">1</span> <span class="hljs-keyword">and</span> BOARD_MASK[row][i] == <span class="hljs-number">1</span>:
      <span class="hljs-keyword">return</span> <span class="hljs-literal">False</span>

arrangements_for_row = []

<span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(<span class="hljs-number">11</span>):
  valid_alignments = []
  <span class="hljs-keyword">for</span> j <span class="hljs-keyword">in</span> range(len(arrangements)):
    <span class="hljs-keyword">if</span> is_valid_alignment_with_row(arrangements[j], i):
      valid_alignments.append(arrangements[j])
  arrangements_for_row.append(valid_alignments)
</code></pre>
<h3 id="heading-generating-board-mappings">Generating board mappings:</h3>
<pre><code class="lang-python"><span class="hljs-comment"># Representaion of the different regions of the board (the other way around to make it easier to work with)</span>
INDEX_BOARD = [
  [<span class="hljs-number">5</span>,<span class="hljs-number">5</span>,<span class="hljs-number">8</span>,<span class="hljs-number">8</span>,<span class="hljs-number">8</span>,<span class="hljs-number">8</span>,<span class="hljs-number">8</span>,<span class="hljs-number">8</span>,<span class="hljs-number">5</span>,<span class="hljs-number">5</span>,<span class="hljs-number">5</span>],
  [<span class="hljs-number">5</span>,<span class="hljs-number">5</span>,<span class="hljs-number">5</span>,<span class="hljs-number">5</span>,<span class="hljs-number">5</span>,<span class="hljs-number">5</span>,<span class="hljs-number">5</span>,<span class="hljs-number">5</span>,<span class="hljs-number">5</span>,<span class="hljs-number">5</span>,<span class="hljs-number">5</span>],
  [<span class="hljs-number">5</span>,<span class="hljs-number">5</span>,<span class="hljs-number">5</span>,<span class="hljs-number">5</span>,<span class="hljs-number">6</span>,<span class="hljs-number">5</span>,<span class="hljs-number">6</span>,<span class="hljs-number">7</span>,<span class="hljs-number">7</span>,<span class="hljs-number">7</span>,<span class="hljs-number">7</span>],
  [<span class="hljs-number">1</span>,<span class="hljs-number">5</span>,<span class="hljs-number">6</span>,<span class="hljs-number">6</span>,<span class="hljs-number">6</span>,<span class="hljs-number">6</span>,<span class="hljs-number">6</span>,<span class="hljs-number">6</span>,<span class="hljs-number">6</span>,<span class="hljs-number">7</span>,<span class="hljs-number">7</span>],
  [<span class="hljs-number">1</span>,<span class="hljs-number">5</span>,<span class="hljs-number">6</span>,<span class="hljs-number">6</span>,<span class="hljs-number">1</span>,<span class="hljs-number">1</span>,<span class="hljs-number">4</span>,<span class="hljs-number">4</span>,<span class="hljs-number">6</span>,<span class="hljs-number">4</span>,<span class="hljs-number">4</span>],
  [<span class="hljs-number">1</span>,<span class="hljs-number">1</span>,<span class="hljs-number">1</span>,<span class="hljs-number">1</span>,<span class="hljs-number">1</span>,<span class="hljs-number">4</span>,<span class="hljs-number">4</span>,<span class="hljs-number">4</span>,<span class="hljs-number">4</span>,<span class="hljs-number">4</span>,<span class="hljs-number">3</span>],
  [<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">2</span>,<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">4</span>,<span class="hljs-number">4</span>,<span class="hljs-number">3</span>,<span class="hljs-number">3</span>,<span class="hljs-number">4</span>,<span class="hljs-number">3</span>],
  [<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">2</span>,<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">4</span>,<span class="hljs-number">3</span>,<span class="hljs-number">3</span>,<span class="hljs-number">3</span>,<span class="hljs-number">3</span>,<span class="hljs-number">3</span>],
  [<span class="hljs-number">1</span>,<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">2</span>,<span class="hljs-number">2</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>,<span class="hljs-number">3</span>,<span class="hljs-number">3</span>,<span class="hljs-number">0</span>,<span class="hljs-number">3</span>],
  [<span class="hljs-number">0</span>,<span class="hljs-number">1</span>,<span class="hljs-number">0</span>,<span class="hljs-number">0</span>,<span class="hljs-number">0</span>,<span class="hljs-number">0</span>,<span class="hljs-number">0</span>,<span class="hljs-number">0</span>,<span class="hljs-number">0</span>,<span class="hljs-number">0</span>,<span class="hljs-number">0</span>],
  [<span class="hljs-number">0</span>,<span class="hljs-number">0</span>,<span class="hljs-number">0</span>,<span class="hljs-number">0</span>,<span class="hljs-number">0</span>,<span class="hljs-number">0</span>,<span class="hljs-number">0</span>,<span class="hljs-number">0</span>,<span class="hljs-number">0</span>,<span class="hljs-number">0</span>,<span class="hljs-number">0</span>], 
]

<span class="hljs-comment"># A mapping of region-index to neighboring region indices (from which the original region needs to be different)</span>
difference_mapping = {
  <span class="hljs-number">0</span>: [<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>],
  <span class="hljs-number">1</span>: [<span class="hljs-number">0</span>,<span class="hljs-number">2</span>,<span class="hljs-number">4</span>,<span class="hljs-number">5</span>,<span class="hljs-number">6</span>],
  <span class="hljs-number">2</span>: [<span class="hljs-number">0</span>,<span class="hljs-number">1</span>,<span class="hljs-number">3</span>,<span class="hljs-number">4</span>],
  <span class="hljs-number">3</span>: [<span class="hljs-number">0</span>,<span class="hljs-number">2</span>,<span class="hljs-number">4</span>],
  <span class="hljs-number">4</span>: [<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>,<span class="hljs-number">6</span>,<span class="hljs-number">7</span>],
  <span class="hljs-number">5</span>: [<span class="hljs-number">1</span>,<span class="hljs-number">6</span>,<span class="hljs-number">7</span>,<span class="hljs-number">8</span>],
  <span class="hljs-number">6</span>: [<span class="hljs-number">1</span>,<span class="hljs-number">4</span>,<span class="hljs-number">5</span>,<span class="hljs-number">7</span>],
  <span class="hljs-number">7</span>: [<span class="hljs-number">4</span>,<span class="hljs-number">5</span>,<span class="hljs-number">6</span>],
  <span class="hljs-number">8</span>: [<span class="hljs-number">5</span>],
}

<span class="hljs-keyword">from</span> itertools <span class="hljs-keyword">import</span> product

mappings_cache = {}

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">generate_mappings</span>(<span class="hljs-params">fixed, variable</span>):</span>
  <span class="hljs-keyword">if</span> any(idx <span class="hljs-keyword">in</span> fixed <span class="hljs-keyword">for</span> idx <span class="hljs-keyword">in</span> variable):
    <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">"Variable indices contain already fixed indices."</span>)

  key = (tuple(sorted(variable)), tuple(sorted(fixed.items())))
  <span class="hljs-keyword">if</span> key <span class="hljs-keyword">in</span> mappings_cache:
    <span class="hljs-keyword">return</span> mappings_cache[key]

  all_digits = list(range(<span class="hljs-number">1</span>, <span class="hljs-number">10</span>))
  results = []

  <span class="hljs-keyword">for</span> digits <span class="hljs-keyword">in</span> product(all_digits, repeat=len(variable)):
    candidate = dict(zip(variable, digits))
    candidate.update(fixed)

    valid = <span class="hljs-literal">True</span>
    <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> candidate:
      <span class="hljs-keyword">for</span> j <span class="hljs-keyword">in</span> candidate:
        <span class="hljs-keyword">if</span> i != j:
          <span class="hljs-keyword">if</span> (j <span class="hljs-keyword">in</span> difference_mapping.get(i, [])) <span class="hljs-keyword">or</span> (i <span class="hljs-keyword">in</span> difference_mapping.get(j, [])):
            <span class="hljs-keyword">if</span> candidate[i] == candidate[j]:
              valid = <span class="hljs-literal">False</span>
              <span class="hljs-keyword">break</span>
      <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> valid:
          <span class="hljs-keyword">break</span>

    <span class="hljs-keyword">if</span> valid:
      results.append(candidate)

  mappings_cache[key] = results
  <span class="hljs-keyword">return</span> results
</code></pre>
<p>This is a function for generating mapping from region indices to numbers 1-9 respecting the difference rule and allowing for some indices to be fixed.</p>
<h3 id="heading-solving-a-single-number">Solving a single number</h3>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">solve_number</span>(<span class="hljs-params">board, number_indices, number_index, row_index, condition, down</span>):</span>
  <span class="hljs-string">"""
  Tries every possible tile state for a given number section in a row and returns
  a list of boards that satisfy the condition.

  Args:
    board         : The current board.
    number_indices: A list of dictionaries with start and end indices for numbers.
    number_index  : The index of the current number in number_indices.
    row_index     : The row in which the number is located.
    condition     : A function that checks if the number is valid.
    down          : A boolean flag to inform tile placement direction.

  Returns:
    A list of boards with valid distributions or None if number_index is out of range.
  """</span>
  <span class="hljs-keyword">if</span> number_index &gt;= len(number_indices):
    <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>

  <span class="hljs-comment"># Determine the section of the number we are going to solve.</span>
  current_number_range = number_indices[number_index]
  number_start = current_number_range[<span class="hljs-string">"start"</span>]
  number_end = current_number_range[<span class="hljs-string">"end"</span>]

  solutions = []

  <span class="hljs-comment"># Determine tile positions that may affect this number.</span>
  block_locations = find_block_locations_for_number(board, number_start, number_end, row_index, down)
  replaced_numbers = extract_remaining_numbers(board, block_locations)

  current_solving_number = board.get_section(row_index, number_start, number_end)
  possible_tile_states = nested_count(replaced_numbers)
  mapping = map_block_locations_to_number(board, row_index, block_locations, number_start, number_end)

  <span class="hljs-keyword">for</span> tile_state <span class="hljs-keyword">in</span> possible_tile_states:
    candidate_number = current_solving_number.copy()
    invalid_state = <span class="hljs-literal">False</span>

    <span class="hljs-comment"># Apply each tile state increment to the candidate number.</span>
    <span class="hljs-keyword">for</span> idx, tile_value <span class="hljs-keyword">in</span> enumerate(tile_state):
      num_index = mapping[idx][<span class="hljs-string">"num_index"</span>]
      index_into_row = number_start + num_index

      <span class="hljs-keyword">if</span> board.is_locked_space(row_index, index_into_row):
        invalid_state = <span class="hljs-literal">True</span>
        <span class="hljs-keyword">break</span>

      candidate_number[num_index] += tile_value
      <span class="hljs-keyword">if</span> candidate_number[num_index] &gt; <span class="hljs-number">9</span>:
        invalid_state = <span class="hljs-literal">True</span>
        <span class="hljs-keyword">break</span>

    <span class="hljs-keyword">if</span> invalid_state:
      <span class="hljs-keyword">continue</span>

    <span class="hljs-comment"># If candidate number meets the condition, try to distribute the tiles.</span>
    <span class="hljs-keyword">if</span> condition(candidate_number):
      sol_board = board.copy()
      distribution_error = <span class="hljs-literal">False</span>

      <span class="hljs-keyword">for</span> idx, tile_value <span class="hljs-keyword">in</span> enumerate(tile_state):
        tile_x, tile_y = mapping[idx][<span class="hljs-string">"coords"</span>]
        direction = mapping[idx][<span class="hljs-string">"direction"</span>]
        num_index = mapping[idx][<span class="hljs-string">"num_index"</span>]
        index_into_row = number_start + num_index

        <span class="hljs-keyword">if</span> board.is_locked_space(row_index, index_into_row):
          distribution_error = <span class="hljs-literal">True</span>
          <span class="hljs-keyword">break</span>

        sol_board.set_distribution(tile_x, tile_y, direction, tile_value)

      <span class="hljs-keyword">if</span> distribution_error:
        <span class="hljs-keyword">continue</span>

      <span class="hljs-comment"># If this is the last number in the row, mark the row solved and update locked tiles.</span>
      <span class="hljs-keyword">if</span> number_index == len(number_indices) - <span class="hljs-number">1</span>:
        sol_board.solved[row_index] = <span class="hljs-literal">True</span>
        <span class="hljs-keyword">for</span> tile_idx, tile <span class="hljs-keyword">in</span> enumerate(sol_board.tile_board[row_index]):
          <span class="hljs-keyword">if</span> tile == <span class="hljs-number">1</span>:
            tile_info = sol_board.tile_info_board[row_index][tile_idx]
            <span class="hljs-keyword">if</span> tile_info[<span class="hljs-string">"remaining"</span>] != <span class="hljs-number">0</span>:
              <span class="hljs-keyword">try</span>:
                direction = <span class="hljs-string">"down"</span> <span class="hljs-keyword">if</span> down <span class="hljs-keyword">else</span> <span class="hljs-string">"up"</span>
                sol_board.set_distribution(row_index, tile_idx, direction, tile_info[<span class="hljs-string">"remaining"</span>])
              <span class="hljs-keyword">except</span> Exception:
                sol_board.solved[row_index] = <span class="hljs-literal">False</span>
                distribution_error = <span class="hljs-literal">True</span>
                <span class="hljs-keyword">break</span>

      <span class="hljs-keyword">if</span> distribution_error:
        <span class="hljs-keyword">continue</span>

      solutions.append(sol_board)

  <span class="hljs-keyword">return</span> solutions
</code></pre>
<p>This function is responsible for solving a number within a row. It does this by finding the bounds of the number and figuring out which tiles affect which digits. Subsequently it tries all possible combinations of the tiles until the given condition is met.</p>
<h3 id="heading-solving-a-row">Solving a row</h3>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">solve_row</span>(<span class="hljs-params">board, number_indices, row_index, current_number_index, condition, down</span>):</span>
  boards = solve_number(board, number_indices, current_number_index, row_index, condition, down)

  <span class="hljs-keyword">if</span> boards == <span class="hljs-literal">None</span>:
    <span class="hljs-keyword">return</span> []
  <span class="hljs-keyword">if</span> len(boards) == <span class="hljs-number">0</span>:
    <span class="hljs-keyword">return</span> []

  results = []

  <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(len(boards)):
    b = boards[i]

    <span class="hljs-keyword">if</span> b.solved[row_index]:
      numbers = []

      rules_satisfied = <span class="hljs-literal">True</span>


      <span class="hljs-comment"># Check if there are no duplicate numbers in any solved row</span>
      <span class="hljs-keyword">for</span> index <span class="hljs-keyword">in</span> range(len(b.number_board)):
        <span class="hljs-keyword">if</span> b.solved[index] == <span class="hljs-literal">False</span>:
          <span class="hljs-keyword">continue</span>


        _number_indices = b.row_get_number_indices(index)

        <span class="hljs-keyword">for</span> j <span class="hljs-keyword">in</span> range(len(_number_indices)):
          number = b.get_number(index, _number_indices[j][<span class="hljs-string">"start"</span>], _number_indices[j][<span class="hljs-string">"end"</span>])
          <span class="hljs-keyword">if</span> number <span class="hljs-keyword">in</span> numbers:
            rules_satisfied = <span class="hljs-literal">False</span>
          numbers.append(number)

      <span class="hljs-keyword">for</span> j <span class="hljs-keyword">in</span> range(len(b.solved)):
        <span class="hljs-comment"># Check if there are no unsatisfied tiles in the already solved rows</span>
        <span class="hljs-keyword">if</span> b.solved[j] == <span class="hljs-literal">True</span> <span class="hljs-keyword">and</span> j != row_index:
          <span class="hljs-keyword">for</span> x <span class="hljs-keyword">in</span> range(len(b.tile_info_board[j])):
            <span class="hljs-keyword">if</span> b.tile_board[j][x] == <span class="hljs-number">1</span>:
              <span class="hljs-keyword">if</span> b.tile_info_board[j][x][<span class="hljs-string">"remaining"</span>] != <span class="hljs-number">0</span>:
                rules_satisfied = <span class="hljs-literal">False</span>
                <span class="hljs-keyword">raise</span> Exception(<span class="hljs-string">"Tile is not satisfied"</span>)

      <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> rules_satisfied:
        <span class="hljs-keyword">continue</span>

      results.append(b)
    <span class="hljs-keyword">else</span>:
      results += solve_row(b, number_indices, row_index, current_number_index + <span class="hljs-number">1</span>, condition, down)
  <span class="hljs-keyword">return</span> results
</code></pre>
<p>This function is used for solving a row. It works by calling solve_number until the row is solved. All solved boards are returned as results if the board has no unsatisfied tiles and no duplicates.</p>
<h2 id="heading-solution-1">Solution:</h2>
<pre><code class="lang-plaintext">10: 🟨3️⃣4️⃣2️⃣2️⃣2️⃣5️⃣🟨3️⃣2️⃣4️⃣
09: 5️⃣4️⃣🟨2️⃣2️⃣5️⃣🟨2️⃣5️⃣2️⃣🟨
08: 🟨5️⃣3️⃣4️⃣3️⃣🟨6️⃣5️⃣🟨2️⃣6️⃣
07: 7️⃣3️⃣6️⃣🟨3️⃣2️⃣🟨5️⃣4️⃣4️⃣🟨
06: 🟨5️⃣5️⃣5️⃣🟨2️⃣2️⃣🟨8️⃣1️⃣6️⃣
05: 5️⃣5️⃣🟨5️⃣5️⃣1️⃣🟨1️⃣5️⃣5️⃣🟨
04: 🟨3️⃣6️⃣7️⃣4️⃣4️⃣1️⃣2️⃣🟨1️⃣5️⃣
03: 7️⃣3️⃣7️⃣🟨9️⃣6️⃣9️⃣🟨9️⃣9️⃣🟨
02: 3️⃣4️⃣🟨4️⃣6️⃣3️⃣6️⃣8️⃣🟨8️⃣9️⃣
01: 5️⃣3️⃣5️⃣9️⃣3️⃣🟨5️⃣9️⃣9️⃣5️⃣🟨
00: 🟨4️⃣7️⃣🟨8️⃣8️⃣7️⃣🟨4️⃣3️⃣3️⃣

10: 342225 + 324
09: 54 + 225 + 252
08: 5343 + 65 + 26
07: 736 + 32 + 544
06: 555 + 22 + 816
05: 55 + 551 + 155
04: 3674412 + 15
03: 737 + 969 + 99
02: 34 + 46368 + 89
01: 53593 + 5995
00: 47 + 887 + 433

342225 + 324 + 54 + 225 + 252 + 5343 + 65 + 26 + 736 + 32 + 544 + 555 + 22 + 816 + 55 + 551 + 155 + 3674412 + 15 + 737 + 969 + 99 + 34 + 46368 + 89 + 53593 + 5995 + 47 + 887 + 433
</code></pre>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">4135658</div>
</div>

<h2 id="heading-source-code">Source Code:</h2>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="266b70900279cbe4b64652c87d6ea156"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/Addelec/266b70900279cbe4b64652c87d6ea156" class="embed-card">https://gist.github.com/Addelec/266b70900279cbe4b64652c87d6ea156</a></div><p> </p>
<p><a target="_blank" href="https://gist.github.com/Addelec/266b70900279cbe4b64652c87d6ea156">https://gist.github.com/Addelec/266b70900279cbe4b64652c87d6ea156</a></p>
]]></content:encoded></item><item><title><![CDATA[Jane Street puzzle writeup: Sum One, Somewhere]]></title><description><![CDATA[I really enjoyed working on this months puzzle which involved labeling nodes in an infinite binary tree (0 with the probability of p and 1 otherwise).
The goal was to find the value of p so the probability of an infinite path along the binary tree (w...]]></description><link>https://korff.dev/jane-street-puzzle-writeup-sum-one-somewhere</link><guid isPermaLink="true">https://korff.dev/jane-street-puzzle-writeup-sum-one-somewhere</guid><category><![CDATA[janestreet]]></category><category><![CDATA[puzzle]]></category><category><![CDATA[puzzle-solving]]></category><category><![CDATA[Write Up]]></category><dc:creator><![CDATA[David]]></dc:creator><pubDate>Thu, 01 May 2025 14:00:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1746115036032/8e073471-7028-45ae-b272-85633a80afc7.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I really enjoyed working on this months puzzle which involved labeling nodes in an infinite binary tree (0 with the probability of p and 1 otherwise).</p>
<p>The goal was to find the value of p so the probability of an infinite path along the binary tree (with at most one 1) existing is 1/2.</p>
<h2 id="heading-solution">Solution:</h2>
<p>I started out by defining f(o) as the probability that a binary tree has at most o ones.</p>
<p>Now there are two relevant cases to consider:</p>
<h3 id="heading-case-1-o-0-no-extra-1s-allowed">Case 1: o = 0 (no extra 1’s allowed)</h3>
<p>For an infinite path to exist in this case the current node must be 0, which occurs with probability p. Additionally at least one of the two sub-trees must have an infinite path of 0's (or equivalently not both of them should have no path).</p>
<p>$$f_p(0) = p \cdot (1 - (1 - f_p(0))^2)$$</p><p>Simplifying this equation by dividing by f(0) yields:</p>
<p>$$f_p(0) = \frac{2p-1}{p}$$</p><h3 id="heading-case-2-o-1-at-most-one-1-allowed">Case 2: o = 1 (at most one 1 allowed)</h3>
<p>Now there are two more sub-cases depending on if the current node is a 0 or 1:</p>
<ul>
<li><p>If it's a 0, there is still have one 1 left.</p>
</li>
<li><p>If it's a 1, we need to continue using f(0).</p>
</li>
</ul>
<p>A possible equation modelling this behavior is:</p>
<p>$$f_p(1) = p \cdot (1 - (1 - f_p(1))^2) + (1-p) \cdot (1 - (1 - f_p(0))^2)$$</p><p>Since f(1) should be 1/2 I simply replaced each occurrence of f(1) with 1/2.</p>
<p>$$\frac12 = p \cdot (1-(1-\frac12)^2)+(1-p)\cdot (1-(1-f_p(0))^2)$$</p><p>Solving results in this cubic equation:</p>
<p>$$0= \frac34 p^3 - \frac52 p^2+3p-1$$</p><p>After solving for p, the first 10 decimal digits are: 0.5306035754.</p>
<p>I had a great time working on this puzzle, and I am looking forward to the next one!</p>
]]></content:encoded></item><item><title><![CDATA[Jane Street puzzle writeup: Hall of Mirrors 3]]></title><description><![CDATA[After solving the last puzzle just in the nick in time, I was eager to dive into the next one. Fortunately, this one turned out to be much more straightforward.
I started by building a visualizer, to better understand how the puzzle functioned. After...]]></description><link>https://korff.dev/jane-street-puzzle-writeup-hall-of-mirrors-3</link><guid isPermaLink="true">https://korff.dev/jane-street-puzzle-writeup-hall-of-mirrors-3</guid><category><![CDATA[puzzle]]></category><category><![CDATA[puzzles]]></category><category><![CDATA[puzzle-solving]]></category><category><![CDATA[Write Up]]></category><category><![CDATA[janestreet]]></category><dc:creator><![CDATA[David]]></dc:creator><pubDate>Tue, 01 Apr 2025 11:30:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1741095667376/f71a2f0c-fa64-4ad1-9415-7e5745b47bdb.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>After solving the last puzzle just in the nick in time, I was eager to dive into the next one. Fortunately, this one turned out to be much more straightforward.</p>
<p>I started by building a visualizer, to better understand how the puzzle functioned. After playing around I realized the puzzle could most likely be solved by hand, so I enhanced the tool with features like automatic length counting to streamline the process.</p>
<p>I started by solving the small numbers in the top right corner and the 5 at the bottom, as they had only one valid configuration. Since 5 is a prime number and 1 forces the mirror to be in the top right corner, this limits where the mirror for the number 4 can go, as mirrors can't be placed in orthogonally adjacent cells. From there, I focused on the larger numbers, using prime factorization to figure out the needed segment lengths.</p>
<p>For example, 3087 = 3 × 3 × 7 × 7 × 7, meaning I needed either two 3-segments (or one 9-segment) and three 7-segments.</p>
<p>I worked on fitting the segments into the grid while resolving any conflicts. Additionally I tried to use as few mirrors as possible</p>
<p>Two hours of experimenting later, I found the solution and submitted it within the first 24 hours of the puzzle’s release.</p>
<p>I've attached the visualizer with the solution below.</p>
<iframe src="https://static.korff.dev/blog/janestreet-hall-of-mirrors-3/" width="600px" height="600px" style="display:block;transform:translate(15%, 0%)"></iframe>

<ul>
<li><p>ENTER = Show Solution</p>
</li>
<li><p>Click on a number = Enable/Disable Laser</p>
</li>
<li><p>Click on a space = Cycle through mirror options</p>
</li>
</ul>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">I recommend only turning on a few lasers at a time, as things can quickly get chaotic. Also there is no logic for turning off connected lasers.</div>
</div>]]></content:encoded></item><item><title><![CDATA[Jane Street puzzle writeup: Top Score (Give or Take)]]></title><description><![CDATA[Starting out this puzzle proved to be far more challenging than its predecessors. Giving no direct clues apart form the title.
This article outlines the process we followed in solving this intricate puzzle.
Naive beginnings
Starting out we simply ass...]]></description><link>https://korff.dev/jane-street-puzzle-writeup-top-score-give-or-take</link><guid isPermaLink="true">https://korff.dev/jane-street-puzzle-writeup-top-score-give-or-take</guid><category><![CDATA[janestreet]]></category><category><![CDATA[puzzles]]></category><category><![CDATA[Write Up]]></category><dc:creator><![CDATA[David]]></dc:creator><pubDate>Sat, 01 Mar 2025 20:08:16 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1740601770157/2534e270-89f2-4bc5-9c55-95ceea77233d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Starting out this puzzle proved to be far more challenging than its predecessors. Giving no direct clues apart form the title.</p>
<p>This article outlines the process we followed in solving this intricate puzzle.</p>
<h2 id="heading-naive-beginnings">Naive beginnings</h2>
<p>Starting out we simply assumed that the numbers meant, we had to order the words in the two respective groups by some other metric. Since they were all alphabetically ordered which usually is a clue towards finding a different order.</p>
<p>So we thought about different ways to <strong>score</strong> words instinctively we looked at scrabble scoring and wrote a quick script to order the words by their scrabble score and replace the numbers with the respective words. This however proved to be a wrong assumption since multiple words had the same score so no absolute order could be determined.</p>
<h2 id="heading-analyzing-the-words">Analyzing the Words</h2>
<p>Our next approach was to simply look at and rearrange the words in hopes that some pattern might become evident. This however was not the case. We did notice that paring up the words of the two groups no words had the same length.</p>
<p>We tried removing the amount of letters of the shorter word from the longer one:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740497169132/0ee52d22-b9c5-44c7-9e3c-b154a1d86452.png" alt class="image--center mx-auto" /></p>
<p>But as you may see this didn’t accomplish anything. Removing letters the words had in common also didn’t help.</p>
<h2 id="heading-finally-looking-at-the-title">Finally looking at the title</h2>
<p>After hitting roadblock after roadblock we came to the conclusion that our approach was wrong. So we decided to divert more focus toward the title. Specifically the “Give or Take“ part. We were aware that “score” could also have different meanings (e.g. “film music”) however we didn’t yet see how this could be significant.</p>
<p>So we wrote another script to generate words one letter away from our word. We did this by using a list of English words which we subsequently normalized (sorting the letters of each word alphabetically) to achieve a lookup table of letters to possible words.</p>
<p>We than removed a letter form the word at every position and appended a letter to the word.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">normalize_word</span>(<span class="hljs-params">word</span>):</span>
  <span class="hljs-keyword">return</span> <span class="hljs-string">''</span>.join(sorted(word))

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">find_words_with_distance_1</span>(<span class="hljs-params">target, word_list</span>):</span>
    <span class="hljs-comment"># Create dict</span>
    word_dict = {}
    <span class="hljs-keyword">for</span> word <span class="hljs-keyword">in</span> word_list:
        normalized = normalize_word(word)
        <span class="hljs-keyword">if</span> normalized <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> word_dict.keys():
            word_dict[normalized] = set()
        word_dict[normalized].add(word)

    word_chars = list(normalize_word(target))
    possible_words = set()

    <span class="hljs-comment"># Delete Letters</span>
    <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(len(word_chars)):
        modified = word_chars[:i] + word_chars[i+<span class="hljs-number">1</span>:]
        key = <span class="hljs-string">''</span>.join(modified)
        <span class="hljs-keyword">if</span> key <span class="hljs-keyword">in</span> word_dict:
            possible_words.update(word_dict[key])

    <span class="hljs-comment"># Insert letters</span>
    <span class="hljs-keyword">for</span> letter <span class="hljs-keyword">in</span> <span class="hljs-string">'abcdefghijklmnopqrstuvwxyz'</span>:
        <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(len(word_chars) + <span class="hljs-number">1</span>):
            modified = word_chars[:i] + [letter] + word_chars[i:]
            key = normalize_word(modified)
            <span class="hljs-keyword">if</span> key <span class="hljs-keyword">in</span> word_dict:
                possible_words.update(word_dict[key])

    <span class="hljs-keyword">return</span> possible_words
</code></pre>
<p>This left us with a giant list of words. So time to simplify.</p>
<h3 id="heading-attempt-one-using-scrabble-scoring">Attempt One - Using scrabble scoring</h3>
<p>Our first attempt to narrow down the list involved using Scrabble scores to prioritize the highest-scoring words. However, this was a flawed strategy since longer words (especially those containing "Q" and "Z") were disproportionately favored, making it ineffective for identifying a "taking" element.</p>
<h3 id="heading-attempt-two-recognizing-an-obscure-pattern">Attempt Two - Recognizing an obscure Pattern</h3>
<p>Late at night I was staring at the google sheet we set up for this puzzle when suddenly I had a epiphany. I saw that one could convert the word “premise” to “<strong>empire</strong>” and “stickers“ to “<strong>strikes</strong>”. My mind auto completed “The empire strikes back”. Which I first dismissed as a funny accident, later I saw that is was also possible to build more movie titles like “<strong>Star Wars</strong>“ and “<strong>Raiders</strong> of the <strong>lost ark</strong>“. This is were we remembered that we discussed “Top score” possibly meaning something else.</p>
<p>So we tried matching films to the “key” by firstly ignoring the numbers and using them only as placeholders. We later realized that all films fitting the key are listed within the <a target="_blank" href="https://en.wikipedia.org/wiki/List_of_compositions_by_John_Williams">Compositions by John Williams Article</a>. So we were soon able to complete the key.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740508626244/fa01177d-00c2-48fc-ba34-d0dee22723c0.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740508605018/fd013ac3-bf95-42d0-8079-5bb846a162eb.png" alt class="image--center mx-auto" /></p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">So we have a list of movies. What is the solution?</div>
</div>

<p>We noticed that the letters removed and added were identical when the two words shared the same number. This led us to consider whether the number represented the index of the added or removed letter within a specific phrase. This resulted in:</p>
<p><strong>F I L M C O M P O S E R J W</strong></p>
<p>This refers to <strong>John Williams</strong>, the composer behind the <strong>scores</strong> of these films. His name is a proper noun and also the solution to this puzzle.</p>
]]></content:encoded></item></channel></rss>