<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Extiri Blog</title>
    <description>blog about Extiri, programming and cybersecurity
</description>
    <link>https://extiri.com/blog/</link>
    <atom:link href="https://extiri.com/blog/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Sun, 03 May 2026 16:56:27 +0200</pubDate>
    <lastBuildDate>Sun, 03 May 2026 16:56:27 +0200</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
      <item>
        <title>Your Whole Stack, One Place: Introducing Resso</title>
        <description>&lt;p&gt;At some point, managing your own infrastructure stops being a background task and starts being a part-time job.&lt;/p&gt;

&lt;p&gt;For me, that point came somewhere around the third cloud VM. I had three servers, a handful of domains, over eight apps and projects across different environments, Vercel deployments, SaaS accounts with their own API keys and 2FA setups, and a growing list of things I was fairly sure I remembered, but was not entirely sure about. Which SSH key goes to which server? What registrar holds that domain, and when does it expire? Which environment variable in which project points to the staging database?&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;The answers were scattered. Some lived in a notes app, some in a password manager, some just in my head. None of those places talk to each other, and none of them give you a picture of how everything connects.&lt;/p&gt;

&lt;p&gt;I built Resso to fix this.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog/assets/resso/screen1.png&quot; alt=&quot;Resso Overview&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;what-resso-does&quot;&gt;What Resso does&lt;/h2&gt;

&lt;p&gt;Resso is a native macOS app for managing your infrastructure assets. Not just listing them, but actually organizing them in a way that reflects how they relate to each other.&lt;/p&gt;

&lt;p&gt;The core idea is that everything in your stack is an asset. A server is an asset. A domain is an asset. An app, a cloud account, a SaaS subscription are all assets. Resso gives each of them a home, a structure, and a way to link to everything else it touches.&lt;/p&gt;

&lt;p&gt;Once your assets are in, the app starts to do something genuinely useful: it shows you your infrastructure as a graph. You can see your staging environment linked to production, your payment processor connected to the apps that depend on it, your servers holding the deployments that run on them. The dependencies that were invisible before are now visible, and staying on top of them stops feeling like a memory exercise.&lt;/p&gt;

&lt;h2 id=&quot;built-for-the-kind-of-setup-that-grows-sideways&quot;&gt;Built for the kind of setup that grows sideways&lt;/h2&gt;

&lt;p&gt;Solo developers and small studios tend to accumulate infrastructure organically. You spin up a server here, register a domain there, add a Vercel project, hook in Stripe, maybe pick up a second server when the first one starts to feel crowded. Before long, you have a real stack, with real interdependencies, and almost nothing to help you reason about it as a whole.&lt;/p&gt;

&lt;p&gt;Resso is designed for exactly this kind of setup. The focus is on giving you a clear mental model of what you have, so you can maintain it and build on it without constantly reconstructing that picture from scratch.&lt;/p&gt;

&lt;p&gt;For solopreneurs specifically, Resso acts as a central knowledge base. You store server IPs, providers, monthly costs, and renewal dates. You track domain registrars, expiration dates, and auto-renewal status. You keep app bundle IDs, platforms, and repository URLs organized. You manage account usernames, emails, and service names. All in one place, all searchable, all structured consistently.&lt;/p&gt;

&lt;h2 id=&quot;assets-with-real-structure&quot;&gt;Assets with real structure&lt;/h2&gt;

&lt;p&gt;Resso ships with standard templates for the things most developers actually manage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Servers&lt;/strong&gt; capture IP addresses, providers, monthly costs, and renewal dates. When you have three VMs across different providers, having this in one structured format instead of three different notes is already a meaningful upgrade.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Domains&lt;/strong&gt; track registrars, expiration dates, and auto-renewal status. Domain expiry is one of those things that will eventually catch you off-guard if you are not watching it somewhere intentional. Resso is intentional about it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Apps&lt;/strong&gt; store bundle IDs, platforms, and repository URLs. The full picture of how a project is identified and where its code lives, all in one structured view.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Accounts&lt;/strong&gt; track service names, usernames, and email addresses. Useful when you need to find which credentials belong to which service without opening five browser tabs.&lt;/p&gt;

&lt;p&gt;Beyond these templates, Resso also supports custom entities. If your stack has something specific that does not fit a standard mold, you can define your own asset type with its own properties. The app does not assume it knows every shape infrastructure can take.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog/assets/resso/screen2.png&quot; alt=&quot;Creating Custom Assets in Resso&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;the-graph-view&quot;&gt;The graph view&lt;/h2&gt;

&lt;p&gt;The visual dependency graph is one of the things that sets Resso apart from a well-organized notes app.&lt;/p&gt;

&lt;p&gt;When you start linking assets together, the graph view turns those connections into something you can actually see and navigate. You get a spatial map of your infrastructure: which servers host which apps, which apps talk to which external services, which accounts own which resources. That kind of map is surprisingly hard to hold in your head, and extremely useful to have on screen.&lt;/p&gt;

&lt;p&gt;The graph also makes gaps obvious. If a service is floating without documented connections, you notice it. If something is linked to a dependency that has not been fully set up yet, you can see that too. It becomes much easier to reason about the health of your setup when the setup has a shape.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog/assets/resso/screen3.png&quot; alt=&quot;Resso Asset Graph&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;filtering-that-actually-handles-complexity&quot;&gt;Filtering that actually handles complexity&lt;/h2&gt;

&lt;p&gt;As the number of assets grows, search and filtering become essential. Resso has a filter engine that supports complex logic across different property types.&lt;/p&gt;

&lt;p&gt;You can combine string, numeric, and date conditions. You can filter by whether a value contains something, falls below a threshold, or precedes a specific date. The idea is that when you have hundreds of assets across different categories, finding exactly what you need should not involve scrolling.&lt;/p&gt;

&lt;p&gt;The filtering is fast enough that it does not slow you down. The goal is to make your asset library feel small even when it is not.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog/assets/resso/screen4.png&quot; alt=&quot;Resso Search and Filters&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;icloud-sync-and-offline-support&quot;&gt;iCloud sync and offline support&lt;/h2&gt;

&lt;p&gt;Resso syncs via iCloud, which means your assets are available across your Macs without any extra setup. If you work across a laptop and a desktop, or want to access your infrastructure notes from a secondary machine, the library stays current.&lt;/p&gt;

&lt;p&gt;The app also works fully offline. Infrastructure management is not always done in ideal network conditions, and Resso does not require connectivity to be useful.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog/assets/resso/screen5.png&quot; alt=&quot;Resso - more features&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;why-a-native-mac-app&quot;&gt;Why a native Mac app&lt;/h2&gt;

&lt;p&gt;There are web-based tools for some of this. Spreadsheets, Notion databases, purpose-built SaaS products. Most of them are either too generic to be useful or too enterprise-focused to be practical for a solo developer who just wants to know when their domain expires and where to find the SSH key for their staging server.&lt;/p&gt;

&lt;p&gt;A native Mac app that lives in your dock and opens instantly is a different kind of tool. It fits into the way you actually work instead of adding another browser tab to the pile. Resso requires macOS 26 or later and is available on the Mac App Store.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;If you manage your own infrastructure, Resso is worth a look. The first few hours of setting it up will pay back quickly the next time you need to find something you were certain you remembered.&lt;/p&gt;

&lt;div class=&quot;cta-box&quot;&gt;
    &lt;h2&gt;Try Resso for free on the Mac App Store&lt;/h2&gt;
    &lt;p&gt;Manage your infrastructure assets with ease. Works on macOS 26 or later.&lt;/p&gt;
    &lt;a href=&quot;https://apps.apple.com/pl/app/resso-asset-manager/id6761390161?mt=12&quot; class=&quot;cta-btn&quot;&gt;Download Resso on the App Store&lt;/a&gt;
&lt;/div&gt;
</description>
        <pubDate>Thu, 30 Apr 2026 00:00:00 +0200</pubDate>
        <link>https://extiri.com/blog/posts/introducing-resso</link>
        <guid isPermaLink="true">https://extiri.com/blog/posts/introducing-resso</guid>
        
        <category>product-launch</category>
        
        <category>devops</category>
        
        <category>extiri</category>
        
        
      </item>
    
      <item>
        <title>The Power of Elegant Simplicity #2: Bloom Filters — The Data Structure That&apos;s Okay With Being Wrong</title>
        <description>&lt;p&gt;&lt;em&gt;Part of the “The Power of Elegant Simplicity” series - algorithms that hide power in plain sight&lt;/em&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;You know what’s refreshing? An algorithm that looks you in the eye, shrugs, and says: “Look, I might be wrong. But I’m &lt;em&gt;never&lt;/em&gt; wrong in &lt;em&gt;that&lt;/em&gt; direction.”&lt;/p&gt;

&lt;p&gt;Welcome to Bloom Filters — a probabilistic data structure so audaciously simple that it uses a handful of hash functions and a bit array to answer membership queries at lightning speed. No linked lists, no tree traversals, no disk reads. Just bits and honesty about its own limitations.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;the-problem-checking-without-knowing-everything&quot;&gt;The Problem: Checking Without Knowing Everything&lt;/h2&gt;

&lt;p&gt;Let’s say you’re building a web crawler. Every second, you’re discovering thousands of URLs. You need to know: &lt;em&gt;have I visited this URL before?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;One approach: keep a giant set of every URL you’ve ever seen. Query it before each visit.&lt;/p&gt;

&lt;p&gt;Sounds fine, until you realize you’ve crawled a billion pages. Storing a billion URLs takes serious memory — often tens or hundreds of gigabytes. Suddenly your crawler is spending more time juggling memory than actually crawling.&lt;/p&gt;

&lt;p&gt;What if you could answer “have I seen this before?” using a fraction of the memory? What if the only trade-off was occasionally a false positive — claiming you’ve seen something you haven’t?&lt;/p&gt;

&lt;p&gt;For many applications, that’s a completely acceptable deal. You re-check false positives. You never miss true ones.&lt;/p&gt;

&lt;p&gt;That’s Bloom Filters in a nutshell.&lt;/p&gt;

&lt;h2 id=&quot;the-ingredients-embarrassingly-simple&quot;&gt;The Ingredients: Embarrassingly Simple&lt;/h2&gt;

&lt;p&gt;A Bloom Filter is just two things:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;A bit array&lt;/strong&gt; of size &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;m&lt;/code&gt;, initialized to all zeros.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;k&lt;/code&gt; independent hash functions&lt;/strong&gt;, each mapping any input to an index in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[0, m-1]&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s it. No pointers. No nodes. No dynamic allocation. Just a flat array of bits and some hash functions.&lt;/p&gt;

&lt;p&gt;![Bloom Filter diagram showing a bit array with bits set by multiple hash functions for two elements]&lt;/p&gt;

&lt;h2 id=&quot;how-it-works-inserting-an-element&quot;&gt;How It Works: Inserting an Element&lt;/h2&gt;

&lt;p&gt;When you want to add an element to the filter, you run it through all &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;k&lt;/code&gt; hash functions. Each one gives you an index. You flip those &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;k&lt;/code&gt; bits in the array to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;table style=&quot;border-spacing: 0&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot; style=&quot;text-align: right&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bloom_filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hash_functions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;
    Add an item to the Bloom Filter.
    
    Args:
        bloom_filter: List of m bits (integers 0 or 1)
        item: The item to add
        hash_functions: List of k hash functions
        m: Size of the bit array
    &quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hash_functions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;bloom_filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Let’s say we have a bit array of size 16 and 3 hash functions. We add the word &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;apple&quot;&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;h1(&quot;apple&quot;) % 16 = 3&lt;/code&gt;  → set bit 3&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;h2(&quot;apple&quot;) % 16 = 7&lt;/code&gt;  → set bit 7&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;h3(&quot;apple&quot;) % 16 = 13&lt;/code&gt; → set bit 13&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now we add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;banana&quot;&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;h1(&quot;banana&quot;) % 16 = 0&lt;/code&gt;  → set bit 0&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;h2(&quot;banana&quot;) % 16 = 7&lt;/code&gt;  → set bit 7 (already set — no problem)&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;h3(&quot;banana&quot;) % 16 = 11&lt;/code&gt; → set bit 11&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our filter after two insertions:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;table style=&quot;border-spacing: 0&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot; style=&quot;text-align: right&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;Index: 0  1  2  3  4  5  6  7  8  9  10 11 12 13 14 15
Bits:  1  0  0  1  0  0  0  1  0  0  0  1  0  1  0  0
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;h2 id=&quot;how-it-works-querying-an-element&quot;&gt;How It Works: Querying an Element&lt;/h2&gt;

&lt;p&gt;To check if an element is in the set, run it through all &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;k&lt;/code&gt; hash functions and look up those indices.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;If &lt;strong&gt;any&lt;/strong&gt; bit is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt; → the element is &lt;strong&gt;definitely not&lt;/strong&gt; in the set. No way around it. You never set those bits, so the element was never added.&lt;/li&gt;
  &lt;li&gt;If &lt;strong&gt;all&lt;/strong&gt; bits are &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt; → the element is &lt;strong&gt;probably&lt;/strong&gt; in the set.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;table style=&quot;border-spacing: 0&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot; style=&quot;text-align: right&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bloom_filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hash_functions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;
    Check if an item is probably in the set.
    
    Returns:
        False: Item is DEFINITELY NOT in the set.
        True:  Item is PROBABLY in the set (might be a false positive).
    &quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hash_functions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bloom_filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;False&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Let’s check for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;cherry&quot;&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;h1(&quot;cherry&quot;) % 16 = 3&lt;/code&gt;  → bit 3 is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt; ✓&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;h2(&quot;cherry&quot;) % 16 = 7&lt;/code&gt;  → bit 7 is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt; ✓&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;h3(&quot;cherry&quot;) % 16 = 11&lt;/code&gt; → bit 11 is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt; ✓&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All bits are set! But we never added &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;cherry&quot;&lt;/code&gt;. This is a &lt;strong&gt;false positive&lt;/strong&gt; — &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;cherry&quot;&lt;/code&gt; just happened to hash to positions that were already set by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;apple&quot;&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;banana&quot;&lt;/code&gt;. The filter says “probably yes” when the truth is “definitely no.”&lt;/p&gt;

