
    
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
            
{"version":"https:\/\/jsonfeed.org\/version\/1","title":"mathspp.com feed","home_page_url":"https:\/\/mathspp.com\/blog\/tags\/algorithms","feed_url":"https:\/\/mathspp.com\/blog\/tags\/algorithms.json","description":"Stay up-to-date with the articles on mathematics and programming that get published to mathspp.com.","author":{"name":"Rodrigo Gir\u00e3o Serr\u00e3o"},"items":[{"title":"Remove extra spaces","date_published":"2026-03-02T13:39:00+01:00","id":"https:\/\/mathspp.com\/blog\/remove-extra-spaces","url":"https:\/\/mathspp.com\/blog\/remove-extra-spaces","content_html":"<p>Learn how to remove extra spaces from a string using regex, string splitting, a fixed point, and <code>itertools.groupby<\/code>.<\/p>\n\n<p>In this article you'll learn about three different ways in which you can remove extra spaces from the middle of a string.\nThat is, you'll learn how to go from a string like<\/p>\n<pre><code class=\"language-py\">string = \"This is  a   perfectly    normal     sentence.\"<\/code><\/pre>\n<p>to a string like<\/p>\n<pre><code class=\"language-py\">string = \"This is a perfectly normal sentence.\"<\/code><\/pre>\n<h2 id=\"the-best-solution-to-remove-extra-spaces-from-a-string\">The best solution to remove extra spaces from a string<a href=\"#the-best-solution-to-remove-extra-spaces-from-a-string\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>The best solution for this task, which is both readable and performant, uses the regex module <code>re<\/code>:<\/p>\n<pre><code class=\"language-py\">import re\n\ndef remove_extra_spaces(string):\n    return re.sub(\" {2,}\", \" \", string)<\/code><\/pre>\n<p>The function <code>sub<\/code> can be used to <strong>sub<\/strong>stitute a pattern for a replacement you specify.\nThe pattern <code>\" {2,}\"<\/code> finds runs of 2 or more consecutive spaces and replaces them with a single space.<\/p>\n<h2 id=\"string-splitting\">String splitting<a href=\"#string-splitting\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>Using the string method <code>split<\/code> can also be a good approach:<\/p>\n<pre><code class=\"language-py\">def remove_extra_spaces(string):\n    return \" \".join(string.split(\" \"))<\/code><\/pre>\n<p>If you're using string splitting, you'll want to provide the space <code>\" \"<\/code> as an argument.\nIf you call <code>split<\/code> with no arguments, you'll be splitting on <em>all<\/em> whitespace, which is not what you want if you have newlines and other whitespace characters you should preserve.<\/p>\n<p>This solution is great, except it doesn't work:<\/p>\n<pre><code class=\"language-py\">print(remove_extra_spaces(string))\n# 'This is  a   perfectly    normal     sentence.'<\/code><\/pre>\n<p>The problem is that splitting on the space will produce a list with empty strings:<\/p>\n<pre><code class=\"language-py\">print(string.split(\" \"))\n# ['This', 'is', '', 'a', '', '', 'perfectly', '', '', '', 'normal', '', '', '', '', 'sentence.']<\/code><\/pre>\n<p>These empty strings will be joined back together and you'll end up with the same string you started with.\nFor this to work, you'll have to filter the empty strings first:<\/p>\n<pre><code class=\"language-py\">def remove_extra_spaces(string):\n    return \" \".join(filter(None, string.split(\" \")))<\/code><\/pre>\n<p>Using <code>filter(None, ...)<\/code> filters out the <a href=\"\/blog\/pydonts\/truthy-falsy-and-bool\">Falsy<\/a> strings, so that the final joining operation only joins the strings that matter.<\/p>\n<p>This solution has a problem, though, in that it will completely remove any leading or trailing whitespace, which may or may not be a problem.<\/p>\n<p>The two solutions presented so far &mdash; using regular expressions and string splitting &mdash; are pretty reasonable.\nBut they're also boring.\nYou'll now learn about two other solutions.<\/p>\n<h2 id=\"replacing-spaces-until-you-hit-a-fixed-point\">Replacing spaces until you hit a fixed point<a href=\"#replacing-spaces-until-you-hit-a-fixed-point\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>You can think about the task of removing extra spaces as the task of replacing extra spaces by the empty string.\nAnd if you think about doing string replacements, you should think about the string method <code>replace<\/code>.<\/p>\n<p>You can't do something like <code>string.replace(\" \", \"\")<\/code>, otherwise you'd remove <em>all<\/em> spaces, so you have to be a bit more careful:<\/p>\n<pre><code class=\"language-py\">def remove_extra_spaces(string):\n    while True:\n        new_string = string.replace(\"  \", \" \")\n        if new_string == string:\n            break\n        string = new_string\n    return string<\/code><\/pre>\n<p>You can replace two consecutive spaces by a single space, and you repeat this operation until nothing changes in your string.<\/p>\n<p>The idea of running a function until its output doesn't change is common enough in maths that they call...<\/p>","summary":"Learn how to remove extra spaces from a string using regex, string splitting, a fixed point, and itertools.groupby.","date_modified":"2026-03-02T16:25:02+01:00","tags":["programming","python","algorithms","regex"],"image":"\/user\/pages\/02.blog\/remove-extra-spaces\/thumbnail.webp"},{"title":"Generalising itertools.pairwise","date_published":"2025-11-24T19:36:00+01:00","id":"https:\/\/mathspp.com\/blog\/generalising-itertools-pairwise","url":"https:\/\/mathspp.com\/blog\/generalising-itertools-pairwise","content_html":"<p>In this article you will learn about itertools.pairwise, how to use it, and how to generalise it.<\/p>\n\n<p>In this tutorial you will learn to use and generalise <code>itertools.pairwise<\/code>.\nYou will understand what <code>itertools.pairwise<\/code> does, how to use it, and how to implement a generalised version for when <code>itertools.pairwise<\/code> isn't enough.<\/p>\n<h2 id=\"itertools-pairwise\"><code>itertools.pairwise<\/code><a href=\"#itertools-pairwise\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p><code>itertools.pairwise<\/code> is an iterable from the standard module <code>itertools<\/code> that lets you access overlapping pairs of consecutive elements of the input iterable.\nThat's quite a mouthful, so let me translate:<\/p>\n<blockquote>\n<p>You give <code>pairwise<\/code> an iterable, like <code>\"ABCD\"<\/code>, and <code>pairwise<\/code> gives you pairs back, like <code>(\"A\", \"B\")<\/code>, <code>(\"B\", \"C\")<\/code>, and <code>(\"C\", \"D\")<\/code>.<\/p>\n<\/blockquote>\n<p>In loops, it is common to unpack the pairs directly to perform some operation on both values.\nThe example below uses <code>pairwise<\/code> to determine how the balance of a bank account changed based on the balance history:<\/p>\n<pre><code class=\"language-py\">from itertools import pairwise  # Python 3.10+\n\nbalance_history = [700, 1000, 800, 750]\n\nfor before, after in pairwise(balance_history):\n    change = after - before\n    print(f\"Balance changed by {change:+}.\")<\/code><\/pre>\n<pre><code class=\"language-txt\">Balance changed by +300.\nBalance changed by -200.\nBalance changed by -50.<\/code><\/pre>\n<h2 id=\"how-to-implement-pairwise\">How to implement <code>pairwise<\/code><a href=\"#how-to-implement-pairwise\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>If you had to implement <code>pairwise<\/code>, you might think of something like the code below:<\/p>\n<pre><code class=\"language-py\">def my_pairwise(iterable):\n    for prev_, next_ in zip(iterable, iterable[1:]):\n        yield (prev_, next_)<\/code><\/pre>\n<p>Which directly translates to<\/p>\n<pre><code class=\"language-py\">def my_pairwise(iterable):\n    yield from zip(iterable, iterable[1:])<\/code><\/pre>\n<p>But there is a problem with this implementation, and that is the slicing operation.\n<code>pairwise<\/code> is supposed to work with any iterable and not all iterables are sliceable.\nFor example, files are iterables but are not sliceable.<\/p>\n<p>There are a couple of different ways to fix this but my favourite uses <a href=\"https:\/\/mathspp.com\/blog\/python-deque-tutorial#implement-itertools-pairwise\"><code>collections.deque<\/code> with its parameter <code>maxlen<\/code><\/a>:<\/p>\n<pre><code class=\"language-py\">from collections import deque\nfrom itertools import islice\n\ndef my_pairwise(data):\n    data = iter(data)\n    window = deque(islice(data, 1), maxlen=2)\n    for value in data:\n        window.append(value)\n        yield tuple(window)<\/code><\/pre>\n<h2 id=\"generalising-itertools-pairwise\">Generalising <code>itertools.pairwise<\/code><a href=\"#generalising-itertools-pairwise\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p><code>pairwise<\/code> will always produce <strong>pairs<\/strong> of consecutive elements, but sometimes you might want tuples of different sizes.\nFor example, you might want something like &ldquo;<code>triplewise<\/code>&rdquo;, to get triples of consecutive elements, but <code>pairwise<\/code> can't be used for that.\nSo, how do you implement that generalisation?<\/p>\n<p>In the upcoming subsections I will present different ways of implementing the function <code>nwise(iterable, n)<\/code> that accepts an iterable and a positive integer <code>n<\/code> and produces overlapping tuples of <code>n<\/code> elements taken from the given iterable.<\/p>\n<p>Some example applications:<\/p>\n<pre><code class=\"language-py\">nwise(\"ABCD\", 2) -&gt; (\"A\", \"B\"), (\"B\", \"C\"), (\"C\", \"D\")\nnwise(\"ABCD\", 3) -&gt; (\"A\", \"B\", \"C\"), (\"B\", \"C\", \"D\")\nnwise(\"ABCD\", 4) -&gt; (\"A\", \"B\", \"C\", \"D\")<\/code><\/pre>\n<h3 id=\"using-deque\">Using <code>deque<\/code><a href=\"#using-deque\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h3>\n<p>The implementation of <code>pairwise<\/code> that I showed above can be adapted for <code>nwise<\/code>:<\/p>\n<pre><code class=\"language-py\">from collections import deque\nfrom itertools import islice\n\ndef nwise(iterable, n):\n    iterable = iter(iterable)\n    window = deque(islice(iterable, n - 1), maxlen=n)\n    for value in iterable:\n        window.append(value)\n        yield tuple(window)<\/code><\/pre>\n<p>Note that you have to change <code>maxlen=2<\/code> to <code>maxlen=n<\/code>, but also <code>islice(iterable, 1)<\/code> to <code>islice(iterable, n - 1)<\/code>.<\/p>\n<h3 id=\"using-tee\">Using <code>tee<\/code><a href=\"#using-tee\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h3>\n<p>Another fundamentally different way of implement <code>nwise<\/code> is by using <code>itertools.tee<\/code> to split the input...<\/p>","summary":"In this article you will learn about itertools.pairwise, how to use it, and how to generalise it.","date_modified":"2025-12-03T10:07:01+01:00","tags":["programming","python","algorithms"],"image":"\/user\/pages\/02.blog\/generalising-itertools-pairwise\/thumbnail.webp"},{"title":"Floodfill algorithm in Python","date_published":"2025-11-17T16:49:00+01:00","id":"https:\/\/mathspp.com\/blog\/floodfill-algorithm-in-python","url":"https:\/\/mathspp.com\/blog\/floodfill-algorithm-in-python","content_html":"<p>Learn how to implement and use the floodfill algorithm in Python.<\/p>\n\n<link rel=\"stylesheet\" href=\"https:\/\/pyscript.net\/releases\/2025.11.1\/core.css\"><script defer type=\"module\" src=\"https:\/\/pyscript.net\/releases\/2025.11.1\/core.js\"><\/script><py-script>\nimport js\n\nroot = js.document.documentElement\ncomputed = js.window.getComputedStyle(root)\n\nBG_COLOUR = computed.getPropertyValue(\"--bg\").strip()\nFG_COLOUR = computed.getPropertyValue(\"--tx\").strip()\nUI_COLOUR = computed.getPropertyValue(\"--ui\").strip()\nAC_COLOUR = computed.getPropertyValue(\"--accent\").strip()\nAC2_COLOUR = computed.getPropertyValue(\"--accent-2\").strip()\nRE_COLOUR = computed.getPropertyValue(\"--re\").strip()\nBL_COLOUR = computed.getPropertyValue(\"--bl\").strip()\nGR_COLOUR = computed.getPropertyValue(\"--gr\").strip()\nYE_COLOUR = computed.getPropertyValue(\"--ye\").strip()\nOR_COLOUR = computed.getPropertyValue(\"--or\").strip()\n\nCONTRAST = {\n    BG_COLOUR: FG_COLOUR,\n    FG_COLOUR: BG_COLOUR,\n    UI_COLOUR: FG_COLOUR,\n    AC_COLOUR: FG_COLOUR,\n    AC2_COLOUR: FG_COLOUR,\n    RE_COLOUR: FG_COLOUR,\n    BL_COLOUR: FG_COLOUR,\n    GR_COLOUR: FG_COLOUR,\n    YE_COLOUR: FG_COLOUR,\n    OR_COLOUR: FG_COLOUR,\n}\n<\/py-script><p>In this article you will learn about the floodfill algorithm.\nYou will learn the intuitive explanation of the algorithm, how it can be used to colour regions in images, and how to implement it in Python.\nYou will also see three example applications of the floodfill algorithm, with interactive demos and code.<\/p>\n<p>By the time you are finished reading this article, you will be able to apply the floodfill algorithm in your own projects and modify it or tweak it according to your needs and preferences.<\/p>\n<h2 id=\"what-is-the-floodfill-algorithm\">What is the floodfill algorithm?<a href=\"#what-is-the-floodfill-algorithm\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>Click the image below to randomly colour the region you click.<\/p>\n<p>Go ahead, try it!<\/p>\n<canvas id=\"bitmap\" width=\"320\" height=\"320\" style=\"display: block; margin: 0 auto; max-width: 100%; height: auto;\"><\/canvas><script>\nfunction set_canvas_loading(canvas) {\n    let ctx = canvas.getContext(\"2d\");\n\n    \/\/ Get computed values of CSS variables\n    const styles = getComputedStyle(document.documentElement);\n    const bg = styles.getPropertyValue(\"--bg\").trim();\n    const fg = styles.getPropertyValue(\"--accent\").trim();\n\n    ctx.fillStyle = bg;\n    ctx.fillRect(0, 0, canvas.width, canvas.height);\n    ctx.fillStyle = fg;\n    ctx.font = \"36px Atkinson Hyperlegible\";\n    ctx.textAlign = \"center\";\n    ctx.textBaseline = \"middle\";\n    ctx.fillText(\"Loading...\", canvas.width \/ 2, canvas.height \/ 2);\n}\n\nset_canvas_loading(document.getElementById(\"bitmap\"));\n<\/script><py-script>\nIMG_WIDTH = 160\nIMG_HEIGHT = 160\nPIXEL_SIZE = 2\n\nimport asyncio\nimport collections\nimport itertools\n\nfrom pyscript import display\nfrom pyodide.ffi import create_proxy\nimport js\nfrom js import fetch\n\ncanvas = js.document.getElementById(\"bitmap\")\nctx = canvas.getContext(\"2d\")\n\n_BITMAP_COLOURS = itertools.cycle([AC_COLOUR, AC2_COLOUR, RE_COLOUR, BL_COLOUR, YE_COLOUR, GR_COLOUR, OR_COLOUR])\n\n_ints = [0, 0, 0, 0, 0, 0, 0, 9903520019135137019840167936, 316912650047833978337321025536, 5069364463233662545642129457152, 40406362882311561545666757918720, 159723975628759174402796798607360, 628754697713202062365541686837248, 1216944576219100292990829487718400, 2433889152438200467762168756961280, 4543259751217974183408433589387264, 9086519502435948354150493226795008, 18173039004871896701827061989244928, 15586952552243794457451031487840256, 36366340612306816504545604379082752, 31189423920820070440268979792510976, 31224838909463946599208478310400000, 31214663042341020773348807509278720, 31295792680755627454903859026067456, 31295772873714998888819460640079872, 31295772873714998888819460640079872, 31214663042341020773208070020923392, 31214658090580863631686970424426496, 31224838909463946599067740822044672, 31191949318500212615924220889661440, 31174023946731360309543681570897920, 31158772525447364424556924360458240, 31153781151208965771288531091587072, 31153781151208965771288531091587072, 31153781151208965771288531091587072, 31153781151208965771288531091587072, 31153781151208965771288531091587072, 31153781151208965771288531091587072, 41538374867674158118542209162674176, 41538374867674158118542209162674176, 41538374867674158118542209162674176, 1813388729527496878325760, 1813388729531894857728000, 5444517870735014810951084025942904930304, 87112285931760246042160989809567281971200, 347088014259357233337105009500967893204992, 1372018503425223884684326417278139134115840, 2613368577952807399398716985189229563609088, 5226737155905614798797433970265209426542592, 9756576024357147624421876744396907856986112, 19513152048714295248843753488680566015623168, 39026304097428590497687506977247882333241344, 78052608194857180995375013954382514968641536, 78052608194857180995375013954382514968649728, 156105216389714361990750027908651780239548416, 133804471191183738849214309636003418733572096, 312210432779428723981500055817190310781399040, 267608942382367477698428619271893587769440256, 624420865558857447963000111634267371865126912, 535217884764734955396857238543673925841197056, 535217884764734955396857238543673925841198080, 1248841731117714895926000223268421494032571392, 1070435769529469910793714477087375339473079296, 1070435769529469910793714477087375339473079296, 1070435769529469910793714477087339055589363200, 2497683462235429791852000446537115666948820480, 2497683462235429791852000446537045298204642816, 2140871539058939821587428954175243260155397632, 2140871539058939821587428954176226223550629376, 2140871539058939821587428954176243815736673792, 2140871539058939821587428954182717740201018880, 2140871539058939821587428954191192775827916288, 4995366924470859583704000893143091548121990912, 4995366924470859583704000893352579299538896640, 4995366924470859583704000897667150887862142720, 4995366924470900148523208196270458264975574784, 4995366924471184102257659318649205691078674176, 4995366924472147516713832774153879352074307328, 4995366924475889621285706507409303589789632256, 4995366924480596407964353923103877354846422784, 4995366924489042763913674615590287289608046336, 4995366924507286632895834264625967053561399040, 4995366924543708928397916780318445517146162944, 4995366924533795900704132026398741298080122624, 2140871539205541078202623227998533436869445120, 2140871539185988835344703017709848286629725696, 2140871539354576223970255702273697839319090688, 2140871539312713330548318654518670712664753664, 2140871539649563589245765596919586662022907392, 2140871539648265515031131890012454037940604416, 2497683462752063329276215795575400873427209728, 2497683462749467180846948381761135625262599168, 1070435770708121297681120348763544019020024832, 1070435770728890485115259659277666004336905216, 1248841732317135470247545405458852896384752640, 1248841732311943173389010577830322400055531520, 535217885958963232859867593105574831864158208, 535217885958963232859867593105574831864166400, 624420866753085725426010466196168277888086016, 267608943576595755161438973833794493792415744, 312210433973657001444510410379091216804372480, 133804472385412016312224664197904324756561920, 156105217583942639453760382470552686262534144, 66902236789820146887617509379959240238678016, 78052609389085458458385368516283420991782912, 39026305291656867960697861539148788356546560, 19513153242942572711854108050581472039272448, 20906949817850736658200090442621994634313728, 10453475506039507060605222502318075184414720, 5226738350133892261807788532166115481092096, 1306685483204681162709713054552146185289728, 691454963811624423185783403544767058935808, 174224436863802173805581096441418979737600, 21777936483221741569526362504672367869952, 31153781153022354500604921737379840, 31153781153626817410377051952644096, 31153781153626817410377051952644096, 31153781153626817410377051952644096, 31153781151208965771288531091587072, 31153781151208965771288531091587072, 31153781151208965771288531091587072, 31153781151208965771288531091587072, 31153781151208965771288531091587072, 31153781151208965771288531091587072, 31153781151208974706430191794651136, 31153781151209002592719084472762368, 31153781151209035487010762786865152, 31153781151209095024597836624822272, 31153781151209076577853762915270656, 31153781151209224079748758553755648, 31153781151209187195267810389393408, 31153781151209187195267810389393408, 31153781151209224088755957808496640, 36346078009744051708279254882975744, 36346078009743922653128332954042368, 15576890575604621488479174058311680, 18173039004871970415021728557170688, 9086519502435985099512434151915520, 9086519502435951809185463605919744, 4543259751217974175949346706554880, 2433889152438200452843994991296512, 1216944576219100229377484751110144, 628754697713201800030863392505856, 157188674428300515591385421709312, 39930993907189793600699199651840, 10061976639316146531601490116608, 2534063261007325117889137082368, 158456325026222832177874206720, 4951760083354544804758290432, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n\ndef parse_bitmap():\n    return [[1] * 160] + [\n        [1] + [int(c) for c in bin(i).removeprefix(\"0b\").zfill(158)] + [1]\n        for i in _ints\n    ] + [[1] * 160]\n\n'''\nasync def load_bitmap(url: str) -&gt; list[list[int]]:\n    # Fetch the text file from the URL\n    response = await fetch(url)\n    text = await response.text()\n\n    bitmap: list[list[int]] = []\n    for line in text.splitlines():\n        line = line.strip()\n        if...<\/py-script>","summary":"Learn how to implement and use the floodfill algorithm in Python.","date_modified":"2025-11-23T15:59:09+01:00","tags":["python","programming","algorithms","pyscript","visualisation","graphs"],"image":"\/user\/pages\/02.blog\/floodfill-algorithm-in-python\/thumbnail.webp"},{"title":"TIL #126 \u2013 Hash of infinity","date_published":"2025-07-20T22:18:00+02:00","id":"https:\/\/mathspp.com\/blog\/til\/hash-of-infinity","url":"https:\/\/mathspp.com\/blog\/til\/hash-of-infinity","content_html":"<p>Today I learned about a Python Easter Egg hidden in the hash of two special float values.<\/p>\n\n<h2 id=\"hash-of-infinity\">Hash of infinity<a href=\"#hash-of-infinity\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>Today I learned that the values <code>float(\"inf\")<\/code> and <code>float(\"-inf\")<\/code> have two very special hashes:<\/p>\n<pre><code class=\"language-pycon\">&gt;&gt;&gt; hash(float(\"inf\"))\n314159\n&gt;&gt;&gt; hash(float(\"-inf\"))\n-314159<\/code><\/pre>\n<p>In case you can't tell, those are the first few digits of the mathematical constant <span class=\"mathjax mathjax--inline\">\\(\\pi\\)<\/span>:<\/p>\n<pre><code class=\"language-pycon\">&gt;&gt;&gt; import math\n&gt;&gt;&gt; math.pi\n3.141592653589793\n## ^^^^^<\/code><\/pre>\n<p>I learned about this during the <a href=\"https:\/\/ep2025.europython.eu\/session\/cpython-core-development-panel\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">CPython panel<\/a> held at EuroPython 2025, after the hosts \u0141ukasz &amp; Pablo asked the panel if they know what <a href=\"\/blog\/til\/hash-of-1-is-2\">the hash of <code>-1<\/code> was<\/a>.<\/p>","summary":"Today I learned about a Python Easter  Egg hidden in the hash of two special float values.","date_modified":"2025-10-20T22:34:56+02:00","tags":["algorithms","mathematics","python","programming"],"image":"\/user\/pages\/02.blog\/04.til\/126.hash-of-infinity\/thumbnail.webp"},{"title":"Binary search as a bidirectional generator","date_published":"2025-03-03T14:20:00+01:00","id":"https:\/\/mathspp.com\/blog\/binary-search-as-a-bidirectional-generator","url":"https:\/\/mathspp.com\/blog\/binary-search-as-a-bidirectional-generator","content_html":"<p>This article proposes an implementation of an ergonomic binary search algorithm implemented as a bidirectional generator.<\/p>\n\n<p>Python generators can be bidirectional and in this article I will use this feature to try to implement the <a href=\"https:\/\/en.wikipedia.org\/wiki\/Binary_search\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">binary search<\/a> algorithm in an ergonomic way.\nTo this end, I'll write a bidirectional generator that implements the binary search algorithm independent of any use cases.\nThis means the resulting generator can be used ergonomically whenever a binary search needs to be employed.<\/p>\n<h2 id=\"bidirectional-generators\">Bidirectional generators<a href=\"#bidirectional-generators\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>Python generators support the method <code>send<\/code> which allows sending data into a generator.\nTo receive data inside a generator, you can assign a <code>yield<\/code> statement to a variable and when the user calls the method <code>send<\/code>, the argument passed in will be stored in the variable used.\nIt's also important to note that the method <code>send<\/code> will send data into the generator and it will also execute the generator up to its next <code>yield<\/code> statement.<\/p>\n<p>The snippet below shows how the method <code>send<\/code> interacts with a generator:<\/p>\n<pre><code class=\"language-py\">def my_gen():\n    yield 1\n    yield 2\n    value = yield 3\n    print(value)\n    yield 5\n    yield 6\n\nvalues = my_gen()\nprint(next(values))  # 1\nprint(next(values))  # 2\nprint(next(values))  # 3\nvalues.send(4)  # 4\nprint(next(values))  # 6<\/code><\/pre>\n<p>Note that the <code>5<\/code> was skipped because it was yielded when you sent the <code>4<\/code> into the generator and we didn't catch it outside the generator.<\/p>\n<p>Because of the way the method <code>send<\/code> interacts with generators, a generator that has been instantiated but hasn't been started yet can't be sent any data but it can be started with a call to <code>send(None)<\/code>, which will run the generator up to its first <code>yield<\/code>.<\/p>\n<h2 id=\"binary-search\">Binary search<a href=\"#binary-search\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>If you abstract the algorithm away from its concrete applications, the binary search algorithm produces a sequence of integer guesses that are candidates to satisfy some criteria.\nSince binary search produces these values consecutively, and it doesn't need to generate every possible value ahead of time, it makes sense to consider a generator as the tool to implement binary search.<\/p>\n<p>Additionally, depending on the result of an evaluation that is performed on a guess produced by the algorithm, the next guess might be above or below the previous guess.\nThis is why the generator needs to be bidirectional, since the user will inform the generator on whether guesses are currently too high or too low.<\/p>\n<p>To inform the binary search generator of whether the guesses need to be increased or decreased, we will send the following integers to the binary search:<\/p>\n<ul><li><code>-1<\/code>: the guess is too high;<\/li>\n<li><code>0<\/code>: the guess is correct; and<\/li>\n<li><code>1<\/code>: the guess is too low.<\/li>\n<\/ul><p>With this in mind, here is an example usage of the generator I am proposing:<\/p>\n<pre><code class=\"language-py\">import random\n\nsn = random.randint(0, 100)  # Secret number to be guessed.\n\nsearcher = binary_search(0, 100)\nfor guess in searcher:\n    feedback = 1 if guess &lt; sn else -1 if guess &gt; sn else 0\n    searcher.send(feedback)\n\nprint(f\"Secret number was {guess}.\")\n# Secret number was 42.<\/code><\/pre>\n<p>The...<\/p>","summary":"This article proposes an implementation of an ergonomic binary search algorithm implemented as a bidirectional generator.","date_modified":"2025-07-23T16:49:02+02:00","tags":["algorithms","generators","programming","python"],"image":"\/user\/pages\/02.blog\/binary-search-as-a-bidirectional-generator\/thumbnail.webp"},{"title":"Beating LinkedIn \u201cQueens\u201d with Python","date_published":"2025-02-25T18:40:00+01:00","id":"https:\/\/mathspp.com\/blog\/beating-linkedin-queens-with-python","url":"https:\/\/mathspp.com\/blog\/beating-linkedin-queens-with-python","content_html":"<p>This is a short account of how I wrote a program that solves all LQueens puzzles from LinkedIn automatically with Python.<\/p>\n\n<p>About a year ago LinkedIn started publishing a daily logic puzzle called &ldquo;Queens&rdquo;.\nThis puzzle is a crossover between the <a href=\"\/blog\/problems\/8-queens\">queen-placing puzzle from chess<\/a> and Sudoku, and takes place in a square grid with a number of heterogeneous coloured regions, as the image below demonstrates:<\/p>\n<figure class=\"image-caption\"><img title=\"Queens puzzle #179.\" alt='A screenshot showing the text \"Puzzle 179 Appeared on 26 October 2024\" and a coloured grid that is 8 by 8 with 8 coloured regions of heterogeneous shapes.' src=\"\/user\/pages\/02.blog\/beating-linkedin-queens-with-python\/_puzzle.webp\"><figcaption class=\"\">Queens puzzle #179.<\/figcaption><\/figure><h2 id=\"puzzle-rules\">Puzzle rules<a href=\"#puzzle-rules\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>The rules for the puzzle are quite simple.\nYou have to place one queen on each coloured region while also making sure that:<\/p>\n<ul><li>there is one and only one queen per row and column; and<\/li>\n<li>queens cannot touch each other, not even diagonally.<\/li>\n<\/ul><p>See if you can solve the puzzle above, for example by visiting <a href=\"https:\/\/www.archivedqueens.com\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">this unofficial archive<\/a>.\n(Make sure to select the same puzzle as I'm showing above, number 179.)<\/p>\n<h2 id=\"solving-the-puzzle\">Solving the puzzle<a href=\"#solving-the-puzzle\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>I write algorithms for things like this all the time &ndash; which is a bit of a weird thing to be experienced on, but oh well... &ndash;, so solving the puzzle in Python was actually quite straightforward.<\/p>\n<p>I decided to represent the board as a list of sets, where each set contains the coordinates of the cells of a coloured region.\nFor example, puzzle 179 shown above would be represented as the following list of sets:<\/p>\n<pre><code class=\"language-py\">[\n    {(0, 1), (4, 0), (0, 0), (7, 0), (2, 0), (3, 0), ...},  # yellow\n    {(7, 4), (7, 1), (2, 1), (7, 7), (3, 1), (6, 1), ...},  # orange\n    {(2, 4), (0, 4), (0, 3), (1, 4), (0, 6), (0, 2), ...},  # blue\n    {(2, 3), (3, 2), (1, 2), (2, 2)},                       # green\n    {(6, 2), (4, 3), (4, 2), (3, 3), (5, 2)},               # gray\n    {(2, 5), (3, 4), (3, 5), (1, 5)},                       # red\n    {(4, 4), (5, 5), (6, 5), (5, 4), (4, 5)},               # purple\n    {(0, 7), (2, 7), (3, 7), (4, 6), (5, 7), (1, 7), ...},  # brown\n]<\/code><\/pre>\n<p>The good stuff lies in the function <code>solve<\/code>.\nThe function <code>solve<\/code> is a recursive function that accepts the list with coloured groups that don't have a queen yet and returns the list of positions where queens must go.<\/p>\n<p>How does the function know it's done?\nIf it receives an empty list of coloured groups, then that means all groups received a queen, and we can start producing the list of queen positions:<\/p>\n<pre><code class=\"language-py\">def solve(group_sets):\n    if not group_sets:\n        return []\n\n    # ...<\/code><\/pre>\n<p>That's the best possible scenario, because that's when the function is already done.\nBut usually, the function has work to do.<\/p>\n<p>If the list of coloured groups is not empty, then the function looks at the very first coloured group, which should be <code>group_sets[0]<\/code>, and traverses that set of positions, one position at a time:<\/p>\n<pre><code class=\"language-py\">    for tx, ty in group_sets[0]:<\/code><\/pre>\n<p>The <code>tx<\/code> and <code>ty<\/code> and the x and y coordinates for the <em>tentative<\/em> position of the queen of the current coloured group.\nBut let us assume, for a second,...<\/p>","summary":"This is a short account of how I wrote a program that solves all Queens puzzles from LinkedIn automatically with Python.","date_modified":"2025-07-23T16:49:02+02:00","tags":["algorithms","image processing","python","programming","recursion","slice of life"],"image":"\/user\/pages\/02.blog\/beating-linkedin-queens-with-python\/thumbnail.webp"},{"title":"functools.cmp_to_key","date_published":"2025-01-21T21:05:00+01:00","id":"https:\/\/mathspp.com\/blog\/functools-cmp_to_key","url":"https:\/\/mathspp.com\/blog\/functools-cmp_to_key","content_html":"<p>In this article I explore <code>functools.cmp_to_key<\/code> and propose a possible implementation.<\/p>\n\n<p>The <a href=\"https:\/\/docs.python.org\/3\/library\/functools.html#functools.cmp_to_key\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">module <code>functools<\/code> provides a function <code>cmp_to_key<\/code><\/a> whose documentation says<\/p>\n<blockquote>\n<p>&ldquo;Transform an old-style comparison function to a key function. Used with tools that accept key functions (such as <code>sorted()<\/code>, <code>min()<\/code>, <code>max()<\/code>, [...]) [...]<\/p>\n<p>A comparison function is any callable that accepts two arguments, compares them, and returns a negative number for less-than, zero for equality, or a positive number for greater-than. A key function is a callable that accepts one argument and returns another value to be used as the sort key.&rdquo;<\/p>\n<\/blockquote>\n<p>I never used this function but it got me thinking...\nA key function is a function that accepts a value and produces another single value used for comparisons, whereas an &ldquo;old-style comparison function&rdquo; accepts two values and produces a single value as the result of a comparison!\nHow come <code>cmp_to_key<\/code> converts the later into the former?<\/p>\n<h2 id=\"an-example-comparison-function\">An example comparison function<a href=\"#an-example-comparison-function\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>An example comparison key can be given by the function <code>cmp<\/code> below:<\/p>\n<pre><code class=\"language-py\">def cmp(a, b):\n    return a - b<\/code><\/pre>\n<p>You can see that <code>cmp<\/code> returns a negative number if <code>a &lt; b<\/code>, it returns zero if <code>a == b<\/code>, and it returns a positive number if <code>a &gt; b<\/code>:<\/p>\n<pre><code class=\"language-py\">print(cmp(1, 3))  # &lt; 0\nprint(cmp(42, 42))  # == 0\nprint(cmp(73, -5))  # &gt; 0<\/code><\/pre>\n<h2 id=\"the-example-scenario\">The example scenario<a href=\"#the-example-scenario\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>To study <code>cmp_to_key<\/code> I will consider the task of sorting strings in a list by length.\nWith the built-in <code>sorted<\/code> and the built-in <code>len<\/code> this is a single function call:<\/p>\n<pre><code class=\"language-py\">words = [\"platypus\", \"dog\", \"horse\"]\nprint(sorted(words, key=len))  # ['dog', 'horse', 'platypus']<\/code><\/pre>\n<p>As an &ldquo;old-style&rdquo; comparison function, I could implement the following function <code>cmp_len<\/code>:<\/p>\n<pre><code class=\"language-py\">def cmp_len(str1, str2):\n    return len(str1) - len(str2)<\/code><\/pre>\n<p>Let me verify that this works by using <code>functools.cmp_to_key<\/code>:<\/p>\n<pre><code class=\"language-py\">from functools import cmp_to_key\n\nwords = [\"platypus\", \"dog\", \"horse\"]\nprint(sorted(words, key=cmp_to_key(cmp_len)))  # ['dog', 'horse', 'platypus']<\/code><\/pre>\n<p>Alright, so <code>cmp_len<\/code> was properly defined.\nNow, how do I implement <code>cmp_to_key<\/code> myself?<\/p>\n<h2 id=\"using-a-binary-tree-to-encode-comparisons\">Using a binary tree to encode comparisons<a href=\"#using-a-binary-tree-to-encode-comparisons\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>The reason I was puzzled by <code>cmp_to_key<\/code> is that I typically imagine the key function as a function that accepts whatever values I am working with &ndash; in this case, strings &ndash; and produces an integer result that is used for comparisons &ndash; in this case, string lengths.<\/p>\n<p>However, the key function does not have to return integers.\nIt can return anything that Python knows how to compare.\nFor example, the key function can return tuples!\nAnd that's what I went with.<\/p>\n<p>To implement <code>cmp_to_key<\/code> I will write a class that keeps track of a binary tree.\nEvery time I try to compute the key value of a given string, I will use the comparison function to figure out where in the tree the value would fall into, and then I will return a tuple that encodes this result.<\/p>\n<p>For example, in the beginning the tree is empty.\nWhen Python tries to compute the key value for <code>\"platypus\"<\/code>, it goes down the tree and it puts it...<\/p>","summary":"In this article I explore `functools.cmp_to_key` and propose a possible implementation.","date_modified":"2025-07-23T16:49:02+02:00","tags":["algorithms","programming","python"],"image":"\/user\/pages\/02.blog\/functools-cmp_to_key\/thumbnail.webp"},{"title":"TIL #110 \u2013 Hash of -1 is -2","date_published":"2025-01-15T00:02:00+01:00","id":"https:\/\/mathspp.com\/blog\/til\/hash-of-1-is-2","url":"https:\/\/mathspp.com\/blog\/til\/hash-of-1-is-2","content_html":"<p>Today I learned that the hash of an integer is the integer itself, except for <code>-1<\/code>. The hash of <code>-1<\/code> is <code>-2<\/code>.<\/p>\n\n<h2 id=\"hash-of-1-is-2\">Hash of <code>-1<\/code> is <code>-2<\/code><a href=\"#hash-of-1-is-2\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>The hash of a value is a numeric \u201csignature\u201d assigned to that value used in data structures like dictionaries and sets.\nAmong other interpretations, a hash is supposed to be a quick way to check if two objects are different.\nSo, if two objects have different hashes, they <em>are<\/em> different.\nBut if two objects have the same hash, they can still be different.\nWhen this happens, we have what is called a \u201chash collision\u201d.<\/p>\n<p>A \u201chash function\u201d \u2013 a function that is used to compute hashes \u2013 is good when hash collisions are not common.\nHash collisions are impossible to avoid, but you want collisions to be as rare as possible, in some way.<\/p>\n<p>The Python built-in <code>hash<\/code> computes hashes for Python objects and it's used under the hood with dictionaries, sets, and more.\nWhen applied to integers, it looks like the built-in <code>hash<\/code> returns the integer itself:<\/p>\n<pre><code class=\"language-py\">assert 0 == hash(0)\nassert 1 == hash(1)\nassert 46245234 == hash(46245234)\nassert -83572 == hash(-83572)<\/code><\/pre>\n<p>Interestingly enough, the hash of <code>-1<\/code> is <code>-2<\/code>, and so is the hash of <code>-2<\/code>:<\/p>\n<pre><code class=\"language-py\">print(hash(-1))  # -2\nprint(hash(-2))  # -2<\/code><\/pre>\n<p>This shows a very interesting hash collision that is \u201ceasy\u201d to come by!\nFor example, the integers <code>6909455589863252355<\/code> and <code>2297769571435864453<\/code> also share the same hash, for example, but this seems less surprising than the fact that <code>-1<\/code> and <code>-2<\/code> have the same hash.<\/p>\n<p>I am also trying to find the first positive integer for which the hash does not match the integer itself, but so far Python is still looking for the answer:<\/p>\n<pre><code class=\"language-py\">from itertools import count\n\nprint(\n    next(n for n in count() if n != hash(n))  # Python is still running this on my computer.\n)<\/code><\/pre>","summary":"Today I learned that the hash of an integer is the integer itself, except for -21. The hash of -1 is -2.","date_modified":"2025-10-20T22:34:56+02:00","tags":["algorithms","python","programming"],"image":"\/user\/pages\/02.blog\/04.til\/110.hash-of-1-is-2\/thumbnail.webp"},{"title":"Reverse-engineering the \u201cChronospatial Computer\u201d","date_published":"2024-12-21T09:00:00+01:00","id":"https:\/\/mathspp.com\/blog\/reverse-engineering-the-chronospatial-computer","url":"https:\/\/mathspp.com\/blog\/reverse-engineering-the-chronospatial-computer","content_html":"<p>Reverse-engineering the program from &ldquo;Chronospatial Computer&rdquo;, day 17 of Advent of Code 2024.<\/p>\n\n<p>The &ldquo;Chronospatial Computer&rdquo; is from <a href=\"https:\/\/adventofcode.com\/2024\/day\/17\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">Advent of Code 2024, day 17<\/a>, a problem that entertained me for a couple of hours.<\/p>\n<h2 id=\"parsing-the-input\">Parsing the input<a href=\"#parsing-the-input\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>My input file looked like this:<\/p>\n<pre><code class=\"language-txt\">Register A: 64012472\nRegister B: 0\nRegister C: 0\n\nProgram: 2,4,1,7,7,5,0,3,1,7,4,1,5,5,3,0<\/code><\/pre>\n<p>To read the input and parse it I used a context manager and a couple of calls to <code>readline<\/code>:<\/p>\n<pre><code class=\"language-py\">with open(\"input.txt\", \"r\") as f:\n    register_a = int(f.readline().split()[-1])\n    register_b = int(f.readline().split()[-1])\n    register_c = int(f.readline().split()[-1])\n    _ = f.readline()\n    program = [int(num) for num in f.readline().split()[-1].split(\",\")]\n\nprint(program, register_a, register_b, register_c)<\/code><\/pre>\n<h2 id=\"solving-part-1-with-functional-programming\">Solving part 1 with functional programming<a href=\"#solving-part-1-with-functional-programming\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>Part 1 required me to simulate a series of simple instructions that operate on three registers.\nWhen I read the problem statement, I decided I wanted to use some ideas from functional programming.\nSo, what I did was to separate each operator (there are 8) into three parts:<\/p>\n<ol><li>the part that performs some computation with the registers and\/or with the operand;<\/li>\n<li>the part that updates the state of the program, maybe by updating a register or by outputting a value; and<\/li>\n<li>the part that updates the pointer of the program, which controls the instruction that will run next.<\/li>\n<\/ol><p>By using <code>lambda<\/code> functions, <a href=\"\/blog\/pydonts\/dunder-methods\">dunder methods<\/a>, and <a href=\"\/blog\/functools-partial\">currying with <code>functools.partial<\/code><\/a>, each list below represents one of the three parts of each opcode.<\/p>\n<p>First, the computation part of each operation:<\/p>\n<pre><code class=\"language-py\">registers = [0, 1, 2, 3, register_A, register_B, register_C]\nA, B, C = 4, 5, 6  # Indices of the named registers in the list `registers`.\n\ncomputations = [\n    lambda o: registers[A] \/\/ pow(2, registers[o]),  # ADV\n    lambda o: registers[B] ^ o,                      # BXL\n    lambda o: registers[o] % 8,                      # BST\n    lambda o: ...,                                   # JNZ\n    lambda o: registers[B] ^ registers[C],           # BXC\n    lambda o: registers[o] % 8,                      # OUT\n    lambda o: registers[A] \/\/ pow(2, registers[o]),  # BDV\n    lambda o: registers[A] \/\/ pow(2, registers[o]),  # CDV\n]<\/code><\/pre>\n<p>In the lambda functions above, when we use <code>o<\/code> in isolation, we're using the operand as a literal operand, whereas the list <code>registers<\/code> maps an operand into its combo operand.\nBy using this list, we can map the numbers 0 through 3 to themselves and the indices 4, 5, and 6, to the registers A, B, and C, respectively, without having to use a conditional statement.<\/p>\n<p>The operation <code>JNZ<\/code> has a lambda function that does nothing because there is no proper computation for this operator.<\/p>\n<p>Then, I wrote a list with all the functions that update the state of the program:<\/p>\n<pre><code class=\"language-py\">from functools import partial\n\noutput = []\nstate_updates = [\n    partial(registers.__setitem__, A),\n    partial(registers.__setitem__, B),\n    partial(registers.__setitem__, B),\n    lambda v: ...,\n    partial(registers.__setitem__, B),\n    output.append,\n    partial(registers.__setitem__, B),\n    partial(registers.__setitem__, C),\n]<\/code><\/pre>\n<p>This uses the <a href=\"\/blog\/pydonts\/dunder-methods\">dunder method <code>__setitem__<\/code><\/a> and <a href=\"\/blog\/functools-partial\">the function <code>functools.partial<\/code><\/a> to create a function that accepts a single value and that writes that value to the correct register in the list <code>registers<\/code>.<\/p>\n<p>Finally, all operators move the program pointer by two positions except the operator <code>JNZ...<\/code><\/p>","summary":"Reverse-engineering the program from \u201cChronospatial Computer\u201d, day 17 of Advent of Code 2024.","date_modified":"2025-07-23T16:49:02+02:00","tags":["algorithms","mathematics","modular arithmetic","programming","python","recursion"],"image":"\/user\/pages\/02.blog\/reverse-engineering-the-chronospatial-computer\/thumbnail.webp"},{"title":"Solving \u201cBridge Repair\u201d in 4ms with Python","date_published":"2024-12-07T19:00:00+01:00","id":"https:\/\/mathspp.com\/blog\/solving-bridge-repair-in-4ms-with-python","url":"https:\/\/mathspp.com\/blog\/solving-bridge-repair-in-4ms-with-python","content_html":"<p>Solving &ldquo;Bridge Repair&rdquo;, from day 7 of Advent of Code 2024, in 4ms with Python with a simple deductive algorithm.<\/p>\n\n<p>Today I solved the problem from <a href=\"https:\/\/adventofcode.com\/2024\/day\/7\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">Advent of Code 2024, day 7<\/a> in 4ms using Python.\nMy first solution used brute-force and ran in 15 seconds.\nIt was very easy to implement and I got the job done quite quickly.\nBut then I thought about using a smarter algorithm, and I got a solution that's over 4,000 times faster and that runs in under 4ms.\nI will describe both solutions in this article.<\/p>\n<p>(It should be obvious by now, but this article contains spoilers!)<\/p>\n<h2 id=\"problem-statement\">Problem statement<a href=\"#problem-statement\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>In short, given a target integer and a list of integers, can we use addition, multiplication, and concatenation, to manipulate the list of integers to equal the target integer?\nThere is the added restriction that the operations are always evaluated from left to right and the integers cannot be reordered.<\/p>\n<p>Here are some examples that are possible:<\/p>\n<pre><code>5: 2 3\n6: 2 3\n23: 2 3\n94: 2 4 9 5<\/code><\/pre>\n<p>To make <span class=\"mathjax mathjax--inline\">\\(5\\)<\/span> with <span class=\"mathjax mathjax--inline\">\\(2\\)<\/span> and <span class=\"mathjax mathjax--inline\">\\(3\\)<\/span> we can do <span class=\"mathjax mathjax--inline\">\\(5 = 2 + 3\\)<\/span>.\nTo make <span class=\"mathjax mathjax--inline\">\\(6\\)<\/span> with <span class=\"mathjax mathjax--inline\">\\(2\\)<\/span> and <span class=\"mathjax mathjax--inline\">\\(3\\)<\/span> we can multiply the two numbers and to make <span class=\"mathjax mathjax--inline\">\\(23\\)<\/span> with <span class=\"mathjax mathjax--inline\">\\(2\\)<\/span> and <span class=\"mathjax mathjax--inline\">\\(3\\)<\/span> we can concatenate the two digits.\nFinally, to make <span class=\"mathjax mathjax--inline\">\\(94\\)<\/span> with the integers <span class=\"mathjax mathjax--inline\">\\(2\\)<\/span>, <span class=\"mathjax mathjax--inline\">\\(4\\)<\/span>, <span class=\"mathjax mathjax--inline\">\\(9\\)<\/span>, and <span class=\"mathjax mathjax--inline\">\\(5\\)<\/span>, we start by multiplying, then we concatenate, and then we add.\nIf we use <span class=\"mathjax mathjax--inline\">\\(||\\)<\/span> to represent concatenation, then <span class=\"mathjax mathjax--inline\">\\(94 = ((2 \\times 4) || 9) + 5\\)<\/span>.<\/p>\n<h2 id=\"brute-force-solution-15-seconds\">Brute-force solution, 15 seconds<a href=\"#brute-force-solution-15-seconds\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>To determine if a certain list of numbers can be manipulated to match the target value, we can write a function that generates all distinct sequences of operators and then tries to use each sequence of operators in turn.<\/p>\n<p>Using <code>itertools.product<\/code>, we can generate all sequences of distinct operators quite easily:<\/p>\n<pre><code class=\"language-py\">from itertools import product\nfrom operator import add, mul\n\nvalid_operators = [\n    add,\n    mul,\n    lambda x, y: int(f\"{x}{y}\")\n]\n\ndef is_valid(target, operands):\n    operator_sequences = product(valid_operators, repeat=len(operands) - 1)\n    start, *head = operands\n    for operators in operator_sequences:\n        total = start\n        for operator, operand in zip(operators, head):\n            total = operator(total, operand)\n        if total == target:\n            return True\n    return False\n\nprint(is_valid(94, [2, 4, 9, 5]))<\/code><\/pre>\n<p>Advent of Code provides an <a href=\"\/blog\/solving-bridge-repair-in-4ms-with-python\/.\/input.txt\">input file with 850 tests<\/a> and this code takes slightly over 15 seconds to classify all 850 tests on my machine.\nNow, I will show you how you can speed this up by a factor of 2,000.<\/p>\n<h2 id=\"deducing-valid-operations-4-milliseconds\">Deducing valid operations, 4 milliseconds<a href=\"#deducing-valid-operations-4-milliseconds\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>The faster algorithm is conceptually very simple.\nDisappointingly simple.\nInstead of working from the front to the back, trying random operations and seeing if they work out in the end, we work from the back to the front.<\/p>\n<p>For example, consider the target <code>94<\/code> and the list of integers <code>[2, 4, 9, 5]<\/code>.\nIf this is a possible case, then the final operation must be an addition.\nWhy?<\/p>\n<p>The...<\/p>","summary":"Solving \u201cBridge Repair\u201d, from day 7 of Advent of Code 2024, in 4ms with Python with a simple deductive algorithm.","date_modified":"2025-07-23T16:49:02+02:00","tags":["algorithms","mathematics","programming","python","recursion"],"image":"\/user\/pages\/02.blog\/solving-bridge-repair-in-4ms-with-python\/thumbnail.webp"}]}