&lt;p&gt;This is the deal you make with a Bloom Filter. You accept the occasional false positive. In return, you get blazing-fast queries and microscopic memory usage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Crucially&lt;/strong&gt;, there are no false negatives. If an element was added, every bit it set stays set — they’ll all be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt; when you query. The filter will never tell you “not in set” when it actually is.&lt;/p&gt;

&lt;h2 id=&quot;the-math-behind-the-trade-off&quot;&gt;The Math Behind the Trade-Off&lt;/h2&gt;

&lt;p&gt;Here’s where it gets surprisingly elegant. The false positive rate is not arbitrary or unpredictable — it’s precisely calculable.&lt;/p&gt;

&lt;p&gt;Given:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;m&lt;/code&gt; = size of the bit array&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n&lt;/code&gt; = number of elements inserted&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;k&lt;/code&gt; = number of hash functions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The probability of a false positive is approximately:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;table style=&quot;border-spacing: 0&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot; style=&quot;text-align: right&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;p ≈ (1 - e^(-kn/m))^k
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;This formula is beautiful because it tells you exactly how to tune your filter:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The optimal number of hash functions&lt;/strong&gt; for a given &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;m&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n&lt;/code&gt; is:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;table style=&quot;border-spacing: 0&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot; style=&quot;text-align: right&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;k = (m/n) * ln(2) ≈ 0.693 * (m/n)
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The required bit array size&lt;/strong&gt; to achieve a target false positive rate &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;p&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;table style=&quot;border-spacing: 0&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot; style=&quot;text-align: right&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;m = -n * ln(p) / (ln(2))²
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Let’s say you want to track 1 million URLs with a 1% false positive rate. How much memory do you need?&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;table style=&quot;border-spacing: 0&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot; style=&quot;text-align: right&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;math&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_000_000&lt;/span&gt;  &lt;span class=&quot;c&quot;&gt;# elements&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.01&lt;/span&gt;       &lt;span class=&quot;c&quot;&gt;# 1% false positive rate&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;math&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;math&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;k&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;math&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Bit array size: {m:,.0f} bits ({m / 8 / 1024 / 1024:.2f} MB)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Optimal hash functions: {k:.1f}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Output:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;table style=&quot;border-spacing: 0&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot; style=&quot;text-align: right&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;Bit array size: 9,585,059 bits (1.14 MB)
Optimal hash functions: 6.6
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;1.14 megabytes&lt;/strong&gt; to track a million URLs with 1% false positives. A naive Python &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;set&lt;/code&gt; of those same URLs would consume hundreds of megabytes. Often an order of magnitude or more of memory reduction.&lt;/p&gt;

&lt;h2 id=&quot;a-complete-implementation&quot;&gt;A Complete Implementation&lt;/h2&gt;

&lt;p&gt;Here’s a clean, practical Bloom Filter using Python:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;table style=&quot;border-spacing: 0&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot; style=&quot;text-align: right&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;math&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;hashlib&lt;/span&gt;


&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;BloomFilter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;
    A space-efficient probabilistic data structure for set membership queries.
    
    Guarantees:
        - No false negatives: if an item was added, contains() always returns True.
        - Bounded false positives: probability is calculable from n, m, and k.
    &quot;&quot;&quot;&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;capacity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;error_rate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.01&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;
        Initialize a Bloom Filter.
        
        Args:
            capacity: Expected number of elements to insert.
            error_rate: Desired false positive probability (e.g., 0.01 = 1&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;).
        &quot;&quot;&quot;&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;capacity&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;capacity&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;error_rate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;error_rate&lt;/span&gt;
        
        &lt;span class=&quot;c&quot;&gt;# Calculate optimal parameters&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_optimal_m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;capacity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;error_rate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_optimal_k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;capacity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        
        &lt;span class=&quot;c&quot;&gt;# Bit array using a bytearray for memory efficiency&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bit_array&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bytearray&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;math&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ceil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
        
        &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Bloom Filter initialized:&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;  Bit array size: {self.m:,} bits ({self.m / 8 / 1024:.1f} KB)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;  Hash functions: {self.k}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    
    &lt;span class=&quot;nd&quot;&gt;@staticmethod&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;_optimal_m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Optimal bit array size for n elements and false positive rate p.&quot;&quot;&quot;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;math&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ceil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;math&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;math&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    
    &lt;span class=&quot;nd&quot;&gt;@staticmethod&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;_optimal_k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Optimal number of hash functions.&quot;&quot;&quot;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;round&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;math&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;_hash_indices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Generate k hash indices for an item using double hashing.&quot;&quot;&quot;&lt;/span&gt;
        &lt;span class=&quot;c&quot;&gt;# Two base hashes from different algorithms&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;h1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hashlib&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;md5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;encode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hexdigest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;h2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hashlib&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sha1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;encode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hexdigest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        
        &lt;span class=&quot;c&quot;&gt;# Generate k indices using linear combination (Kirsch-Mitzenmacher)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;yield&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;h1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;_set_bit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;byte_index&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;//&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;bit_offset&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bit_array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;byte_index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bit_offset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;_get_bit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;byte_index&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;//&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;bit_offset&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bit_array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;byte_index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bit_offset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Add an item to the filter.&quot;&quot;&quot;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_hash_indices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
            &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_set_bit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;
        Check if item is in the set.
        
        Returns:
            False → item is DEFINITELY NOT in the set.
            True  → item is PROBABLY in the set (may be a false positive).
        &quot;&quot;&quot;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_get_bit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;index&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;index&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_hash_indices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    
    &lt;span class=&quot;nd&quot;&gt;@property&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;estimated_false_positive_rate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Estimated current false positive rate based on elements inserted.&quot;&quot;&quot;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;math&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;


&lt;span class=&quot;c&quot;&gt;# Example: URL deduplication for a web crawler&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;bf&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BloomFilter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;capacity&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_000_000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;error_rate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.01&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;urls_to_crawl&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;https://example.com/page-1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;https://example.com/page-2&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;https://example.com/page-1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;c&quot;&gt;# Duplicate!&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;url&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;urls_to_crawl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Skipping (already seen): {url}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;bf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Crawling: {url}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Output:&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# Crawling: https://example.com/page-1&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# Crawling: https://example.com/page-2&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# Skipping (already seen): https://example.com/page-1&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Note, this implementation uses cryptographic hashes for simplicity, but in production, you should use other faster hash functions.&lt;/p&gt;

&lt;p&gt;Notice the use of the &lt;strong&gt;Kirsch-Mitzenmacher trick&lt;/strong&gt;: instead of implementing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;k&lt;/code&gt; fully independent hash functions (expensive and complex), we use two base hashes and linearly combine them: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;h(i) = h1 + i * h2&lt;/code&gt;. It’s been mathematically proven to achieve asymptotically the same error rate as truly independent hashes — another layer of elegance.&lt;/p&gt;

&lt;h2 id=&quot;why-this-is-actually-mind-blowing&quot;&gt;Why This is Actually Mind-Blowing&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Memory efficiency that borders on magical&lt;/strong&gt;: You’re representing the possible presence of millions of items in a structure orders of magnitude smaller than the items themselves. You’re storing &lt;em&gt;shadows&lt;/em&gt; of items, not the items.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Constant time, always&lt;/strong&gt;: Both &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;add&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;contains&lt;/code&gt; are &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;O(k)&lt;/code&gt; — constant with respect to the number of elements in the filter. Whether you’ve inserted ten items or ten billion, each query runs through exactly &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;k&lt;/code&gt; hash functions and looks up &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;k&lt;/code&gt; bits. No searching, no sorting, no tree traversal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tunable trade-offs&lt;/strong&gt;: The relationship between memory, speed, and accuracy is explicit and mathematical. You’re not guessing — you’re engineering. Want 0.1% false positives instead of 1%? The formula tells you exactly how many more bits you need.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Asymmetric error&lt;/strong&gt;: False negatives are impossible. This asymmetry is often exactly what you want. In caching, in spam filtering, in security — you’d much rather occasionally check something twice than miss something you should have caught.&lt;/p&gt;

&lt;h2 id=&quot;the-real-world-roster&quot;&gt;The Real-World Roster&lt;/h2&gt;

&lt;p&gt;Bloom Filters aren’t academic curiosities. They’re quietly running in systems you use every day:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Google Chrome’s Safe Browsing&lt;/strong&gt;: Before making a network call to check if a URL is malicious, Chrome checks a local Bloom Filter. If it’s definitely not in the filter, no network call needed. Only potential hits require verification. This makes Safe Browsing both fast and private.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Apache Cassandra and other databases&lt;/strong&gt;: Cassandra uses Bloom Filters to avoid disk reads. Before checking whether a row exists in an SSTable (which requires an expensive I/O operation), it checks the filter. A definite miss skips the disk entirely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bitcoin&lt;/strong&gt;: Bitcoin’s SPV (Simple Payment Verification) nodes use Bloom Filters to request only relevant transactions from full nodes, without revealing exactly which addresses they’re interested in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Akamai CDN&lt;/strong&gt;: One of the largest CDN operators uses Bloom Filters to avoid caching “one-hit-wonders” — URLs requested exactly once, which would pollute the cache without providing value to future users. The filter tracks whether a URL has been seen before; it only gets cached on the second hit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Medium, Reddit, and recommendation systems&lt;/strong&gt;: To avoid showing you the same article twice, these platforms can use Bloom Filters to track what you’ve already seen — per user, at scale, with negligible memory overhead.&lt;/p&gt;

&lt;h2 id=&quot;the-limitations-honesty-is-part-of-the-package&quot;&gt;The Limitations (Honesty Is Part of the Package)&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;No deletions (mostly)&lt;/strong&gt;: Once a bit is set to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1&lt;/code&gt;, you can’t safely set it back to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0&lt;/code&gt; — other elements might share that bit. If you need deletions, you need a &lt;strong&gt;Counting Bloom Filter&lt;/strong&gt;, which stores counters instead of bits (incrementing on add, decrementing on remove). This trades some memory efficiency for mutability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No retrieval&lt;/strong&gt;: A Bloom Filter tells you &lt;em&gt;whether&lt;/em&gt; something is in the set, not &lt;em&gt;what&lt;/em&gt; is in the set. You can’t iterate over elements. You can’t get an element back. It’s pure membership testing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One-way resizing&lt;/strong&gt;: Unlike a hash table, you can’t cleanly resize a Bloom Filter. If you add far more elements than planned, your false positive rate degrades. You’d need to rebuild from scratch. Plan your capacity upfront, or use &lt;strong&gt;scalable Bloom Filters&lt;/strong&gt; — a layered approach that adds new filter layers as capacity fills.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hash function quality matters&lt;/strong&gt;: Poor hash functions lead to clustering — many items mapping to the same bits — which inflates the false positive rate. Use cryptographic or well-tested non-cryptographic hashes (MurmurHash3, xxHash, FNV-1a) in production. The Kirsch-Mitzenmacher trick helps you get away with two good hashes instead of implementing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;k&lt;/code&gt; fully independent ones.&lt;/p&gt;

&lt;h2 id=&quot;the-philosophical-bit&quot;&gt;The Philosophical Bit&lt;/h2&gt;

&lt;p&gt;Here’s what I find most elegant about Bloom Filters: they institutionalize the right kind of uncertainty.&lt;/p&gt;

&lt;p&gt;Most data structures pretend certainty. Hash tables say “definitely yes” or “definitely no.” Binary search trees say “this is exactly where it is.” That’s usually what you want.&lt;/p&gt;

&lt;p&gt;But Bloom Filters say something more nuanced: &lt;em&gt;“I can tell you for certain when something is NOT here. As for whether it IS here — I’m probably right, but you might want to double-check.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This asymmetric honesty is surprisingly powerful. In many real systems, a definite “no” is more valuable than a certain “yes” — because “no” lets you skip expensive work entirely.&lt;/p&gt;

&lt;p&gt;It’s a bit like a good bouncer who never lets innocent people through by mistake — but occasionally turns away someone with a slightly unusual ID. The asymmetry is the feature, not the bug.&lt;/p&gt;

&lt;h2 id=&quot;try-it-yourself&quot;&gt;Try It Yourself&lt;/h2&gt;

&lt;p&gt;Here’s a small experiment to make the math tangible:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;table style=&quot;border-spacing: 0&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot; style=&quot;text-align: right&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;math&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;hashlib&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;random&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;string&lt;/span&gt;


&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;make_bloom_filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;capacity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;error_rate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;math&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ceil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;capacity&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;math&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;error_rate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;math&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;max&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;round&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;capacity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;math&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;bits&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bytearray&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;math&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ceil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;hashes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;h1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hashlib&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;md5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;encode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hexdigest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;h2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hashlib&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sha1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;encode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hexdigest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;h1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;h2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;idx&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hashes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;bits&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;idx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;//&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;idx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bits&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;idx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;//&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;idx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;idx&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hashes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;


&lt;span class=&quot;c&quot;&gt;# Create a filter for 10,000 items at 1% false positive rate&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;make_bloom_filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.01&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Bit array: {m:,} bits | Hash functions: {k}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Add 10,000 random &quot;known&quot; items&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;known&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;word&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;choices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ascii_lowercase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;known&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;word&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;word&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Test 10,000 &quot;unknown&quot; items and count false positives&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;false_positives&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;trials&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_000&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;trials&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;word&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;choices&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ascii_lowercase&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;  &lt;span class=&quot;c&quot;&gt;# longer = unlikely to collide&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;word&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;known&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;word&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;false_positives&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;False positive rate: {false_positives / trials:.2&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;}  (target: 1.00&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;False negatives: 0  (guaranteed by construction)&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Run it yourself and watch the false positive rate hover right around 1% — not by luck, but by math.&lt;/p&gt;

&lt;h2 id=&quot;the-takeaway&quot;&gt;The Takeaway&lt;/h2&gt;

&lt;p&gt;Bloom Filters are the rare algorithm that teaches you something philosophical alongside something practical.&lt;/p&gt;

&lt;p&gt;Practically: you can represent set membership for millions of items in kilobytes of memory, with constant-time operations, by accepting a controllable, asymmetric error rate.&lt;/p&gt;

&lt;p&gt;Philosophically: sometimes the most powerful thing you can do is clearly define &lt;em&gt;what kind of wrong you’re willing to be&lt;/em&gt; — and build that honesty into your system’s architecture.&lt;/p&gt;

&lt;p&gt;A bit array and some hash functions. Probabilistic guarantees that hold up under mathematical scrutiny. Deployed at Google, Cassandra, Bitcoin, and your browser.&lt;/p&gt;

&lt;p&gt;Elegant. Simple. Hiding something powerful in plain sight.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;strong&gt;Further Reading:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://dl.acm.org/doi/10.1145/362686.362692&quot;&gt;Burton Howard Bloom’s original 1970 paper&lt;/a&gt; — only 3 pages long, and surprisingly readable&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.eecs.harvard.edu/~michaelm/postscripts/rsa2008.pdf&quot;&gt;Kirsch &amp;amp; Mitzenmacher (2006)&lt;/a&gt; — proof that double hashing works as well as independent hashes&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.eecs.harvard.edu/~michaelm/postscripts/im2005b.pdf&quot;&gt;Network Applications of Bloom Filters: A Survey&lt;/a&gt; — an exhaustive look at real-world applications&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Sun, 12 Apr 2026 00:00:00 +0200</pubDate>
        <link>https://extiri.com/blog/posts/elegant-simplicity-bloom-filters</link>
        <guid isPermaLink="true">https://extiri.com/blog/posts/elegant-simplicity-bloom-filters</guid>
        
        <category>fun-facts</category>
        
        <category>The Power of Elegant Simplicity</category>
        
        
      </item>
    
      <item>
        <title>How to Read Notes Aloud (Physical, Handwritten, or Digital)</title>
        <description>&lt;p class=&quot;lead&quot;&gt;
    Yes, even your handwritten notebook. Here’s how to get any notes — paper, PDF, or digital — read aloud on your iPhone, hands-free.
&lt;/p&gt;

&lt;p&gt;Most people searching for how to read notes aloud are thinking of one of two scenarios. Either they have digital files they want to listen to while doing something else, or they have physical notes — a notebook, printed handouts, lecture slides on paper — and they want a way to convert those into audio too. Both are very solvable on iPhone, and the best tool for the job handles both without needing to switch between apps.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;reading-physical-and-handwritten-notes-aloud&quot;&gt;Reading physical and handwritten notes aloud&lt;/h2&gt;

&lt;p&gt;If your notes are on paper, the workflow is simple: photograph the page, let the app convert it to text with OCR, then hit play. &lt;a href=&quot;https://apps.apple.com/pl/app/chitneek-read-aloud/id6749827870&quot;&gt;Chitneek&lt;/a&gt; has a built-in camera scanner that handles this entire chain in one place. You don’t need a separate scanning app, a cloud upload, or any extra steps.&lt;/p&gt;

&lt;div class=&quot;steps&quot;&gt;
    &lt;div class=&quot;step&quot;&gt;
        &lt;div class=&quot;step-num&quot;&gt;1&lt;/div&gt;
        &lt;div class=&quot;step-body&quot;&gt;
            &lt;strong&gt;Open Chitneek and tap the scan button&lt;/strong&gt;
            &lt;p&gt;The built-in scanner opens your camera. Hold it over a page of notes — the app detects edges and captures a clean, deskewed image automatically.&lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;step&quot;&gt;
        &lt;div class=&quot;step-num&quot;&gt;2&lt;/div&gt;
        &lt;div class=&quot;step-body&quot;&gt;
            &lt;strong&gt;Let OCR convert the page to text&lt;/strong&gt;
            &lt;p&gt;The app runs optical character recognition on the image. This works well for printed notes and reasonably neat handwriting.&lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;figure&gt;
  &lt;img src=&quot;https://extiri.com/images/czytnik/scanner-feature-screenshot.webp&quot; alt=&quot;Scanner Feature&quot; style=&quot;height: 500px; width: auto; object-fit: contain; margin: 2rem auto; display: block; border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.1);&quot; /&gt;
  &lt;figcaption&gt;Chitneek&apos;s built-in scanner handles OCR automatically.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;div class=&quot;steps&quot;&gt;
    &lt;div class=&quot;step&quot;&gt;
        &lt;div class=&quot;step-num&quot;&gt;3&lt;/div&gt;
        &lt;div class=&quot;step-body&quot;&gt;
            &lt;strong&gt;Scan additional pages if needed&lt;/strong&gt;
            &lt;p&gt;You can scan multiple pages into a single document, so a whole section of a notebook becomes one continuous listening session rather than a pile of separate files.&lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class=&quot;step&quot;&gt;
        &lt;div class=&quot;step-num&quot;&gt;4&lt;/div&gt;
        &lt;div class=&quot;step-body&quot;&gt;
            &lt;strong&gt;Tap play&lt;/strong&gt;
            &lt;p&gt;Chitneek reads the extracted text aloud using Apple’s natural-sounding TTS voices. Adjust the speed, lock your screen, and listen while you do something else.&lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;callout&quot;&gt;
    &lt;p&gt;&lt;strong&gt;Tip on handwriting quality:&lt;/strong&gt; OCR accuracy depends on how legible the writing is. Print-style handwriting converts well; very cursive or compressed handwriting can produce errors.&lt;/p&gt;
&lt;/div&gt;

&lt;h2 id=&quot;reading-digital-notes-aloud-pdf-epub-and-more&quot;&gt;Reading digital notes aloud (PDF, EPUB, and more)&lt;/h2&gt;

&lt;p&gt;For notes that already exist as files — PDFs exported from Notion, lecture slides saved as PDFs, ebook chapters, Word documents — Chitneek imports and reads them directly, with no scanning needed.&lt;/p&gt;

&lt;p&gt;You can import files from the Files app, share a PDF into Chitneek from any other app using the iOS Share Sheet, or use the in-app browser to download documents directly from the web. Once a file is in your library, it’s available for offline listening any time, with no internet connection required.&lt;/p&gt;

&lt;h3 id=&quot;what-the-playback-experience-looks-like&quot;&gt;What the playback experience looks like&lt;/h3&gt;

&lt;p&gt;Chitneek reads your documents with synchronized text tracking — the current sentence is highlighted as it’s spoken, which helps if you want to follow along visually. Background playback keeps the audio going when you lock your screen or switch apps, so you can commute, walk, or cook without touching your phone again.&lt;/p&gt;

&lt;p&gt;Speed and pitch are both adjustable. Most people doing review listening settle somewhere between 1.2x and 1.5x for comfortable comprehension without mental fatigue.&lt;/p&gt;

&lt;div class=&quot;feature-grid&quot;&gt;
    &lt;div class=&quot;feature-card&quot;&gt;
        &lt;span class=&quot;icon&quot;&gt;🌍&lt;/span&gt;
        &lt;strong&gt;In-app translation&lt;/strong&gt;
        &lt;p&gt;Translate a PDF into another language before listening — useful for language learners or anyone working with foreign documents.&lt;/p&gt;
    &lt;/div&gt;
    &lt;div class=&quot;feature-card&quot;&gt;
        &lt;span class=&quot;icon&quot;&gt;👥&lt;/span&gt;
        &lt;strong&gt;Shared groups&lt;/strong&gt;
        &lt;p&gt;Create a shared library with classmates or study partners. Everyone can upload documents and listen; individual progress stays private.&lt;/p&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;cta-box&quot;&gt;
    &lt;h2&gt;Try Chitneek free for 7 days&lt;/h2&gt;
    &lt;p&gt;Scan physical notes, import PDFs, listen offline. Works on iPhone with iOS 16.6 or later.&lt;/p&gt;
    &lt;a href=&quot;https://apps.apple.com/pl/app/chitneek-read-aloud/id6749827870&quot; class=&quot;cta-btn&quot;&gt;Download on the App Store&lt;/a&gt;
&lt;/div&gt;

&lt;h2 id=&quot;tips-that-actually-improve-how-much-you-retain&quot;&gt;Tips that actually improve how much you retain&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Review material you’ve already seen once&lt;/strong&gt;: Listening is most effective as a second pass, not a first encounter.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Clean up notes briefly before scanning&lt;/strong&gt;: A quick check of OCR results ensures a coherent audio experience.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Stay between 1.2x and 1.5x speed&lt;/strong&gt;: Speed is good, but comprehension is better.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Walk while you listen&lt;/strong&gt;: Light movement has been shown to improve focus and memory recall during auditory learning.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;callout-neutral&quot;&gt;
    &lt;p&gt;&lt;strong&gt;Voice quality matters.&lt;/strong&gt; Go to Settings → Accessibility → Spoken Content → Voices and download an Enhanced or Premium voice for a much more natural-sounding experience.&lt;/p&gt;
&lt;/div&gt;
</description>
        <pubDate>Sun, 05 Apr 2026 00:00:00 +0200</pubDate>
        <link>https://extiri.com/blog/posts/how-to-read-notes-aloud</link>
        <guid isPermaLink="true">https://extiri.com/blog/posts/how-to-read-notes-aloud</guid>
        
        <category>productivity</category>
        
        <category>chitneek</category>
        
        <category>extiri</category>
        
        
      </item>
    
      <item>
        <title>CodeMenu 1.7 makes your snippet library more useful</title>
        <description>&lt;p&gt;CodeMenu 1.7 is a release about getting more value out of the snippets you already have. Storing code is only half of the job. The harder part is finding the right thing at the right moment, reusing it in a way that fits your current work, and connecting your library to the rest of your workflow. This update pushes that side of the app forward with snippet-aware AI, better automation hooks, iCloud sync, and more integration features across macOS.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;chat-with-your-snippet-library&quot;&gt;Chat with your snippet library&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/blog/assets/img/ai-demo-codemenu.png&quot; alt=&quot;Demo of AI chat in CodeMenu&quot; /&gt;&lt;/p&gt;

&lt;p&gt;One of the biggest changes in CodeMenu 1.7 is the new snippet-aware AI workflow. In the side window, AI mode can now search through your library while it responds. There is also a dedicated AI view in the Dashboard, so these conversations no longer have to feel like a small side feature tucked away in another panel.&lt;/p&gt;

&lt;p&gt;What makes this useful is not just that there is an AI prompt inside the app. The AI can actively pull snippets into the conversation, keep track of what it is currently using as context, and surface snippets it found during the chat. That turns CodeMenu into something closer to a searchable working memory for your code.&lt;/p&gt;

&lt;p&gt;The practical use cases are easy to imagine. You might remember that you solved a tricky API problem a few months ago, but not what you called the snippet or where you filed it. You can describe the behavior instead of guessing keywords. You can ask the AI to compare two saved approaches before you reuse one. You can start from an old snippet and ask for a refactor, a cleaner version, or an explanation of how it works. You can also use it when you know you have solved something before, but the details are gone from your head.&lt;/p&gt;

&lt;h2 id=&quot;ollama-support-for-integrated-ai&quot;&gt;Ollama support for integrated AI&lt;/h2&gt;

&lt;p&gt;CodeMenu 1.7 also expands the local AI story by adding Ollama as an integrated provider. In settings, local AI is no longer limited to the built-in path. You can choose between the built-in local model and Ollama, point CodeMenu at an Ollama endpoint, pick an installed model, or enter a model manually.&lt;/p&gt;

&lt;p&gt;That matters because local AI setups are not one-size-fits-all. Some people want the simplest built-in experience. Others already run Ollama and want their existing models to do the work. With this release, CodeMenu is more flexible about that choice while keeping the experience inside the app.&lt;/p&gt;

&lt;p&gt;In practice, this means you can keep snippet-aware AI closer to your own machine and your own stack. If you already use Ollama for development work, you do not have to treat CodeMenu as a separate island. If you prefer a specific local model for speed or style, CodeMenu can fit into that setup instead of forcing you into a single path.&lt;/p&gt;

&lt;h2 id=&quot;shortcuts-app-support&quot;&gt;Shortcuts app support&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/blog/assets/img/shortcuts-codemenu.png&quot; alt=&quot;Shortcuts supported by CodeMenu&quot; /&gt;&lt;/p&gt;

&lt;p&gt;CodeMenu 1.7 now supports the Shortcuts app through App Shortcuts and App Intents for snippets, tags, and groups. That includes actions for creating snippets, searching snippets, listing snippets, creating tags, creating groups, and moving snippets into groups.&lt;/p&gt;

&lt;p&gt;This is an important step because snippet management becomes much more powerful once it can participate in system automation. A snippet manager should not only wait for you to open it. It should also be able to respond to the way you already work on macOS.&lt;/p&gt;

&lt;p&gt;There are a lot of practical directions this can go. You can capture a snippet from a Shortcut built around selected text, browser content, or clipboard data. You can create voice-driven or menu bar flows for organizing your library. You can build personal automations that drop incoming material into the right group or tag structure. For people who like connecting apps together, CodeMenu is now a much better citizen of the broader macOS workflow.&lt;/p&gt;

&lt;h2 id=&quot;icloud-synchronization&quot;&gt;iCloud synchronization&lt;/h2&gt;

&lt;p&gt;Another major addition in 1.7 is iCloud synchronization. CodeMenu now includes iCloud-based sync infrastructure.&lt;/p&gt;

&lt;p&gt;This is the kind of feature that disappears when it works well, which is usually a good sign. If you use CodeMenu on more than one Mac, the value is immediate. Your snippets and related library data are no longer tied to one machine and one local state. You can move from a work Mac to a personal Mac, or from a desktop setup to a laptop, without treating export and import as part of the routine.&lt;/p&gt;

&lt;p&gt;For a tool that is meant to act like an external memory, that matters a lot. The less friction there is between your machines, the easier it is to trust the library as the place where things live.&lt;/p&gt;

&lt;h2 id=&quot;better-http-server-integrations&quot;&gt;Better HTTP server integrations&lt;/h2&gt;

&lt;p&gt;CodeMenu 1.7 also improves the built-in HTTP server with new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET /v1/snippet&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;POST /v1/snippet&lt;/code&gt; endpoints. That sounds small on paper, but it makes integrations much more practical when another tool only needs to work with one specific snippet at a time or wants to create a new one directly.&lt;/p&gt;

&lt;p&gt;The value here is simplicity. Companion tools, editor helpers, and lightweight scripts usually do not want to pull an entire list and filter it themselves when all they need is one result. They also benefit from being able to create a snippet in a single request when something useful appears elsewhere in your workflow.&lt;/p&gt;

&lt;p&gt;That opens up cleaner ways to wire CodeMenu into other developer tools. A helper can fetch one exact snippet by ID. A script can create a new snippet from generated output, captured text, or another app’s result. The HTTP server becomes a little more composable, which usually means it becomes a lot more usable.&lt;/p&gt;

&lt;h2 id=&quot;smaller-improvements&quot;&gt;Smaller improvements&lt;/h2&gt;

&lt;p&gt;There are a few smaller changes worth mentioning too. Deleting a parent group now removes its subgroups as well, which makes nested organization behave much more predictably. The preferences window resizing behavior has been cleaned up so it stays anchored more naturally. The side window header has also received polish for light mode, which helps one of CodeMenu’s most frequently used surfaces feel more consistent.&lt;/p&gt;

&lt;p&gt;This release also continues the move away from the older utilities window. That part of the app is being deprecated and is no longer where the product direction is headed. The emphasis is now much more clearly on the main snippet workflows, integrated AI, automation, and the surfaces people use every day.&lt;/p&gt;

&lt;p&gt;CodeMenu 1.7 is a focused release, but it moves the app in an important direction. If you already have a library of snippets inside CodeMenu, this is a good time to revisit it. The app is getting better at helping you search it, talk to it, automate around it, and carry it with you across the rest of your setup.&lt;/p&gt;
</description>
        <pubDate>Sun, 05 Apr 2026 00:00:00 +0200</pubDate>
        <link>https://extiri.com/blog/posts/codemenu-1-7-release</link>
        <guid isPermaLink="true">https://extiri.com/blog/posts/codemenu-1-7-release</guid>
        
        <category>codemenu</category>
        
        <category>extiri</category>
        
        
      </item>
    
      <item>
        <title>Building an Image Compressor with the Discrete Fourier Transform</title>
        <description>&lt;p&gt;A few weeks ago, I was flipping through one of my old programming magazines when I stumbled across an interesting claim: you can compress images using the Discrete Fourier Transform. The idea seemed almost too elegant to be true - transform an image into the frequency domain, throw away the small stuff, and reconstruct something that still looks decent. This is apparently one of the core mechanisms behind JPEG compression.&lt;/p&gt;

&lt;p&gt;I had to try it myself.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;The result is a Python script that compresses BMP files into a custom “IMGX” format. It’s not going to replace modern codecs anytime soon, but it works, and building it taught me more about signal processing than any textbook ever did. If you want to see the code, the whole experiment is &lt;a href=&quot;https://github.com/wiktorwojcik112/fourier_compress&quot;&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog/assets/img/compression-experiment.jpeg&quot; alt=&quot;A comparison of the experiment at two steps&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;what-is-the-discrete-fourier-transform&quot;&gt;What is the Discrete Fourier Transform?&lt;/h2&gt;

&lt;p&gt;Before diving into the implementation, it helps to understand what the DFT actually does. At its core, the DFT takes a signal - in this case, the pixel values in an image - and decomposes it into a sum of sine and cosine waves at different frequencies.&lt;/p&gt;

&lt;p&gt;Think of it like this: imagine you have a complicated audio waveform. The DFT tells you how much of each musical note (frequency) is present in that sound. For images, instead of sound waves, you’re analyzing how quickly pixel values change across the image. Smooth gradients have low frequencies, while sharp edges and fine details have high frequencies.&lt;/p&gt;

&lt;p&gt;The key insight for compression is that most natural images have most of their “energy” concentrated in the low frequencies. The high-frequency components - the ones representing fine details - tend to have much smaller amplitudes. If we keep only the frequencies with the largest amplitudes and discard the rest, we can reconstruct an image that looks pretty similar to the original, but with far less data.&lt;/p&gt;

&lt;h2 id=&quot;the-first-attempt-bigger-than-the-original&quot;&gt;The First Attempt: Bigger Than the Original&lt;/h2&gt;

&lt;p&gt;My initial implementation was straightforward: apply a 1D DFT to each color channel, keep the top 70% of frequency components by amplitude, and discard the rest. The reconstructed image looked great. There was just one problem: the compressed file was &lt;em&gt;larger&lt;/em&gt; than the original BMP.&lt;/p&gt;

&lt;p&gt;This was frustrating, but not entirely surprising. Each pixel in a BMP uses 3 bytes (one per RGB channel). Each complex frequency value in the DFT, however, requires storing both a real and imaginary component as floating-point numbers - 8 bytes each, for a total of 16 bytes. Even keeping only 70% of the values meant I was using more space than before.&lt;/p&gt;

&lt;p&gt;I dropped the threshold to 10% to reduce file size. The image quality suffered significantly, but the file was still bigger than the BMP. Something was fundamentally wrong.&lt;/p&gt;

&lt;p&gt;Then I realized my mistake: I had assumed the first 70% of DFT values were the largest ones. They weren’t. The DFT doesn’t output values sorted by magnitude - I needed to explicitly find and keep only the top frequencies. Once I fixed that, things started to improve, but I still wasn’t beating BMP compression.&lt;/p&gt;

&lt;h2 id=&quot;making-it-actually-work&quot;&gt;Making It Actually Work&lt;/h2&gt;

&lt;p&gt;Getting the file size down required three key improvements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First: 2D DFT instead of 1D.&lt;/strong&gt; Originally, I was treating each row of pixels independently and applying a 1D DFT. But images are inherently two-dimensional, and a 2D DFT captures spatial patterns more efficiently. Instead of transforming rows separately, the 2D DFT analyzes the image as a whole, identifying patterns in both horizontal and vertical directions. This meant fewer frequency components were needed to represent the same information.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second: Quantization.&lt;/strong&gt; Even with the 2D DFT, each frequency component still required far more bytes than a single pixel. I needed to reduce the precision of the stored values. By finding the maximum amplitude in each channel and scaling all values relative to that maximum, I could represent each component with just 2 bytes for the real part and 2 bytes for the imaginary part - 4 bytes total instead of 16. The trick was finding a quantization level that preserved image quality without wasting bits. After some experimentation, I settled on a quantization factor that brought the file size down significantly without introducing too many visual artifacts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third: Exploiting DFT symmetry.&lt;/strong&gt; This was the real breakthrough. The DFT has a mathematical property: when the input is real-valued (like pixel intensities), the output frequencies are symmetric. Specifically, half of the frequency components are complex conjugates of the other half. This means they contain the same information - just with the imaginary part flipped in sign.&lt;/p&gt;

&lt;p&gt;Since conjugate pairs have the same magnitude, if one frequency is in the top 10%, its conjugate almost certainly is too. Instead of storing both, I could store just half the frequencies and reconstruct the conjugates during decompression. This nearly halved the number of values I needed to save.&lt;/p&gt;

&lt;h2 id=&quot;the-results&quot;&gt;The Results&lt;/h2&gt;

&lt;p&gt;After implementing all three improvements, the compression finally worked. I tested it on a 16.8 MB BMP image, and the compressed IMGX file came out to 8.8 MB - a reduction of about 48%. The decompressed image isn’t pixel-perfect, but it’s remarkably close to the original, especially considering only 10% of the frequency information was kept.&lt;/p&gt;

&lt;p&gt;For reference, modern JPEG compression would do much better on the same image, but that’s not really the point. This was never about building a production-ready codec. It was about understanding how frequency-domain compression actually works, and seeing firsthand why choices like 2D transforms and quantization matter.&lt;/p&gt;

&lt;h2 id=&quot;what-i-learned&quot;&gt;What I Learned&lt;/h2&gt;

&lt;p&gt;The biggest lesson from this project was that compression algorithms work thanks to careful exploitation of patterns in data. Images happen to have most of their information in low frequencies, so discarding high frequencies works. Audio compression uses similar principles. Text compression exploits the fact that some words appear more often than others.&lt;/p&gt;

&lt;p&gt;I also learned that the devil really is in the details. The difference between “doesn’t work at all” and “works reasonably well” came down to a few specific implementation choices: using a 2D transform, quantizing carefully, and leveraging symmetry.&lt;/p&gt;

&lt;p&gt;If you’re curious about the implementation details, the full source code is available at &lt;a href=&quot;https://github.com/wiktorwojcik112/fourier_compress&quot;&gt;github.com/wiktorwojcik112/fourier_compress&lt;/a&gt;. The compression script takes a BMP file and outputs an IMGX format, which you can then decompress back to BMP to view the result.&lt;/p&gt;

&lt;p&gt;Would I use this for real image compression? Absolutely not. But building it gave me a much deeper appreciation for the algorithms we use every day and for the clever mathematical insights that make them possible.&lt;/p&gt;
</description>
        <pubDate>Sun, 08 Mar 2026 00:00:00 +0100</pubDate>
        <link>https://extiri.com/blog/posts/experiment-compressing-images-with-dft</link>
        <guid isPermaLink="true">https://extiri.com/blog/posts/experiment-compressing-images-with-dft</guid>
        
        <category>experiments</category>
        
        <category>projects</category>
        
        
      </item>
    
      <item>
        <title>Teaching Machines to Smell Danger: A Fun Dive Into ML-Powered Threat Detection</title>
        <description>&lt;p&gt;Every now and then at Extiri, between shipping apps and squashing bugs, I like to take a detour into a completely different corner of tech — just to see what happens. At my university there was a statistics project that could be made so it served as ane xcuse to work wht ML. So this time, the question was: &lt;em&gt;can I teach a machine learning model to sniff out network attacks?&lt;/em&gt; Spoiler: I got it to 97% accuracy, learned a ton, and had a surprisingly good time doing it.&lt;/p&gt;

&lt;p&gt;Here’s how this little research adventure played out.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;the-spark-why-even-do-this&quot;&gt;The Spark: Why Even Do This?&lt;/h2&gt;

&lt;p&gt;Network security is, at its heart, a massive pattern recognition puzzle. Attackers leave fingerprints everywhere — weird packet sizes, suspicious timing, oddly shaped data flows. The catch? These clues are buried under mountains of perfectly normal “someone’s watching YouTube” traffic, and they change all the time.&lt;/p&gt;

&lt;p&gt;Classic rule-based systems are fine, but they’re a bit like a guard dog that only barks at people wearing the exact same hat as the last burglar. Machine learning, on the other hand, can learn the subtle statistical vibe of “everything’s fine” versus “something is definitely off.”&lt;/p&gt;

&lt;p&gt;That sounded like a fun experiment. So I grabbed a dataset, fired up a Jupyter notebook, and went to work.&lt;/p&gt;

&lt;p&gt;You can find all the code on &lt;a href=&quot;https://github.com/wiktorwojcik112/threat-detection-project&quot;&gt;GitHub&lt;/a&gt; if you want to follow along or poke holes in my methodology (feedback welcome!).&lt;/p&gt;

&lt;h2 id=&quot;the-playground-28-million-network-flows&quot;&gt;The Playground: 2.8 Million Network Flows&lt;/h2&gt;

&lt;p&gt;For data, I used the CICIDS 2017 dataset — a week-long capture of real network traffic where security researchers were actively staging different attacks alongside normal activity.&lt;/p&gt;

&lt;p&gt;The numbers are huge: over &lt;strong&gt;2.8 million&lt;/strong&gt; network flows, each packed with features like:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Packet lengths and timing&lt;/li&gt;
  &lt;li&gt;Flow duration&lt;/li&gt;
  &lt;li&gt;Forward and backward packet statistics&lt;/li&gt;
  &lt;li&gt;Inter-arrival times&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the best part — every single flow is labeled as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BENIGN&lt;/code&gt; or one of &lt;strong&gt;14 different attack types&lt;/strong&gt; (DDoS, Port Scan, SQL Injection… the whole villain roster).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog/assets/threat-detection/statistic_analysis_of_threat_detection-2.jpeg&quot; alt=&quot;Overview of the CICIDS 2017 dataset&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;poking-around-the-detective-phase&quot;&gt;Poking Around: The Detective Phase&lt;/h2&gt;

&lt;p&gt;Before letting any algorithm loose on the data, I wanted to actually &lt;em&gt;understand&lt;/em&gt; what I was looking at. This turned out to be the most interesting part.&lt;/p&gt;

&lt;h3 id=&quot;a-lopsided-world&quot;&gt;A Lopsided World&lt;/h3&gt;

&lt;p&gt;First fun fact: the dataset is overwhelmingly benign traffic. Which makes total sense — most real networks are boring most of the time. But it immediately means a model that just shrugs and says “looks fine to me” for every single packet would score high on accuracy while being spectacularly useless. Noted.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog/assets/threat-detection/statistic_analysis_of_threat_detection-4.jpeg&quot; alt=&quot;Class distribution showing heavy skew toward benign traffic&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;which-features-actually-matter&quot;&gt;Which Features Actually Matter?&lt;/h3&gt;

&lt;p&gt;I ran correlations between every feature and the target variable to find out which network characteristics are the best attack detectors. Ten features rose to the top, including:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Backward packet length statistics&lt;/strong&gt; (standard deviation, max, mean)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Packet length variance&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Inter-arrival time patterns&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Average packet sizes&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/blog/assets/threat-detection/statistic_analysis_of_threat_detection-6.jpeg&quot; alt=&quot;Feature correlation analysis&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Here’s where it gets nerdy (in a good way): these top features had &lt;em&gt;massive&lt;/em&gt; standard deviations — some in the millions — and extreme positive skewness. In plain English? Most values huddle near zero, but there are wild outliers stretching way out into the distance. The data is &lt;em&gt;leptokurtic&lt;/em&gt;, which is a word I don’t get to use nearly often enough.&lt;/p&gt;

&lt;p&gt;It makes intuitive sense though. Normal browsing = small, regular packets. DDoS attack = a firehose of chaotic bursts.&lt;/p&gt;

&lt;h3 id=&quot;do-attacks-actually-look-different&quot;&gt;Do Attacks Actually Look Different?&lt;/h3&gt;

&lt;p&gt;I plotted the distributions of these key features for benign vs. malicious traffic side by side, and — oh yes — the difference jumped right off the screen.&lt;/p&gt;

&lt;p&gt;Benign traffic: smooth exponential decay, lots of tiny values, quickly tapering off. Malicious traffic: similar shape, but with telltale spikes at larger values. The attacks were basically wearing neon signs, statistically speaking.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog/assets/threat-detection/statistic_analysis_of_threat_detection-10.jpeg&quot; alt=&quot;Distribution comparison between benign and malicious traffic&quot; /&gt;&lt;/p&gt;

&lt;p&gt;But I didn’t want to just trust my eyeballs. So I ran Kolmogorov-Smirnov tests to compare the distributions formally. The p-values came back so close to zero that Python basically shrugged and said “yeah, these are not the same.” The benign and malicious features live in genuinely different statistical universes.&lt;/p&gt;

&lt;p&gt;Green light. If the math says they’re different, machine learning should be able to find the boundary.&lt;/p&gt;

&lt;p&gt;There was, however, a problem due to the both types - benign and malicious - 
having nearly identical spikes at the low end of the distribution. In those small-value ranges, benign and malicious traffic are practically indistinguishable — you’d need additional features (or deeper packet-level data) to tell them apart. I decided to accept that limitation and file it under “things to improve later.”&lt;/p&gt;

&lt;h2 id=&quot;the-showdown-four-models-enter-one-wins&quot;&gt;The Showdown: Four Models Enter, One Wins&lt;/h2&gt;

&lt;p&gt;Time for the fun part — the model bake-off. I trained four contenders:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Logistic Regression&lt;/strong&gt; — the reliable baseline, the control group of ML&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Random Forest&lt;/strong&gt; — a whole crowd of decision trees voting together&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Gradient Boosting&lt;/strong&gt; — learns from its mistakes, one tree at a time&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;AdaBoost&lt;/strong&gt; — keeps throwing more attention at the hard cases&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All features were standardized first (mean 0, standard deviation 1), because some of those wild outliers would otherwise hijack the learning process. The data has been split into two sets - training and testing - so that I could test the model with data it has never seen. The sets used the “stratify” option to guarantee that each type of traffic will appear with the same proportions as the original.&lt;/p&gt;

&lt;p&gt;The metric I cared about most? &lt;strong&gt;F1 score for the malicious class.&lt;/strong&gt; In security, you’re always balancing two headaches:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Recall&lt;/strong&gt;: Catch as many real attacks as possible (don’t let the bad guys through)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Precision&lt;/strong&gt;: Don’t flood the security team with false alarms (the team might not be big enough)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;F1 is the harmonic mean of both — one number that captures the trade-off nicely.&lt;/p&gt;

&lt;h3 id=&quot;results-and-the-winner-is&quot;&gt;Results: And the Winner Is…&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Model&lt;/th&gt;
      &lt;th&gt;Accuracy&lt;/th&gt;
      &lt;th&gt;Malicious F1&lt;/th&gt;
      &lt;th&gt;Malicious Precision&lt;/th&gt;
      &lt;th&gt;Malicious Recall&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Random Forest&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;97%&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;0.92&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;0.99&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;&lt;strong&gt;0.87&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Gradient Boosting&lt;/td&gt;
      &lt;td&gt;97%&lt;/td&gt;
      &lt;td&gt;0.91&lt;/td&gt;
      &lt;td&gt;0.98&lt;/td&gt;
      &lt;td&gt;0.84&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;AdaBoost&lt;/td&gt;
      &lt;td&gt;95%&lt;/td&gt;
      &lt;td&gt;0.86&lt;/td&gt;
      &lt;td&gt;0.95&lt;/td&gt;
      &lt;td&gt;0.79&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Logistic Regression&lt;/td&gt;
      &lt;td&gt;89%&lt;/td&gt;
      &lt;td&gt;0.60&lt;/td&gt;
      &lt;td&gt;0.98&lt;/td&gt;
      &lt;td&gt;0.43&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;img src=&quot;/blog/assets/threat-detection/statistic_analysis_of_threat_detection-21.jpeg&quot; alt=&quot;Model performance comparison&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Random Forest&lt;/strong&gt; took the crown with an F1 of 0.92. Some highlights:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;99% precision&lt;/strong&gt; on malicious traffic — when it says “attack,” it means it&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;100% recall&lt;/strong&gt; on benign traffic — zero false truth on normal activity (!)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;87% recall&lt;/strong&gt; on malicious traffic — catches 87 out of every 100 attacks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That 13% miss rate is the price of keeping false truths at essentially zero. For many real-world setups, that’s a trade-off most security teams would happily take.&lt;/p&gt;

&lt;h3 id=&quot;why-did-random-forest-win&quot;&gt;Why Did Random Forest Win?&lt;/h3&gt;

&lt;p&gt;Random Forests build hundreds of decision trees, each trained on a slightly different slice of the data, and then let them vote. It’s democracy applied to machine learning, and it turns out to be great at:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Wrangling high-dimensional data with complex interactions&lt;/li&gt;
  &lt;li&gt;Staying cool around outliers (those extreme values we found earlier)&lt;/li&gt;
  &lt;li&gt;Not overfitting, thanks to built-in regularization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Essentially, the model learned hundreds of different “if this packet looks like &lt;em&gt;that&lt;/em&gt;, be suspicious” rules and combined them into one robust detector. Wisdom of the (tree) crowd.&lt;/p&gt;

&lt;h2 id=&quot;bonus-round-what-kind-of-attack-is-it&quot;&gt;Bonus Round: What &lt;em&gt;Kind&lt;/em&gt; of Attack Is It?&lt;/h2&gt;

&lt;p&gt;Knowing “something’s wrong” is step one. But &lt;em&gt;what&lt;/em&gt; exactly is wrong? That’s what security teams really need. So I trained a second Random Forest to classify the specific attack type — and it did surprisingly well:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;99% overall accuracy&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Perfect F1 scores (1.00) for attacks like FTP-Patator, Heartbleed, Infiltration, and PortScan&lt;/li&gt;
  &lt;li&gt;Near-perfect results on DDoS, DoS variants, and Bot traffic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/blog/assets/threat-detection/statistic_analysis_of_threat_detection-23.jpeg&quot; alt=&quot;Attack type classification results&quot; /&gt;&lt;/p&gt;

&lt;p&gt;But — and there’s always a but — web attacks gave it trouble. XSS landed at 0.36 F1, SQL Injection at 0.60, and Web Brute Force at 0.66. The model kept mixing them up, which actually makes sense: from a network-flow perspective, these attacks probably look pretty similar. To truly tell them apart, you’d need deeper packet inspection or application-layer features that this dataset doesn’t capture.&lt;/p&gt;

&lt;h2 id=&quot;where-could-this-go&quot;&gt;Where Could This Go?&lt;/h2&gt;

&lt;p&gt;This was a clean, controlled experiment. Taking it into the real world would mean tackling:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Continuous retraining&lt;/strong&gt; as attack patterns evolve (attackers don’t stand still)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Adversarial robustness&lt;/strong&gt; (what if someone deliberately tries to fool the model?)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Integration&lt;/strong&gt; with actual security tooling&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Explainability&lt;/strong&gt; so humans understand &lt;em&gt;why&lt;/em&gt; something got flagged&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But as a proof of concept? A 97% accurate detector with 99% precision on malicious traffic is a pretty solid starting point — and a really enjoyable way to spend a few weekends.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;em&gt;All the code, statistical tests, confusion matrices, and model comparisons are available in the &lt;a href=&quot;https://github.com/wiktorwojcik112/threat-detection-project&quot;&gt;GitHub repo&lt;/a&gt;. Reproducibility or it didn’t happen.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tools used&lt;/strong&gt;: Python, Scikit-Learn, Pandas, Matplotlib, Seaborn&lt;br /&gt;
&lt;strong&gt;Dataset&lt;/strong&gt;: CICIDS 2017 (2.8M network flows, 14 attack types)&lt;br /&gt;
&lt;strong&gt;Best model&lt;/strong&gt;: Random Forest (F1: 0.92, Accuracy: 97%)&lt;/p&gt;
</description>
        <pubDate>Sun, 08 Feb 2026 00:00:00 +0100</pubDate>
        <link>https://extiri.com/blog/posts/teaching-machines-to-smell-danger</link>
        <guid isPermaLink="true">https://extiri.com/blog/posts/teaching-machines-to-smell-danger</guid>
        
        <category>machine-learning</category>
        
        <category>projects</category>
        
        
      </item>
    
      <item>
        <title>The Power of Elegant Simplicity #1: Shamir&apos;s Secret Sharing - The Polynomial That Guards Your Secrets</title>
        <description>&lt;p&gt;&lt;em&gt;Part of the “The Power of Elegant Simplicity” series - algorithms that hide power in plain sight&lt;/em&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;You know what’s wild? One of the most elegant cryptographic algorithms ever invented uses nothing more than high school algebra. No complex number theory, no quantum mechanics, just polynomials. The kind you probably learned to hate in 9th grade.&lt;/p&gt;

&lt;p&gt;Welcome to Shamir’s Secret Sharing, where a simple quadratic equation becomes the foundation for securing nuclear launch codes, cryptocurrency wallets, and anything else you really, &lt;em&gt;really&lt;/em&gt; don’t want falling into the wrong hands.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;the-problem-trust-no-one-but-trust-everyone-together&quot;&gt;The Problem: Trust No One (But Trust Everyone Together)&lt;/h2&gt;

&lt;p&gt;Imagine you’re a startup founder with the keys to your company’s crypto wallet. If you keep them yourself, you’re one bike accident away from locking everyone out forever. If you give copies to your co-founders, any one of them could clean out the account and disappear to Bali.&lt;/p&gt;

&lt;p&gt;What you need is a way to split a secret so that:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;No single person can reconstruct it alone&lt;/li&gt;
  &lt;li&gt;But any group of, say, 3 out of 5 people can work together to recover it&lt;/li&gt;
  &lt;li&gt;People who don’t have enough shares learn absolutely nothing (not “almost nothing” - literally nothing)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is exactly what Adi Shamir figured out in 1979. And the solution is beautifully simple.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/blog/assets/img/shamir_infographic.png&quot; alt=&quot;A visualization of the algorithm&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;the-trick-its-just-a-polynomial&quot;&gt;The Trick: It’s Just a Polynomial&lt;/h2&gt;

&lt;p&gt;Here’s the entire algorithm in one sentence: &lt;strong&gt;encode your secret as a point on a polynomial, give each person a different point, and use the magic of polynomial interpolation to reconstruct it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That’s it. No, really.&lt;/p&gt;

&lt;p&gt;Let’s break it down with an example where we want 3 people to be able to reconstruct the secret, out of 6 total people (k=3, n=6).&lt;/p&gt;

&lt;h3 id=&quot;step-1-encode-your-secret&quot;&gt;Step 1: Encode Your Secret&lt;/h3&gt;

&lt;p&gt;Let’s say your secret is the number 8 (maybe it’s a password hash, or a piece of an encryption key).&lt;/p&gt;

&lt;p&gt;Create a polynomial of degree k-1 (so degree 2 in our case) where the constant term is your secret:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;table style=&quot;border-spacing: 0&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot; style=&quot;text-align: right&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;f(x) = 3x² + 5x + 8
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;The coefficients 3 and 5? Completely random. Throw dice. Mash your keyboard. Doesn’t matter. But that 8? That’s your secret, hiding in plain sight at f(0).&lt;/p&gt;

&lt;h3 id=&quot;step-2-generate-the-shares&quot;&gt;Step 2: Generate the Shares&lt;/h3&gt;

&lt;p&gt;Now just evaluate your polynomial at different points:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;table style=&quot;border-spacing: 0&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot; style=&quot;text-align: right&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;create_shares&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;secret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;
    Split a secret into n shares where k shares are needed to reconstruct.
    
    Args:
        secret: The secret value to share
        k: Minimum number of shares needed to reconstruct
        n: Total number of shares to create
    &quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;random&lt;/span&gt;
    
    &lt;span class=&quot;c&quot;&gt;# Create a polynomial of degree k-1 with secret as constant term&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;coefficients&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;secret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;randint&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;polynomial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;coef&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;**&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;coef&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;enumerate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;coefficients&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    
    &lt;span class=&quot;c&quot;&gt;# Generate n shares by evaluating polynomial at different points&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;shares&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;polynomial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shares&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Example usage&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;secret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;shares&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;create_shares&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;secret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# With coefficients [8, 5, 3] this gives us:&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# [(1, 16), (2, 30), (3, 50), (4, 76), (5, 108), (6, 146)]&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Give person #1 the point (1, 16), person #2 gets (2, 30), and so on. These are your shares.&lt;/p&gt;

&lt;h3 id=&quot;step-3-the-magic-of-reconstruction&quot;&gt;Step 3: The Magic of Reconstruction&lt;/h3&gt;

&lt;p&gt;Here’s where it gets cool. With fewer than 3 shares, there are &lt;strong&gt;infinitely many&lt;/strong&gt; polynomials that fit through those points. Just like two points define a line but could be part of any parabola, two shares from our 2nd-degree polynomial could match an infinite number of possible secrets.&lt;/p&gt;

&lt;p&gt;But the moment you have 3 shares, boom - there’s exactly one polynomial of degree 2 that fits through all three points. Mathematics locks it in.&lt;/p&gt;

&lt;p&gt;We use Lagrange interpolation to reconstruct the polynomial:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;table style=&quot;border-spacing: 0&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot; style=&quot;text-align: right&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;lagrange_interpolation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shares&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;
    Reconstruct the secret using Lagrange interpolation.
    
    Args:
        shares: List of (x, y) tuples representing shares
    
    Returns:
        The secret (value at x=0)
    &quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;basis_polynomial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Calculate the i-th Lagrange basis polynomial at x&quot;&quot;&quot;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;xi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;yi&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shares&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;yi&lt;/span&gt;
        
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;xj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;yj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;enumerate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shares&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;xi&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;
    
    &lt;span class=&quot;c&quot;&gt;# Evaluate the interpolated polynomial at x=0 to get the secret&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;secret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;basis_polynomial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shares&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;round&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;secret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Reconstruct with any 3 shares&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;secret_recovered&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lagrange_interpolation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;76&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)])&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Secret: {secret_recovered}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c&quot;&gt;# Output: Secret: 8&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;The polynomial gets reconstructed, and f(0) gives us back our secret.&lt;/p&gt;

&lt;h2 id=&quot;why-this-is-actually-mind-blowing&quot;&gt;Why This is Actually Mind-Blowing&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Perfect Security&lt;/strong&gt;: If you have k-1 shares, you have zero information about the secret. Not “very little” - literally zero. The secret could be any value with equal probability. This is information-theoretic security, the same kind that makes one-time pads unbreakable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flexibility&lt;/strong&gt;: Want to go from 5 shares to 10? Just evaluate the same polynomial at new points — instant new shares. Want to update the secret entirely? Generate a new polynomial and distribute fresh shares. One caveat: if you need to change the &lt;em&gt;threshold&lt;/em&gt; (say, from “3 of 5” to “4 of 7”), that means a new polynomial of a different degree, so you’d need to redistribute all shares.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Simplicity&lt;/strong&gt;: The entire algorithm fits in a tweet. Okay, maybe a long tweet. But still.&lt;/p&gt;

&lt;h2 id=&quot;real-world-applications&quot;&gt;Real-World Applications&lt;/h2&gt;

&lt;p&gt;This isn’t just a clever party trick. Shamir’s Secret Sharing is used in:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Cryptocurrency wallets&lt;/strong&gt;: Splitting recovery seeds across multiple devices or people&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Certificate authorities&lt;/strong&gt;: Requiring multiple executives to sign off on root certificates&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Nuclear launch codes&lt;/strong&gt;: Academic example because you definitely want more than one person to agree on this&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Distributed password managers&lt;/strong&gt;: Like Hashicorp Vault’s unseal process&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Threshold signatures&lt;/strong&gt;: Multiple parties cooperating to sign transactions&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;the-catch-theres-always-a-catch&quot;&gt;The Catch (There’s Always a Catch)&lt;/h2&gt;

&lt;p&gt;Like most cryptographic primitives, the devil is in the implementation details:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Random number generation matters&lt;/strong&gt;: Those random coefficients need to come from a cryptographically secure random number generator (CSPRNG), like Python’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;secrets&lt;/code&gt; module. The standard &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;random&lt;/code&gt; module is a regular PRNG — predictable if someone knows the seed. (The code examples in this post use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;random&lt;/code&gt; for clarity, but don’t do that in production.)&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Share storage is critical&lt;/strong&gt;: Each share is as sensitive as the original secret. If someone gets k shares, game over.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;No integrity protection (mostly)&lt;/strong&gt;: Basic Shamir doesn’t have built-in authentication, but here’s a neat trick — if you collect &lt;em&gt;more&lt;/em&gt; than the minimum k shares, you can catch liars. Just try reconstructing from different subsets of k shares. If everyone’s honest, every subset gives the same secret. If someone faked their share, the subsets containing it will produce a different result, exposing the liar. Fair warning: this gets expensive fast (the number of subsets grows combinatorially), so it’s more of a sanity check than a scalable defense. For real-world use, look into verifiable schemes like Feldman’s VSS or Pedersen’s VSS.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Computation in finite fields&lt;/strong&gt;: For production use, you need to work in finite fields (modulo a large prime) to avoid floating-point precision issues and information leakage.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Other production concerns&lt;/strong&gt;: A full deployment would also want share authentication (MACs or signatures on each share), replay protection, and possibly proactive secret sharing — periodically refreshing shares without changing the secret. These are beyond our scope here, but worth knowing about if you ever build this for real.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;the-modular-twist-from-theory-to-practice&quot;&gt;The Modular Twist: From Theory to Practice&lt;/h2&gt;

&lt;p&gt;That last point deserves its own section, because it’s both important and surprisingly simple.&lt;/p&gt;

&lt;p&gt;Everything I explained above works &lt;em&gt;algebraically&lt;/em&gt; over the real numbers — the polynomial math is correct, and you’ll get your secret back. But here’s the thing: over ℝ, the scheme doesn’t actually provide information-theoretic security. The range and magnitude of the share values can leak information about the secret. The “zero knowledge with k-1 shares” guarantee &lt;strong&gt;only holds in a finite field&lt;/strong&gt;. So modular arithmetic isn’t just a practical fix for computers — it’s a security requirement. Let me explain.&lt;/p&gt;

&lt;h3 id=&quot;why-modular-arithmetic&quot;&gt;Why Modular Arithmetic?&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem 1: Floating-point chaos&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When you reconstruct a polynomial using Lagrange interpolation with real numbers, you’re doing lots of divisions. These create fractions that computers represent as floating-point numbers. After a few operations, you might get &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;7.999999999998&lt;/code&gt; instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;8&lt;/code&gt;. Usually you can just round, but what if your secret is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;7&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;8&lt;/code&gt;? At scale, these errors compound.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem 2: Information leakage&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here’s a subtler issue. If someone has k-1 shares and sees the intermediate calculations, the &lt;em&gt;size&lt;/em&gt; of those numbers might reveal information about the secret. In real arithmetic, evaluating a degree-2 polynomial at x=100 gives much bigger results for some coefficients than others. An attacker could use this to narrow down possibilities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The solution: Work modulo a prime&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of doing arithmetic over all real numbers, we do everything modulo a large prime number &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;p&lt;/code&gt;. This means:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;All values stay in the range [0, p-1]&lt;/li&gt;
  &lt;li&gt;No floating-point numbers at all - everything is integers&lt;/li&gt;
  &lt;li&gt;Division becomes “multiplication by the modular inverse”&lt;/li&gt;
  &lt;li&gt;Every value is equally likely, revealing nothing&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;how-simple-is-the-change&quot;&gt;How Simple Is The Change?&lt;/h3&gt;

&lt;p&gt;Remarkably simple. Take any operation from the basic algorithm and add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;% prime&lt;/code&gt; at the end:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;table style=&quot;border-spacing: 0&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot; style=&quot;text-align: right&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c&quot;&gt;# Basic (problematic)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;coef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Modular (production-ready)  &lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;coef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prime&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;The only tricky part is division. In modular arithmetic, you can’t just divide - instead, you multiply by the &lt;strong&gt;modular inverse&lt;/strong&gt;. If you want to compute &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a / b mod p&lt;/code&gt;, you calculate &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;a * (b^(-1)) mod p&lt;/code&gt;, where &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b^(-1)&lt;/code&gt; is the number such that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;b * b^(-1) ≡ 1 (mod p)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Python 3.8+ makes this trivial:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;table style=&quot;border-spacing: 0&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot; style=&quot;text-align: right&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c&quot;&gt;# Division in modular arithmetic&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;pow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prime&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;That’s it. The entire conceptual framework - polynomials, shares, interpolation - stays exactly the same. You’re just doing the arithmetic in a different “universe” where numbers wrap around at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;prime&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;choosing-your-prime&quot;&gt;Choosing Your Prime&lt;/h3&gt;

&lt;p&gt;The prime &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;p&lt;/code&gt; needs to be larger than:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Your secret value&lt;/li&gt;
  &lt;li&gt;The number of shares you’ll create&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A common choice is a Mersenne prime like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2³¹ - 1&lt;/code&gt; or, for cryptographic applications, a 256-bit prime. The “Try It Yourself” example below uses modular arithmetic properly.&lt;/p&gt;

&lt;h2 id=&quot;try-it-yourself&quot;&gt;Try It Yourself&lt;/h2&gt;

&lt;p&gt;Here’s a complete, simple implementation you can play with:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;table style=&quot;border-spacing: 0&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot; style=&quot;text-align: right&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;random&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;create_shares&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;secret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Create n shares of a secret with threshold k&quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prime&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;prime&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;31&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;  &lt;span class=&quot;c&quot;&gt;# Mersenne prime for simplicity&lt;/span&gt;
    
    &lt;span class=&quot;c&quot;&gt;# Random coefficients for polynomial&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;coefficients&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;secret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;randint&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prime&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;evaluate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Evaluate polynomial at x using Horner&apos;s method&quot;&quot;&quot;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;coef&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;reversed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;coefficients&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;coef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prime&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;
    
    &lt;span class=&quot;c&quot;&gt;# Generate shares&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;shares&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;evaluate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shares&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prime&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;reconstruct_secret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shares&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Reconstruct secret from shares using Lagrange interpolation&quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;secret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;xi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;yi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;enumerate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shares&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;numerator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;denominator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
        
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;xj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;enumerate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shares&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;numerator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;numerator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prime&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;denominator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;denominator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;xi&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prime&lt;/span&gt;
        
        &lt;span class=&quot;c&quot;&gt;# Modular inverse for division in finite field&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;lagrange_coef&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;numerator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;pow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;denominator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prime&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;secret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;secret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;yi&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lagrange_coef&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prime&lt;/span&gt;
    
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;secret&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Example&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;secret&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;12345&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;shares&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prime&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;create_shares&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;secret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;104729&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Original secret: {secret}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Shares: {shares[:3]}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;c&quot;&gt;# Show first 3 shares&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Reconstruct with any 3 shares&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;recovered&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reconstruct_secret&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;shares&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Recovered secret: {recovered}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;h2 id=&quot;the-takeaway&quot;&gt;The Takeaway&lt;/h2&gt;

&lt;p&gt;Shamir’s Secret Sharing is sneakily brilliant because it looks so simple. Polynomials? We learned those in high school. But that simplicity hides incredible power: a mathematically perfect way to split trust, with security properties that hold up against even quantum computers.&lt;/p&gt;

&lt;p&gt;Sometimes the best algorithms aren’t the most complex ones. Sometimes they’re the ones that make you slap your forehead and think, “why didn’t I think of that?”&lt;/p&gt;

&lt;p&gt;That’s the beauty of elegantly simple algorithms. They hide in plain sight, doing impossible-seeming things with elementary mathematics.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;em&gt;Next in the series: Bloom Filters - The data structure that’s okay with being wrong (sometimes)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Further Reading:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://dl.acm.org/doi/10.1145/359168.359176&quot;&gt;Shamir’s original 1979 paper&lt;/a&gt; - Surprisingly readable&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing&quot;&gt;Implementation considerations for production systems&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Example implementation: &lt;a href=&quot;https://github.com/wiktorwojcik112/secret_sharing&quot;&gt;Secret Sharing Demo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Sun, 08 Feb 2026 00:00:00 +0100</pubDate>
        <link>https://extiri.com/blog/posts/elegant-simplicity-secret-sharing</link>
        <guid isPermaLink="true">https://extiri.com/blog/posts/elegant-simplicity-secret-sharing</guid>
        
        <category>fun-facts</category>
        
        <category>The Power of Elegant Simplicity</category>
        
        
      </item>
    
      <item>
        <title>CodeMenu as a File System for Your AI Agents</title>
        <description>&lt;p&gt;In the evolving world of AI development, there’s a growing buzz around “filesystems for AI”—essentially, structured ways to organize and access data that empower AI agents to work more intelligently and autonomously. Think of it as giving your AI a well-organized filing cabinet where it can pull out exactly what it needs without rummaging through chaos. This trend is picking up steam because AI agents aren’t just chatbots anymore; they’re becoming active participants in coding, research, and problem-solving workflows. They need reliable, queryable repositories to fetch context, code patterns, or prompts on demand.&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;Enter CodeMenu, a nifty Mac app from Extiri that’s quietly positioning itself as a perfect fit for this niche. It’s not marketed as a filesystem per se, but its design as an offline-first knowledge base for developers makes it an ideal local “file system” for AI agents. I’ve been exploring it, and it’s got that warm, practical feel—like a trusted notebook that’s always at your side. Let me walk you through what it offers, why it aligns with this trend, and some real-world use cases that might inspire you to give it a spin.&lt;/p&gt;

&lt;h3 id=&quot;what-makes-codemenu-a-file-system-for-ai&quot;&gt;What Makes CodeMenu a “File System” for AI?&lt;/h3&gt;

&lt;p&gt;At its core, CodeMenu is a developer-focused tool for storing and managing code snippets, AI prompts, notes, images, and more. Everything lives locally on your Mac in a Realm database, which means no cloud dependencies, no subscriptions, and full privacy—your data stays put unless you choose otherwise. This offline-first approach is a breath of fresh air in a world of always-online tools, and it echoes the reliability we expect from a traditional file system.&lt;/p&gt;

&lt;p&gt;But here’s where it shines for AI: CodeMenu isn’t just a static storage bin. It structures your data in a way that’s easy for humans &lt;em&gt;and&lt;/em&gt; machines to navigate. You can organize artifacts into “Spaces” for project isolation, “Stashes” for quick ideas, and use tags, groups, and even a visual knowledge graph to link things together. Imagine a file system where files aren’t isolated silos—they’re connected nodes in a graph, allowing for relational queries.&lt;/p&gt;

&lt;p&gt;The real magic for AI agents comes from its integration points:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;MCP Server&lt;/strong&gt;: This lets AI tools like Claude Desktop or Cursor directly query your CodeMenu library. Your agent can say something like “CodeMenu fetch the ‘stripe-webhook’ snippet” and pull in verified code without you copying and pasting.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;HTTP Server&lt;/strong&gt;: Running locally (e.g., via curl to localhost), it exposes your snippets as an API. This turns CodeMenu into a programmable data source that AI scripts or agents can tap into programmatically.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;CLI Tool (cdmn)&lt;/strong&gt;: A terminal interface with fuzzy search and JSON output, perfect for scripting AI workflows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s like having a lightweight, developer-centric database that AI can read from, much like how modern AI systems use vector databases or file systems for long-term memory. And with support for over 60 languages, syntax highlighting, and rendering for Markdown, LaTeX, images, and even regex testing, it’s robust enough to handle diverse AI needs.&lt;/p&gt;

&lt;h3 id=&quot;real-use-cases-bringing-it-to-life&quot;&gt;Real Use Cases: Bringing It to Life&lt;/h3&gt;

&lt;p&gt;To make this concrete, let’s look at some practical scenarios where CodeMenu acts as that essential filesystem layer for AI agents. These are drawn from how developers are already using it, adapted to highlight the AI angle.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Building Smarter Coding Assistants&lt;/strong&gt;: Suppose you’re working on a web app with Stripe integration. You’ve stored webhook handlers, error-handling patterns, and test prompts in CodeMenu, tagged and linked in a knowledge graph. Now, integrate it with an AI agent in Cursor (an AI-powered code editor). The agent queries CodeMenu via the MCP Server to retrieve a “stripe-webhook” snippet, then generates unit tests or refactors code based on that context. No more losing track of your best practices—your AI pulls from a structured “file” of proven code, reducing hallucinations and improving output quality. CodeMenu can also store your Agent Skills and AGENTS.md giving you a centralized place to manage them. I’ve seen devs save hours this way, turning scattered notes into a reliable AI memory bank.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Automating Research Workflows&lt;/strong&gt;: For AI agents handling research or OSINT (open-source intelligence), CodeMenu can store screenshots, notes, links, and prompts as interconnected artifacts. Picture an agent tasked with analyzing design trends: It fetches a group of design tokens (colors, refs) from CodeMenu’s API, cross-references them with stored images, and generates a report. It’s neutral and warm in practice: No over-reliance on external APIs, just local, organized data that keeps your agent grounded and efficient.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Scripting Bulk Operations for AI Pipelines&lt;/strong&gt;: CodeMenu’s JavaScript scripting lets you automate tasks, but pair it with AI for more power. Say you’re maintaining a library of AI prompts for generating documentation. An AI agent uses the HTTP server to list all prompts tagged “docs-gen,” then scripts a bulk update (e.g., adding ethical guidelines to each). This filesystem-like access means your AI can “read” and “write” to the repo programmatically, evolving it over time. A real win for solo devs or small teams building custom AI tools—it’s like giving your agent write access to a shared drive, but safer and more structured.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Offline AI Experimentation&lt;/strong&gt;: In areas with spotty internet (or just for privacy), CodeMenu’s local setup is gold. Store system prompts, templates, and code for fine-tuning small models. An AI agent running locally (via tools like Ollama) queries CodeMenu for prompt patterns, tests them offline, and iterates. This use case feels particularly human-centered—it’s about empowering you to tinker without barriers, aligning with the trend of democratizing AI through accessible “filesystems.”&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;why-this-matters-in-the-bigger-picture&quot;&gt;Why This Matters in the Bigger Picture&lt;/h3&gt;

&lt;p&gt;As AI agents become more agentic—handling tasks end-to-end—they’ll need better ways to manage knowledge. Traditional filesystems are too rigid, cloud services too invasive, but tools like CodeMenu bridge the gap with a developer-friendly, AI-ready structure. It’s not perfect for every setup (it’s Mac-only for now), but its free model and extensibility make it approachable for anyone dipping into this trend.&lt;/p&gt;

&lt;p&gt;If you’re curious about enhancing your AI workflows, I’d gently suggest downloading CodeMenu and experimenting. Start small: Populate a Space with your go-to snippets, fire up the MCP Server, and see how your favorite AI tool interacts with it. It’s a subtle shift, but one that could make your agents feel less like tools and more like thoughtful collaborators. What do you think—ready to organize your AI’s “files”?&lt;/p&gt;

&lt;p&gt;Try &lt;strong&gt;CodeMenu&lt;/strong&gt; free for 7 days → &lt;a href=&quot;https://apps.apple.com/us/app/codemenu-snippet-manager/id1583463781?mt=12&quot;&gt;Mac App Store&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Fri, 16 Jan 2026 00:00:00 +0100</pubDate>
        <link>https://extiri.com/blog/posts/codemenu-as-a-filesystem-ai-agent</link>
        <guid isPermaLink="true">https://extiri.com/blog/posts/codemenu-as-a-filesystem-ai-agent</guid>
        
        <category>extiri</category>
        
        <category>codemenu</category>
        
        
      </item>
    
      <item>
        <title>Why 2026 Is the Year of Local-First Productivity Tools (and How CodeMenu Fits In)</title>
        <description>&lt;p&gt;We’re copying API keys into ChatGPT prompts, uploading proprietary code to cloud snippet managers, and syncing our entire knowledge bases through servers we don’t control. Somewhere along the way, we normalized sending our most valuable work artifacts through someone else’s infrastructure.&lt;/p&gt;

&lt;p&gt;That’s changing in 2026 — and it’s about time.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;privacy-isnt-optional-anymore&quot;&gt;Privacy Isn’t Optional Anymore&lt;/h2&gt;

&lt;p&gt;The tools we used to trust are now the ones we question. Every snippet, every piece of code, every design decision we document potentially flows through a third-party server. With data breaches making headlines weekly and companies quietly training AI models on user content, developers are waking up to an uncomfortable truth: cloud-first isn’t always the answer.&lt;/p&gt;

&lt;p&gt;The problem isn’t cloud sync itself—it’s the assumption that it must be the default. Your code snippets, your AI prompts, your research notes—these don’t need to live on someone else’s computer. They shouldn’t have to.&lt;/p&gt;

&lt;p&gt;Local-first tools flip this model. Your data stays on your machine. You choose when (and if) to sync. You control who sees what. And when you close your laptop on a plane, everything still works because it was never dependent on someone else’s servers in the first place.&lt;/p&gt;

&lt;h2 id=&quot;ai-needs-context-not-your-companys-secrets&quot;&gt;AI Needs Context, Not Your Company’s Secrets&lt;/h2&gt;

&lt;p&gt;The AI productivity revolution came with a catch: to be useful, AI needs context. The more it knows about your codebase, your patterns, your preferences, the better it performs. But feeding that context into cloud APIs means broadcasting your intellectual property to external services.&lt;/p&gt;

&lt;p&gt;This creates a painful choice: accept reduced productivity by keeping AI out of your workflow, or accept privacy risks by sending everything to the cloud.&lt;/p&gt;

&lt;p&gt;On-device AI changes the equation entirely. You get intelligent assistance—semantic search, content generation, automated tagging—without any data leaving your machine. The AI model runs locally, learns from your patterns privately, and helps you work faster without compromising what matters.&lt;/p&gt;

&lt;h2 id=&quot;codemenu-your-private-knowledge-system&quot;&gt;CodeMenu: Your Private Knowledge System&lt;/h2&gt;

&lt;p&gt;This is where CodeMenu comes in. It’s built from the ground up as a local-first tool that treats your privacy as a feature, not an afterthought.&lt;/p&gt;

&lt;p&gt;Everything you save—code snippets in 60+ languages, design references, color palettes, AI prompts, screenshots—lives on your Mac. The on-device AI features (generation, chat, semantic search) run entirely on your hardware. Nothing gets sent to external servers unless you explicitly choose to sync or share.&lt;/p&gt;

&lt;p&gt;What makes this powerful isn’t just the privacy angle. It’s the workflow. With parameterized templates, you can create living boilerplates that adapt to different contexts. The JavaScript automation and HTTP API let you build custom workflows that connect CodeMenu to your other tools. Built-in utilities (RegEx tester, converters, mock data generators) mean fewer context switches.&lt;/p&gt;

&lt;p&gt;And because it’s offline-first, everything works on planes, in basements, or when your internet inevitably decides to fail right before a demo. Your augmented memory is always available.&lt;/p&gt;

&lt;p&gt;For teams that do need sharing, CodeMenu’s optional sync and Snippets Store provide controlled collaboration—you decide what to share, when to share it, and who sees it.&lt;/p&gt;

&lt;h2 id=&quot;clipguru-local-first-for-your-mobile-workflow&quot;&gt;ClipGuru: Local-First for Your Mobile Workflow&lt;/h2&gt;

&lt;p&gt;Not everything needs the power of a full snippet manager. Sometimes you just need to capture a URL from Safari, save a quote you’ll reference later, or keep a piece of code accessible across your iPhone and Mac.&lt;/p&gt;

&lt;p&gt;ClipGuru takes the local-first philosophy and distills it into a lightweight clipboard manager for iOS, iPadOS, and macOS. Text, links, and images sync via iCloud (which means your data stays in Apple’s ecosystem, not a third-party server), and the custom keyboard extension gives you instant access without app switching.&lt;/p&gt;

&lt;p&gt;It’s intentionally simple—no overwhelming features, no complex setup, just fast capture and smart search. The free version saves 4 clips, and Pro unlocks unlimited history and tags for about the price of a coffee.&lt;/p&gt;

&lt;p&gt;Think of it as CodeMenu’s mobile companion: ClipGuru handles quick captures and clipboard management across devices, while CodeMenu serves as your deeper knowledge system on Mac. Together, they create a local-first workflow that scales from quick mobile snippets to comprehensive knowledge management.&lt;/p&gt;

&lt;h2 id=&quot;the-offline-first-future-is-already-here&quot;&gt;The Offline-First Future Is Already Here&lt;/h2&gt;

&lt;p&gt;We’re seeing a shift away from the “everything must be cloud-based” mentality of the 2010s. Developers are choosing tools that respect their privacy, work offline, and give them control over their data. The technology is mature—local AI models are powerful, device storage is cheap, and sync protocols exist that don’t require surrendering your data.&lt;/p&gt;

&lt;p&gt;2026 won’t be the year everyone abandons cloud tools. But it will be the year local-first becomes a serious consideration rather than a niche preference. When you can have the best of both worlds—powerful features and complete privacy—why would you settle for less?&lt;/p&gt;

&lt;p&gt;Your code, your snippets, your knowledge base—these are your intellectual property. They deserve to live somewhere you control.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;strong&gt;Ready to go local-first?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Try &lt;strong&gt;CodeMenu&lt;/strong&gt; free for 7 days → &lt;a href=&quot;https://apps.apple.com/us/app/codemenu-snippet-manager/id1583463781?mt=12&quot;&gt;Mac App Store&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or grab &lt;strong&gt;ClipGuru&lt;/strong&gt; for quick clipboard access → &lt;a href=&quot;https://apps.apple.com/pl/app/clipguru/id6753787446&quot;&gt;App Store (iOS, iPadOS, macOS)&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Tue, 13 Jan 2026 00:00:00 +0100</pubDate>
        <link>https://extiri.com/blog/posts/the-year-of-local-software</link>
        <guid isPermaLink="true">https://extiri.com/blog/posts/the-year-of-local-software</guid>
        
        <category>extiri</category>
        
        
      </item>
    
      <item>
        <title>How to Use Code Snippets on Your Mac</title>
        <description>&lt;p class=&quot;lead&quot;&gt;You found a piece of code online-maybe in Python, Bash, JavaScript, Ruby, or even C-and you’re wondering how to actually run it on your Mac.&lt;br /&gt;
You’re in the right place. I’ll walk you through how to run snippets in both interpreted &lt;em&gt;and&lt;/em&gt; compiled languages, explain what’s happening behind the scenes, and keep it simple.&lt;/p&gt;

&lt;!--more--&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;whats-going-on-interpreted-vs-compiled-languages&quot;&gt;What’s going on: interpreted vs compiled languages&lt;/h2&gt;

&lt;p&gt;Before we dive into examples, here’s a quick overview.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Interpreted languages&lt;/strong&gt; (like Python, Bash scripts, JavaScript via Node.js, Ruby) are executed by a program (an interpreter) line by line. You paste code and it runs.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Compiled languages&lt;/strong&gt; (such as C, C++ and others) require a &lt;em&gt;compiler&lt;/em&gt;. The compiler transforms your source code into a binary executable that your Mac can run directly. That means a few extra steps: install the compiler → compile the code → run the executable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why does this matter to you? Because when you pick up a snippet, you’ll want to know whether you can paste it and run it immediately (interpreted), or if you need a toolchain set up (compiled).&lt;br /&gt;
Let’s go through both.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;option-1-running-interpreted-languages&quot;&gt;Option 1: Running Interpreted Languages&lt;/h2&gt;

&lt;h3 id=&quot;python&quot;&gt;Python&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;Check if Python is installed:
    &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;table style=&quot;border-spacing: 0&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot; style=&quot;text-align: right&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;  python3 --version
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you see something like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Python 3.9.x&lt;/code&gt;, you’re ready.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Run code interactively:&lt;/p&gt;

    &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;table style=&quot;border-spacing: 0&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot; style=&quot;text-align: right&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;python3
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
    &lt;/div&gt;

    &lt;p&gt;Now you’re at the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/code&gt; prompt. Paste:&lt;/p&gt;

    &lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;table style=&quot;border-spacing: 0&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot; style=&quot;text-align: right&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Hello from Python!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
    &lt;/div&gt;

    &lt;p&gt;Press Enter → you’ll see the output. To exit, type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exit()&lt;/code&gt; or press &lt;strong&gt;Ctrl + D&lt;/strong&gt;.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Run a script from a file:&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;Open a text editor, paste your snippet.&lt;/li&gt;
      &lt;li&gt;Save it as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;myscript.py&lt;/code&gt;.&lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;In Terminal, navigate to its folder.&lt;/p&gt;

        &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;table style=&quot;border-spacing: 0&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot; style=&quot;text-align: right&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; ~/Desktop     &lt;span class=&quot;c&quot;&gt;# adjust path as needed&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
        &lt;/div&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;Run:&lt;/p&gt;

        &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;table style=&quot;border-spacing: 0&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot; style=&quot;text-align: right&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;python3 myscript.py
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
        &lt;/div&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;bash--shell&quot;&gt;Bash / Shell&lt;/h3&gt;

&lt;p&gt;If the snippet looks like:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;table style=&quot;border-spacing: 0&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot; style=&quot;text-align: right&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Hello from the shell!&quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;You can simply paste it into Terminal and hit Enter-done.&lt;/p&gt;

&lt;p&gt;For a multi-line script:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;table style=&quot;border-spacing: 0&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot; style=&quot;text-align: right&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;nano myscript.sh         &lt;span class=&quot;c&quot;&gt;# open a simple editor&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# paste code&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# save with Ctrl + O, enter; exit with Ctrl + X&lt;/span&gt;
chmod +x myscript.sh     &lt;span class=&quot;c&quot;&gt;# make it executable&lt;/span&gt;
./myscript.sh            &lt;span class=&quot;c&quot;&gt;# run it&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;h3 id=&quot;javascript-with-nodejs&quot;&gt;JavaScript with Node.js&lt;/h3&gt;

&lt;p&gt;To use JavaScript outside of a browser you’ll generally install &lt;strong&gt;Node.js&lt;/strong&gt;. I’ll cover installation in the next section. Once installed:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Interactive mode:&lt;/p&gt;

    &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;table style=&quot;border-spacing: 0&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot; style=&quot;text-align: right&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;node
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
    &lt;/div&gt;

    &lt;p&gt;You’ll see a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;gt;&lt;/code&gt; prompt. Then:&lt;/p&gt;

    &lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;table style=&quot;border-spacing: 0&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot; style=&quot;text-align: right&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Hello from Node.js!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
    &lt;/div&gt;

    &lt;p&gt;Press Enter → output appears. Exit with Ctrl + C.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Running from file:&lt;/p&gt;

    &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;table style=&quot;border-spacing: 0&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot; style=&quot;text-align: right&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;nano app.js           &lt;span class=&quot;c&quot;&gt;# create/edit file&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# paste:&lt;/span&gt;
console.log&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;This runs using Node.js&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;;
&lt;span class=&quot;c&quot;&gt;# save &amp;amp; exit&lt;/span&gt;
node app.js
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;ruby&quot;&gt;Ruby&lt;/h3&gt;

&lt;p&gt;Check:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;table style=&quot;border-spacing: 0&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot; style=&quot;text-align: right&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;ruby -v
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Then create a file &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;script.rb&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;table style=&quot;border-spacing: 0&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot; style=&quot;text-align: right&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Hello from Ruby!&quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Save, then run:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;table style=&quot;border-spacing: 0&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot; style=&quot;text-align: right&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;ruby script.rb
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;installing-nodejs-so-you-can-run-js&quot;&gt;Installing Node.js (so you can run JS)&lt;/h2&gt;

&lt;p&gt;Here’s how to get Node.js running on your Mac:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Visit the official Node.js site and download the &lt;strong&gt;LTS&lt;/strong&gt; version (https://nodejs.org).&lt;/li&gt;
  &lt;li&gt;Open the downloaded &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.pkg&lt;/code&gt; installer and follow the on-screen steps.&lt;/li&gt;
  &lt;li&gt;After installation, open Terminal and type:&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;table style=&quot;border-spacing: 0&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot; style=&quot;text-align: right&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;  node -v
  npm -v
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;You should see version numbers, indicating successful installation.&lt;/p&gt;

&lt;p&gt;Optionally, you can use the Homebrew package manager:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;table style=&quot;border-spacing: 0&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot; style=&quot;text-align: right&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;brew update
brew install node
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Then verify as before.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;option-2-running-compiled-languages---example-with-c&quot;&gt;Option 2: Running Compiled Languages - Example with C&lt;/h2&gt;

&lt;p&gt;Using compiled languages means one extra step: building (compiling) the code before you run it. Let’s walk through C on Mac.&lt;/p&gt;

&lt;h3 id=&quot;installing-the-compiler&quot;&gt;Installing the compiler&lt;/h3&gt;

&lt;p&gt;Your Mac can use Apple’s built-in toolchain (Clang) or you can install GNU GCC. Here’s how to set up the basic tools:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Run:&lt;/p&gt;

    &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;table style=&quot;border-spacing: 0&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot; style=&quot;text-align: right&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;xcode-select --install
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
    &lt;/div&gt;

    &lt;p&gt;This installs Apple’s Command Line Tools (which include Clang) without needing the full Xcode app.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;If you prefer GNU GCC:&lt;/p&gt;

    &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;table style=&quot;border-spacing: 0&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot; style=&quot;text-align: right&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;brew install gcc
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
    &lt;/div&gt;

    &lt;p&gt;And verify:&lt;/p&gt;

    &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;table style=&quot;border-spacing: 0&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot; style=&quot;text-align: right&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;gcc --version
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
    &lt;/div&gt;

    &lt;p&gt;(verify the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gcc&lt;/code&gt; output shows a version string)&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;write-a-simple-c-snippet&quot;&gt;Write a simple C snippet&lt;/h3&gt;

&lt;p&gt;Create a file named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hello.c&lt;/code&gt; with content:&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;table style=&quot;border-spacing: 0&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot; style=&quot;text-align: right&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;cp&quot;&gt;#include &amp;lt;stdio.h&amp;gt;
&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;printf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Hello, C world!&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;h3 id=&quot;compile--run&quot;&gt;Compile &amp;amp; Run&lt;/h3&gt;

&lt;p&gt;In Terminal, navigate to the folder:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;table style=&quot;border-spacing: 0&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot; style=&quot;text-align: right&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; /path/to/folder
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Then compile:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;table style=&quot;border-spacing: 0&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot; style=&quot;text-align: right&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;clang hello.c -o hello
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;or if using GCC:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;table style=&quot;border-spacing: 0&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot; style=&quot;text-align: right&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;gcc-13 hello.c -o hello
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Now run:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;table style=&quot;border-spacing: 0&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot; style=&quot;text-align: right&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;./hello
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;You should see:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;table style=&quot;border-spacing: 0&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot; style=&quot;text-align: right&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;Hello, C world!
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;h3 id=&quot;using-vs-code-for-c-optional&quot;&gt;Using VS Code for C (optional)&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Install VS Code&lt;/li&gt;
  &lt;li&gt;Install the C/C++ extension&lt;/li&gt;
  &lt;li&gt;Open &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hello.c&lt;/code&gt;, then use the built-in terminal (Ctrl + ~) to compile and run as above.&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;a-quick-peek-at-other-languages-you-might-encounter&quot;&gt;A Quick Peek at Other Languages You Might Encounter&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;C++&lt;/strong&gt; - Very similar to C; you’ll use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;clang++&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;g++&lt;/code&gt; to compile.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Go&lt;/strong&gt; - Install via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;brew install go&lt;/code&gt; or official site; run a file with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go run file.go&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Rust&lt;/strong&gt; - Install using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rustup&lt;/code&gt;, compile with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cargo build&lt;/code&gt;, run with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cargo run&lt;/code&gt;.
The pattern stays: install toolchain → compile/build (if needed) → run.&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;option-3-run--manage-snippets-effortlessly-with-codemenu&quot;&gt;Option 3: Run &amp;amp; Manage Snippets Effortlessly with CodeMenu&lt;/h2&gt;

&lt;p&gt;If you’re testing a variety of snippets across languages, switching between terminals, editors and computing steps can get tedious. That’s why I built &lt;strong&gt;&lt;a href=&quot;https://extiri.com/codemenu.html&quot;&gt;CodeMenu&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;With CodeMenu you can:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Save and organize snippets (Python, Bash, Ruby, JavaScript, C, and more).&lt;/li&gt;
  &lt;li&gt;Run them instantly without manually opening Terminal each time.&lt;/li&gt;
  &lt;li&gt;Keep everything in one place so you don’t lose track of “one that worked” or “the version I saved”.
Treat it as a snippet sandbox + launcher-it simplifies your workflow so you can focus on experiments instead of setup.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 Try it here: &lt;a href=&quot;https://extiri.com/codemenu.html&quot;&gt;CodeMenu for Mac&lt;/a&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;tldr-summary&quot;&gt;TL;DR (summary)&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Interpreted languages (Python, Bash, JS, Ruby) = paste &amp;amp; run almost immediately.&lt;/li&gt;
  &lt;li&gt;Compiled languages (C, C++, Go, etc.) = install compiler → compile → run the executable.&lt;/li&gt;
  &lt;li&gt;On Mac you’ll often use Terminal to run commands; editors like VS Code add comfort.&lt;/li&gt;
  &lt;li&gt;If you’re working with lots of snippets or switching languages, CodeMenu gives you structure.&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h2&gt;

&lt;p&gt;You don’t have to be a “developer” to try running code. You just need curiosity and a few practical steps. Start with a simple snippet, run it, see what happens, tweak it. The “typing commands” part might look intimidating-but once you’ve done it once, it becomes second nature.
And when you want to keep your snippets organized, manageable, and easy to run-CodeMenu is there to make life simpler.&lt;/p&gt;
</description>
        <pubDate>Wed, 29 Oct 2025 00:00:00 +0100</pubDate>
        <link>https://extiri.com/blog/posts/how-to-run-code-snippets-mac</link>
        <guid isPermaLink="true">https://extiri.com/blog/posts/how-to-run-code-snippets-mac</guid>
        
        <category>extiri</category>
        
        <category>codemenu-guide</category>
        
        
      </item>
    
  </channel>
</rss>
