<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Nicky Reinert</title><link>https://nickyreinert.de/en/</link><description>Blog &amp; projects by Nicky Reinert (Institute for Digital Challenges): web development &amp; software development, SEO &amp; analytics, hosting &amp; DevOps, WordPress &amp; Hugo, tools &amp; projects, data protection &amp; digital culture — plus content on AI and autism &amp; society.</description><generator>Hugo 0.162.0</generator><language>en</language><managingEditor>Nicky Reinert</managingEditor><webMaster>Nicky Reinert</webMaster><copyright/><lastBuildDate>Fri, 03 Jul 2026 12:34:56 +0000</lastBuildDate><atom:link href="https://nickyreinert.de/en/index.xml" rel="self" type="application/rss+xml"/><item><title>9 – Simple, Secure Browser-Based Text Sharing</title><link>https://nickyreinert.de/en/2026/2026-07-03-nine-share-text/</link><pubDate>Fri, 03 Jul 2026 12:34:56 +0000</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/en/2026/2026-07-03-nine-share-text/</guid><description>9 is a free, anonymous, browser-based tool that sends text directly between two devices via encrypted WebRTC – no server, no signup.</description><content:encoded><![CDATA[<p>You are setting up a new device and you need to get your complex password to it from you current working laptop? You want to quickly copy a private key from one PC to another? You want to quickly share a prompt with another device? You want to quickly share a text snippet with your MacBook?</p>
<p>Then you need 9. A simple browser based text sharing tool.</p>
<p>It&rsquo;s anonyous. You don&rsquo;t need to register.</p>
<p>It&rsquo;s secure. Once a connection is established, the text is encrypted and sent directly to the other device. No server in between.</p>
<p>It&rsquo;s quick. Quickly scan a QR code if you use your mobile phone. Or type in the 6-digit code if you use another computer (which shortly utilizes Cloudeflare&rsquo;s WebRTC implementation to establish a direct connection).</p>
<p>If your devices are not on the same network 9 utilizes STUN by Google to establish a direct connection. Still, the text is being exchanged directly between the two devices.</p>
<figure>
  <a href="https://nickyreinert.de/en/2026/2026-07-03-nine-share-text/image.png" target="_blank" class="image-link " title="Click to view full size">
    <img 
      src="https://nickyreinert.de/en/2026/2026-07-03-nine-share-text/image.png" 
      alt="Screenshot of the 9 browser-based text sharing tool"
      width="600"
      loading="lazy"
    />
  </a>
  <figcaption>Screenshot of the 9 browser-based text sharing tool</figcaption>
</figure>

<p>





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://github.com/nickyreinert/9?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">GitHub Repository</a>


</p>
<p>





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://9.1-1-1.de/?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">nine online</a>


</p>
]]></content:encoded><atom:updated>2026-07-03T19:28:25+02:00</atom:updated><category>blog</category><category>tools</category><category>projekte</category><dc:subject>Lesezeit: 2 Minuten</dc:subject><dc:type>tool</dc:type></item><item><title>LEAKED: Anthropic's Secret Claude Strategy</title><link>https://nickyreinert.de/en/2026/2026-07-03-leak-anthropic-claude-strategy/</link><pubDate>Fri, 03 Jul 2026 12:34:56 +0000</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/en/2026/2026-07-03-leak-anthropic-claude-strategy/</guid><description>A satirical take on the recurring conspiracy theory that AI labs secretly nerf their models only to re-release them for double the price.</description><content:encoded><![CDATA[<p>🚨 HUGE LEAK 🚨</p>
<p>We just got our hands on an internal Anthropic strategy slide, and it will completely blew your mind. 🤯</p>
<p>Here is an absolute masterclass in margin optimization. 👇</p>
<figure>
  <a href="https://nickyreinert.de/en/2026/2026-07-03-leak-anthropic-claude-strategy/image.png" target="_blank" class="image-link " title="Click to view full size">
    <img 
      src="https://nickyreinert.de/en/2026/2026-07-03-leak-anthropic-claude-strategy/image.png" 
      alt="Model Lifecylce &amp; Margin Optimization"
      width="600"
      loading="lazy"
    />
  </a>
  <figcaption>Model Lifecylce &amp; Margin Optimization</figcaption>
</figure>

<p>The strategy is brutally simple:</p>
<p>1️⃣ Silently degrade the model to ruthlessly cut inference costs. 📉
2️⃣ Wait for the market to complain. 🗣️
3️⃣ Re-release the ORIGINAL, un-nerfed model for 2x the price under a slick new name. 🚀💰</p>
<p>Let that sink in. 🧠</p>
<p>They aren&rsquo;t actually training better models. They are just silently routing your queries to heavily quantized, smaller versions of the current one.</p>
<p>Then? They charge you DOUBLE for a &ldquo;next-gen&rdquo; release that merely restores the full precision weights. 💸🔥</p>
<p>This is the ultimate SaaS infinite money glitch. 🤑 A true game-changer in AI monetization.</p>
<p>Agree? Let me know your thoughts in the comments! 👇</p>
<p>/s</p>
]]></content:encoded><atom:updated>2026-07-06T12:13:18+02:00</atom:updated><category>blog</category><dc:subject>Lesezeit: 1 Minuten</dc:subject><dc:type>satire</dc:type></item><item><title>The Confidence Calculator - A/B Testing Made Easy</title><link>https://nickyreinert.de/en/2026/2026-06-24-confidence-calculator/</link><pubDate>Wed, 24 Jun 2026 12:34:56 +0000</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/en/2026/2026-06-24-confidence-calculator/</guid><description>A simple and easy-to-use confidence calculator for A/B testing. Calculate confidence levels, p-values, power and more for your A/B tests.</description><content:encoded><![CDATA[<p>





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://confidence.nickyreinert.de?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">confidence.nickyreinert.de</a>


</p>
<p>Yeah, I know, there are many many confidence calculators out there. But this one is different, because it&rsquo;s mine &lsquo;(͡• ͜ʖ ͡•)&rsquo;</p>
<p>No seriously. All the other options out there are either a little too complicated, lacking features or missing simple <em>layman explanations</em> - because: I am not a data scientist, a market researcher or a rocket scientist. I&rsquo;m just&hellip;a dude, I guess.</p>
<p>However: I built this one as an Excel file, because I was working with Adobe Target for a huge&hellip; very huge&hellip; very infamous - I mean - famous of course - German railway company. And later improved it to look even nicer. And then I got the hint: Make it a product. Yeah well, here it is. It&rsquo;s free. Thank me later. <code>(⌐■_■)</code></p>
<p>Here it is:</p>
<ul>
<li>you can easily add up to 5 variants</li>
<li>you can easily configure confidence levels or other parameters and they are explained in a simple but hopefully sufficient way - I will also repeat it here - for your convenience</li>
<li>you can share the results - always, everywhere, because the parameters are hidden in almost plain sight in the URL</li>
<li>you can see two charts showing you, when the test will reach a certain confidence level, like how many days you have to wait</li>
<li>or how different traffic numbers will affect the confidence level</li>
</ul>
<p>





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://confidence.nickyreinert.de?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">confidence.nickyreinert.de</a>


</p>
<div class="image-gallery" style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 1rem;">
  
<figure>
  <a href="https://nickyreinert.de/en/2026/2026-06-24-confidence-calculator/image.png" target="_blank" class="image-link " title="Click to view full size">
    <img 
      src="https://nickyreinert.de/en/2026/2026-06-24-confidence-calculator/image.png" 
      alt="Confidence over time"
      width="600"
      loading="lazy"
    />
  </a>
  <figcaption>Confidence over time</figcaption>
</figure>

<figure>
  <a href="https://nickyreinert.de/en/2026/2026-06-24-confidence-calculator/image-1.png" target="_blank" class="image-link " title="Click to view full size">
    <img 
      src="https://nickyreinert.de/en/2026/2026-06-24-confidence-calculator/image-1.png" 
      alt="Traffic sensitivity"
      width="600"
      loading="lazy"
    />
  </a>
  <figcaption>Traffic sensitivity</figcaption>
</figure>

<figure>
  <a href="https://nickyreinert.de/en/2026/2026-06-24-confidence-calculator/image-2.png" target="_blank" class="image-link " title="Click to view full size">
    <img 
      src="https://nickyreinert.de/en/2026/2026-06-24-confidence-calculator/image-2.png" 
      alt="Visitors needed by power"
      width="600"
      loading="lazy"
    />
  </a>
  <figcaption>Visitors needed by power</figcaption>
</figure>

<figure>
  <a href="https://nickyreinert.de/en/2026/2026-06-24-confidence-calculator/image-3.png" target="_blank" class="image-link " title="Click to view full size">
    <img 
      src="https://nickyreinert.de/en/2026/2026-06-24-confidence-calculator/image-3.png" 
      alt="Time to power target"
      width="600"
      loading="lazy"
    />
  </a>
  <figcaption>Time to power target</figcaption>
</figure>


</div>

<h2 id="dont-hack-the-p">Dont hack the P!</h2>
<p>Well, now you might think: I have an interface. I will just update it everyday and as soon as I reach the required confidence level, the test is succesful.</p>
<p>That&rsquo;s called p-hacking. Don&rsquo;t do that. What if the very next day, the numbers are going down again, because this one special day external and unknown factors influenced the test?</p>
<p>That&rsquo;s why you <strong>plan</strong> a test and define a <em>target</em>* to reach - not only the confidence level, but also the running time and the number of visitors.</p>
<h2 id="some-basic-information-about-ab-testing">Some basic information about A/B testing</h2>
<h3 id="the-hypothesis">The hypothesis</h3>
<p>This is where everything starts: Your assumption. This is mostly a bookkeeping task and does not affect the calculation itself.</p>
<p>The <strong>null hypothesis</strong> says: &ldquo;The test will have no effect on the conversion rate.&rdquo; Your goal is to <strong>disprove</strong> it.</p>
<p>The <strong>alternative hypothesis</strong> says: &ldquo;The test will have an effect on the conversion rate.&rdquo; Your goal is to find enough evidence to <strong>reject the null hypothesis</strong> in its favour.</p>
<h3 id="type-i-and-type-ii-errors">Type-I and Type-II errors</h3>
<p>The Type-I error means the result is a <strong>false positive</strong>: The test suggests an effect exists — while in reality, no effect exists. You want to avoid that, too.</p>
<p>The Type-II error means the result is a <strong>false negative</strong>: The test fails to detect an effect — while in reality, a real effect exists. You want to avoid that, apparently.</p>
<h3 id="test-type">Test Type</h3>
<p>It sounds strange, but it&rsquo;s easy: A test can have two effects: A conversion rate can either <strong>increase</strong> or <strong>decrease</strong>.</p>
<p>A <strong>one-sided</strong> or <strong>one-tailed test</strong> assumes that only one direction is possible. A <strong>two-sided</strong> or <strong>two-tailed test</strong> assumes that both directions are possible. A one-tailed test requires less data and reaches confidence faster, but there is a risk: What if the effect is in the other direction?  You won&rsquo;t see it because it&rsquo;s not part of the calculation. You want to see the calculation?</p>
<p>$$
p_{\text{one-tailed}} = 1 - \Phi(z)
\qquad\qquad
p_{\text{two-tailed}} = 2,\bigl(1 - \Phi(|z|)\bigr)
$$</p>
<h3 id="whats-the-confidence-level">What&rsquo;s the confidence level?</h3>
<p>There are many mathematical and very complex ways to explain that, but actually, what I think is important to know, is this:</p>
<p>You want to make sure that the results of your A/B test are <strong>not</strong> just <strong>coincidence</strong> or <strong>luck</strong>. The p-value estimates how compatible your observed data is with the assumption that there is no real effect — in other words, how likely it is that what you see is just noise.</p>
<p>Before running a test, you typically choose a significance level — for example 5%, corresponding to a 95% confidence threshold. In this calculator, confidence is reported as:</p>
<p>$$
\text{Confidence} = 1 - \text{p-value}
$$</p>
<p>Strictly speaking, confidence levels and p-values are distinct statistical concepts. But this representation is commonly used in A/B testing tools because it is intuitive for non-specialists — and it is what your calculator reports.</p>
<p>The higher the confidence level, the lower the chance of a <strong>Type-I error</strong>.</p>
<h3 id="whats-the-significance-level">What&rsquo;s the significance level?</h3>
<p>In simple words: It&rsquo;s the confidence level but the other way around, like:</p>
<p>$$
\alpha = 1 − \text{Confidence}
$$</p>
<p>It&rsquo;s closely related to the <strong>p-value</strong>, like: The significance level is the threshold, the p-value is the result.</p>
<p>The lower the significance level, the lower the chance of a <strong>Type-I error</strong>.</p>
<h3 id="whats-the-power-of-the-test">What&rsquo;s the power of the test?</h3>
<p>This is probably the second most important metric of a test and it&rsquo;s almost the opposite of the confidence level. Confidence tells you how sure you are the effect isn&rsquo;t noise; power tells you how likely the test is to detect a real effect if one exists.</p>
<p>Assuming there is a real effect, how likely is this test to detect it?</p>
<p>Power is not part of the p-value calculation. Instead, it helps you estimate before the test starts how many visitors are needed to reliably detect an effect.</p>
<p>$$
\text{Power} = 1 - \beta
$$</p>
<h3 id="whats-beta">What&rsquo;s beta?</h3>
<p>While $\alpha$ and $\text{Confidence}$ are about the Type-I error, $\beta$ is the probability of a Type-II error. The calculation is a little more complex. It requires not only your test variables but also constants such as the <strong>z-score</strong>.</p>
<p>The lower the $\beta$, the lower the chance of a <strong>Type-II error</strong>. The higher the power, the lower the chance of a <strong>Type-II error</strong>.</p>
<h3 id="uplift-visitors-conversion-rate-and-distribution">Uplift, Visitors, Conversion Rate and Distribution</h3>
<p>They are quite simple metrics, as they are just common &ldquo;performance indicators&rdquo;. Visitors are the people you reach. Conversion can be quite anything that you want to measure and improve. The conversion rate is the percentage of visitors that convert. And the uplift is, either the expected or observed change in the conversion rate.</p>
<h3 id="the-other-parameters">The other parameters</h3>
<p>There is a complex mathematical pipeline behind the calculation of the confidence level for each variant. It starts with the <strong>pooled conversion rate</strong>:</p>
<p>$$\bar{p} = \frac{c_1 + c_2}{n_1 + n_2}$$</p>
<p>Where $c_1$ and $c_2$ are the number of conversions for variants 1 and 2, and $n_1$ and $n_2$ are the number of visitors for variants 1 and 2.</p>
<p>From this, the <strong>standard error SE</strong> is calculated:</p>
<p>$$SE = \sqrt{\bar{p} \cdot (1 - \bar{p}) \cdot \left(\frac{1}{n_1} + \frac{1}{n_2}\right)}$$</p>
<p>Which goes into the calculation of the <strong>z-score</strong> using $p_1$ and $p_2$, the conversion rates of variants 1 and 2:</p>
<p>$$Z = \frac{p_2 - p_1}{SE}$$</p>
<p>Followed by the calculation of the <strong>p-value</strong> using the z-score and the cumulative distribution function $\Phi$ (shown here as one-tailed; for a two-tailed test it&rsquo;s $2(1 - \Phi(|Z|))$):</p>
<p>$$p = 1 - \Phi(Z)$$</p>
<p>The what? The <strong>cumulative distribution function</strong> $\Phi$! Historically it came from lookup tables — today it&rsquo;s calculated directly by software. You don&rsquo;t want to know more. <code>(◔_◔)</code></p>
<p>The <strong>p-value</strong> is the first important metric, it must be below the significance level $\alpha$ to reject the <strong>null hypothesis</strong>.</p>
<p>$$\text{Confidence} = 1 - p$$</p>
<p>Therefore, the reported confidence must exceed your chosen confidence threshold before you reject the <strong>null hypothesis</strong>.</p>
<h3 id="but-what-is-the-bonferroni-correction">But What Is the Bonferroni Correction?</h3>
<p>If you have multiple variants compared against one control group, you may want to apply the Bonferroni correction. It&rsquo;s meant to reduce the chance of a Type-I error.</p>
<p>How does it work? You simply divide the significance level $\alpha$ by the number of comparisons $m$. So if you have 3 variants, you would divide $\alpha$ by 3. The adjusted significance level is then used instead of the original one when evaluating each variant&rsquo;s result — raising the bar for everyone. The idea is: if you test many variants at once, the probability of seeing at least one seemingly significant result purely by chance increases. I mean - there are so many colors!</p>
]]></content:encoded><atom:updated>2026-06-27T10:19:43+02:00</atom:updated><category>blog</category><category>tools</category><dc:subject>Lesezeit: 10 Minuten</dc:subject><dc:type>blog</dc:type></item><item><title>Digital Dilemma: Why Google Accounts Should Be Treated as Critical Infrastructure</title><link>https://nickyreinert.de/en/2026/2026-05-26-digital-dilemma/</link><pubDate>Tue, 26 May 2026 11:34:56 +0000</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/en/2026/2026-05-26-digital-dilemma/</guid><description>Google and Microsoft accounts have become part of digital public life. Why account suspensions are more than just an email problem.</description><content:encoded><![CDATA[<figure>
  <a href="https://nickyreinert.de/en/2026/2026-05-26-digital-dilemma/image.png" target="_blank" class="image-link " title="Click to view full size">
    <img 
      src="https://nickyreinert.de/en/2026/2026-05-26-digital-dilemma/image.png" 
      alt="0 days without Google account suspension"
      width="600"
      loading="lazy"
    />
  </a>
  <figcaption>0 days without Google account suspension</figcaption>
</figure>

<p>For many people a <strong>Google account</strong> is the central access point for their <strong>digital identity</strong>. For a great many people, in fact. Through <strong>SSO</strong>, we gain <strong>access</strong> to many <strong>platforms</strong> and <strong>systems</strong>. Communication often runs entirely through Gmail. That starts with <strong>forum logins</strong>, continues through <strong>GitHub</strong> accounts, and can extend all the way to <strong>tax returns</strong> or communication with your <strong>health insurer</strong>. It may even lock you out of your phone, that you use to pay or log in to your banking account. Google Mail is way more than communciation. It&rsquo;s access and identity. The <strong>digital sphere</strong> has completely absorbed real life. More for some, less for others.</p>
<p>That is reason enough to keep advocating for <strong>digital sovereignty</strong>. But that is not the point here. I want to raise a different question and illustrate the problem with a current report: Google has suspended the account of a Japanese manga artist [





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://www.reddit.com/r/degoogle/comments/1tkc0ur/manga_artist_banned_from_google_after_uploading/?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">reddit.com</a>


]. This is not the first time and it does not only concern Google. Microsoft is also known for deleting or suspending accounts for different reasons:</p>
<ul>
<li>





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://www.windowscentral.com/microsoft/microsoft-killed-my-online-life-microsoft-is-reportedly-banning-palestinians-in-the-us-for-life-for-calling-relatives-in-gaza?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">&lsquo;Microsoft killed my online life,&rsquo; Microsoft is reportedly banning Palestinians in the U.S. for life for calling relatives in Gaza</a>


</li>
<li>





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://news.ycombinator.com/item?id=42350245&amp;utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">Need help, locked out of Google account with 10 years of personal data</a>


</li>
<li>





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://blogs.aashgates.com/index.php?post/2025/06/24/Microsoft-Account-Suspension-Leads-to-Loss-of-30-Years-of-Personal-and-Professional-Data&amp;utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">Microsoft Account Suspension Leads to Loss of 30 Years of Personal and Professional Data</a>


</li>
<li>





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://support.google.com/googleplay/android-developer/thread/407515642/account-terminated-without-warning-victim-of-scam-seeking-a-second-chance-after-1-5-years?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">Account Terminated without Warning: Victim of Scam - Seeking a Second Chance after 1.5 Years</a>


</li>
</ul>
<p>At first, that sounds trivial. A few emails are missing, probably some images, that is all? Where&rsquo;s your BaCkUp?</p>
<p>Well… as I described at the beginning, the simple email account, whether from Google or another provider, has evolved from a purely <strong>digital communication</strong> medium into a <strong>central link to real-world identity</strong>. It is not just about emails, but about the representation of one’s own <strong>personality in digital space</strong>.</p>
<p>That gives providers like <strong>Google</strong> a special <strong>responsibility</strong> that goes <strong>far beyond the customer-company relationship</strong>.</p>
<p>Even if Google acts primarily, and understandably, according to economic interests, its <strong>influence</strong> on an <strong>essential</strong> <strong>basic service</strong> is enormous. When it comes to access to the internet, policymakers have imposed important rules on providers, going so far as to recognize that access as a basic right. Yet major identity platforms like Google remain <strong>largely unregulated</strong> when it comes to responsibility in the context of public provision. As a private company, Google can decide on its own, and <strong>relatively opaquely</strong>, when an account is deleted. There is <strong>no neutral appeals process</strong> or even an &ldquo;<strong>in dubio pro reo</strong>&rdquo; rule.</p>
<p>That is a problem. The <strong>call for digital sovereignty</strong> is not the right step here either, because it shifts responsibility onto consumers. Just ask your parents, who are in their mid-60s, why they not host their <strong>own email and LDAP server</strong>. We should not overlook the fact that not everyone is readily able to implement sovereign alternatives.</p>
<p>That is why politics is needed: to better oversee the rules for account management by large companies, so that decision-making power is placed in the hands of a neutral party.</p>
]]></content:encoded><atom:updated>2026-06-25T20:17:45+02:00</atom:updated><category>blog</category><dc:subject>Lesezeit: 5 Minuten</dc:subject><dc:type>blog_post</dc:type></item><item><title>Arrma Gorgon: Purchase, Repairs and Costs</title><link>https://nickyreinert.de/en/2026/2026-04-27-about-rc-cars/</link><pubDate>Sun, 03 May 2026 11:34:56 +0000</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/en/2026/2026-04-27-about-rc-cars/</guid><description>And now to something completely different…
A few weeks ago I dived into a new “hobby”: remote-controlled semi‑pro vehicles — a birthday present for my child.
So I started researching. For me, the priorities were robustness and modularity. I wanted my kid to be able to drive through rough terrain and …</description><content:encoded><![CDATA[<p><em>And now to something completely different…</em></p>
<p>A few weeks ago I dived into a new &ldquo;hobby&rdquo;: remote-controlled semi‑pro vehicles — a birthday present for my child.</p>
<p>So I started researching. For me, the priorities were <strong>robustness</strong> and <strong>modularity</strong>. I wanted my kid to be able to drive through rough terrain and I knew that parts would occasionally break. That meant spare‑part availability had to be good.</p>
<p>At some point I found the <strong>Arrma Gorgon</strong>, a 1/10 monster truck. Retail price without battery or charger: <strong>€180</strong>. Not cheap, but reasonable. Since driving together is twice the fun, I bought <strong>two vehicles</strong>. I also ordered <strong>four LiPo batteries</strong>. (This is where the whole compatibility madness begins: LiPos need a dedicated charger.) Because the stock model uses <em>brushed motors</em>, I also bought <strong>two fans</strong> so I wouldn&rsquo;t be forced to stop after <strong>20 minutes</strong> of driving. And the fans came without screws, so I added a 500‑piece RC screw kit to the cart. It adds up — but there&rsquo;s more.</p>
<figure>
  <a href="https://nickyreinert.de/en/2026/2026-04-27-about-rc-cars/image.jpg" target="_blank" class="image-link " title="Click to view full size">
    <img 
      src="https://nickyreinert.de/en/2026/2026-04-27-about-rc-cars/image.jpg" 
      alt="the supplied screwdrivers for the Arrma Gorgon are terrible, the screw is completely worn out"
      width="600px"
      loading="lazy"
    />
  </a>
  <figcaption>The supplied screwdrivers for the Arrma Gorgon are terrible — the screw is completely worn out</figcaption>
</figure>

<p>More issues followed. For example, the fan connector didn&rsquo;t fit the controller, so out came the side cutters and I trimmed the protruding plastic. Great start for &ldquo;modularity.&rdquo;</p>
<figure>
  <a href="https://nickyreinert.de/en/2026/2026-04-27-about-rc-cars/image-5.jpg" target="_blank" class="image-link " title="Click to view full size">
    <img 
      src="https://nickyreinert.de/en/2026/2026-04-27-about-rc-cars/image-5.jpg" 
      alt="battery cover poorly finished and fixed with tape"
      width="600px"
      loading="lazy"
    />
  </a>
  <figcaption>Minor detail: the battery cover is poorly finished and had to be fixed with gaffer tape</figcaption>
</figure>

<p>Here’s my initial shopping list:</p>
<table>
	<thead>
			<tr>
					<th style="text-align: right">Qty</th>
					<th>Item</th>
					<th>Price</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td style="text-align: right">2</td>
					<td>1/10 GORGON 4X2 MEGA 550 Brushed Monster Truck RTR (ARA3230T1)</td>
					<td>160.20 EUR each</td>
			</tr>
			<tr>
					<td style="text-align: right">2</td>
					<td>High‑speed fan 40mm plastic 5V–8.4V, JST plug, 16,000 RPM (HSPX033)</td>
					<td>11.90 EUR</td>
			</tr>
			<tr>
					<td style="text-align: right">4</td>
					<td>5000mAh 2S 7.4V Smart G2 LiPo battery 30C hardcase – IC3 (SPMX52S30H3)</td>
					<td>122.27 EUR + 40.76 EUR</td>
			</tr>
			<tr>
					<td style="text-align: right">2</td>
					<td>Smart S100 G2 USB‑C charger (SPMXC2090)</td>
					<td>27.10 + 27.10 EUR</td>
			</tr>
			<tr>
					<td style="text-align: right">1</td>
					<td>500‑piece RC screw kit</td>
					<td>13.99 EUR</td>
			</tr>
			<tr>
					<td style="text-align: right">1</td>
					<td>Shipping</td>
					<td>4.90 EUR</td>
			</tr>
	</tbody>
</table>
<p>The first day was a blast — the electric motors have plenty of power and we had an enormous amount of fun. But already on day one the first disappointment: a front suspension rod broke.</p>
<p>I temporarily repaired the shock using a suitable piece of hose coupling until the replacement shock set arrived — fixed professionally with gaffer tape, naturally.</p>
<figure>
  <a href="https://nickyreinert.de/en/2026/2026-04-27-about-rc-cars/image-1.jpg" target="_blank" class="image-link " title="Click to view full size">
    <img 
      src="https://nickyreinert.de/en/2026/2026-04-27-about-rc-cars/image-1.jpg" 
      alt="temporary repair of shock absorber with a piece of hose and duct tape"
      width="600px"
      loading="lazy"
    />
  </a>
  <figcaption>Temporary repair of the shock absorber using a piece of hose and, of course, duct tape</figcaption>
</figure>

<p>Costs for that repair:</p>
<table>
	<thead>
			<tr>
					<th style="text-align: right">Qty</th>
					<th>Item</th>
					<th>Price</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td style="text-align: right">1</td>
					<td>Shock set 11mm bore, 116mm length, 500 cSt oil (ARA330756)</td>
					<td>23.30 EUR</td>
			</tr>
			<tr>
					<td style="text-align: right">1</td>
					<td>Shipping</td>
					<td>3.90 EUR</td>
			</tr>
	</tbody>
</table>
<p>The next day brought the next surprise: within a few hours the steering failed. At first it&rsquo;s confusing since it&rsquo;s not immediately clear what&rsquo;s broken. Eventually you find the steering servo — and it failed on day two, on both vehicles.</p>
<figure>
  <a href="https://nickyreinert.de/en/2026/2026-04-27-about-rc-cars/image-2.jpg" target="_blank" class="image-link " title="Click to view full size">
    <img 
      src="https://nickyreinert.de/en/2026/2026-04-27-about-rc-cars/image-2.jpg" 
      alt="internal plastic debris inside servo"
      width="600px"
      loading="lazy"
    />
  </a>
  <figcaption>Looks lubricated, but not durable — some white plastic debris fell out when I opened it</figcaption>
</figure>

<figure>
  <a href="https://nickyreinert.de/en/2026/2026-04-27-about-rc-cars/image-9.jpg" target="_blank" class="image-link " title="Click to view full size">
    <img 
      src="https://nickyreinert.de/en/2026/2026-04-27-about-rc-cars/image-9.jpg" 
      alt="fiddly work attaching the control arms to the servo"
      width="600px"
      loading="lazy"
    />
  </a>
  <figcaption>Fiddly work: attaching the control arms to the servo is no fun</figcaption>
</figure>

<p>From my perspective the issue is clear: the shock protects the body but not the tires. The tires are simple, tubeless, very soft rubber. Every collision transfers force directly to the control arms, the steering and the suspension. The servos use <em>cheap</em> plastic gears that don&rsquo;t last. That&rsquo;s a design flaw — or am I the only one noticing?</p>
<figure>
  <a href="https://nickyreinert.de/en/2026/2026-04-27-about-rc-cars/image-14.jpg" target="_blank" class="image-link " title="Click to view full size">
    <img 
      src="https://nickyreinert.de/en/2026/2026-04-27-about-rc-cars/image-14.jpg" 
      alt="cutting the plug with side cutters"
      width="600px"
      loading="lazy"
    />
  </a>
  <figcaption>Hacky mod: trimming the protruding part of the connector with side cutters</figcaption>
</figure>

<p>So I grabbed the craft scissors, cut a piece of HD‑PE pipe and screwed it to the small shock.</p>
<figure>
  <a href="https://nickyreinert.de/en/2026/2026-04-27-about-rc-cars/image-12.jpg" target="_blank" class="image-link " title="Click to view full size">
    <img 
      src="https://nickyreinert.de/en/2026/2026-04-27-about-rc-cars/image-12.jpg" 
      alt="hd-pe pipe mod"
      width="600px"
      loading="lazy"
    />
  </a>
  <figcaption>Brute‑force tuning: a cut 32mm HD‑PE pipe attached to the shock</figcaption>
</figure>

<p>I ordered a new pair of servos.</p>
<p>Costs for the servos:</p>
<table>
	<thead>
			<tr>
					<th style="text-align: right">Qty</th>
					<th>Item</th>
					<th>Price</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td style="text-align: right">1</td>
					<td>Miuzei digital servo 25kg 270° pair for mini RC cars (1/8, 1/10, 1/12)</td>
					<td>24.98 EUR</td>
			</tr>
	</tbody>
</table>
<p>The installation is quite fiddly because the control arms sit under a plastic brace. Eventually it was fixed and we went out again…</p>
<p>But then the next problem surfaced:</p>
<p>Less than a week later the drive motors failed. Troubleshooting was tricky because it&rsquo;s not obvious whether the battery, controller or motor itself is at fault.</p>
<p>Eventually it became clear: both motors were defective, despite the added cooling. I contacted support, which I probably should have done earlier. The dilemma: I wanted to keep the vehicles because my kid had already grown attached to them.</p>
<figure>
  <a href="https://nickyreinert.de/en/2026/2026-04-27-about-rc-cars/image-13.jpg" target="_blank" class="image-link " title="Click to view full size">
    <img 
      src="https://nickyreinert.de/en/2026/2026-04-27-about-rc-cars/image-13.jpg" 
      alt="open motor exposed above the ground"
      width="600px"
      loading="lazy"
    />
  </a>
  <figcaption>The open (!) motor sits unprotected just above ground — how long is that supposed to work, Arrma?</figcaption>
</figure>

<p>Support was friendly and quickly offered replacements for both motors. Not wanting a fight, I ignored the broken shocks and servos and accepted the replacement motors — I just wanted to drive.</p>
<figure>
  <a href="https://nickyreinert.de/en/2026/2026-04-27-about-rc-cars/image-4.jpg" target="_blank" class="image-link " title="Click to view full size">
    <img 
      src="https://nickyreinert.de/en/2026/2026-04-27-about-rc-cars/image-4.jpg" 
      alt="original vs replacement motors"
      width="600px"
      loading="lazy"
    />
  </a>
  <figcaption>Left: original motors — Right: replacements</figcaption>
</figure>

<p>When the replacements arrived I installed them and everything initially seemed fine. After a few minutes of driving smoke rose from one truck. This time it was user error: in my excitement I forgot to reconnect the motor/controller insulating sleeve — short circuit.</p>
<p>And a short circuit takes several components with it because there appears to be no fuse.</p>
<p>Okay, I told myself, I wanted to upgrade to brushless motors anyway. So the research machine started again. One tip: don&rsquo;t blindly trust Gemini or ChatGPT — they often recommend things that simply don&rsquo;t fit. Compatibility matters and the AI often misses that.</p>
<p>I found what looked like a perfect brushless combo, but a motor doesn&rsquo;t come alone: you need a matching ESC and typically a separate receiver. The stock controller/receiver is mounted with screws — the new receiver had no mounting points.</p>
<figure>
  <a href="https://nickyreinert.de/en/2026/2026-04-27-about-rc-cars/image-11.jpg" target="_blank" class="image-link " title="Click to view full size">
    <img 
      src="https://nickyreinert.de/en/2026/2026-04-27-about-rc-cars/image-11.jpg" 
      alt="how to attach the new components"
      width="600px"
      loading="lazy"
    />
  </a>
  <figcaption>How am I supposed to secure this? Tape, zip ties, hope?</figcaption>
</figure>

<p>Anyway, the new drive combo arrived and I tested it on the bench: controller + motor + battery.</p>
<p>The battery? It had an IC3 connector — plus and minus plus a small pin for the charger to query capacity/status. The ESC had an EC3 connector. They don&rsquo;t match.</p>
<p>So out came the adapters.</p>
<table>
	<thead>
			<tr>
					<th style="text-align: right">Qty</th>
					<th>Item</th>
					<th>Price</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td style="text-align: right">1</td>
					<td>EC3 male to XT60 adapter with 5cm 14AWG wire for RC LiPo</td>
					<td>8.99 EUR</td>
			</tr>
	</tbody>
</table>
<p>Adapter arrived. Adapter connected. ESC connected. Battery connected. <strong>Motor: runs.</strong></p>
<p><figure>
  <a href="https://nickyreinert.de/en/2026/2026-04-27-about-rc-cars/image-6.jpg" target="_blank" class="image-link " title="Click to view full size">
    <img 
      src="https://nickyreinert.de/en/2026/2026-04-27-about-rc-cars/image-6.jpg" 
      alt="ic3 connector with telemetry pin"
      width="600px"
      loading="lazy"
    />
  </a>
  <figcaption>The IC3 connector with telemetry pin</figcaption>
</figure>

<figure>
  <a href="https://nickyreinert.de/en/2026/2026-04-27-about-rc-cars/image-7.jpg" target="_blank" class="image-link " title="Click to view full size">
    <img 
      src="https://nickyreinert.de/en/2026/2026-04-27-about-rc-cars/image-7.jpg" 
      alt="standard ec3 connector"
      width="600px"
      loading="lazy"
    />
  </a>
  <figcaption>The standard EC3 connector</figcaption>
</figure>

<figure>
  <a href="https://nickyreinert.de/en/2026/2026-04-27-about-rc-cars/image-8.jpg" target="_blank" class="image-link " title="Click to view full size">
    <img 
      src="https://nickyreinert.de/en/2026/2026-04-27-about-rc-cars/image-8.jpg" 
      alt="adapter cables"
      width="600px"
      loading="lazy"
    />
  </a>
  <figcaption>And those are the adapters you end up using</figcaption>
</figure>
</p>
<p>So far, so good — until I tried to mount the motor. The stock motor routes its wires out the back; the new motor has a side exit and is shorter. In short: <strong>it doesn&rsquo;t fit</strong>. Frustration ensued.</p>
<p>Costs for the brushless combo that I ended up returning:</p>
<table>
	<thead>
			<tr>
					<th style="text-align: right">Qty</th>
					<th>Item</th>
					<th>Price</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td style="text-align: right">1</td>
					<td>Quicrun brushless combo WP10BL120G2 with 3652SL‑3250KV‑G2 for 1:10</td>
					<td>84.95 EUR</td>
			</tr>
			<tr>
					<td style="text-align: right">1</td>
					<td>SLR300 3‑channel SLT receiver (single protocol)</td>
					<td>27.48 EUR</td>
			</tr>
			<tr>
					<td style="text-align: right">2</td>
					<td>Shipping</td>
					<td>5.99 EUR</td>
			</tr>
	</tbody>
</table>
<p>I emailed the seller and returned the motor (shipping at my cost). Research continued: the motor needed to be longer or have rear‑exit wires. I found another candidate:</p>
<table>
	<thead>
			<tr>
					<th style="text-align: right">Qty</th>
					<th>Item</th>
					<th>Price</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td style="text-align: right">1</td>
					<td>Quicrun brushless combo WP10BL120G2 with 3660SL‑3150KV‑G2 (HW38030210)</td>
					<td>82.75 EUR</td>
			</tr>
			<tr>
					<td style="text-align: right">1</td>
					<td>Shipping</td>
					<td>5.90 EUR</td>
			</tr>
	</tbody>
</table>
<p>Ordered, waited, received — with a tiny spark of hope.</p>
<figure>
  <a href="https://nickyreinert.de/en/2026/2026-04-27-about-rc-cars/image-10.jpg" target="_blank" class="image-link " title="Click to view full size">
    <img 
      src="https://nickyreinert.de/en/2026/2026-04-27-about-rc-cars/image-10.jpg" 
      alt="motor length check"
      width="600px"
      loading="lazy"
    />
  </a>
  <figcaption>Shouldn&rsquo;t be a millimeter shorter</figcaption>
</figure>

<p>The longer motor fit the case exactly — great — but then the pinion issue appeared: the longer motor&rsquo;s shaft is thicker (<strong>5 mm</strong>) while the stock pinion has a <strong>3.175 mm</strong> bore.</p>
<p>More searching led me to a suitable pinion on Amazon. Two days later it arrived and looked perfect for the 5 mm shaft.</p>
<table>
	<thead>
			<tr>
					<th style="text-align: right">Qty</th>
					<th>Item</th>
					<th>Price</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td style="text-align: right">1</td>
					<td>13T Mod1 Safe‑D5 pinion</td>
					<td>9.99 EUR</td>
			</tr>
	</tbody>
</table>
<p>But it didn&rsquo;t fit. The teeth barely meshed with the gearbox gear and the diameter was too large, causing excessive friction. The plastic gearbox gear wouldn&rsquo;t take the load for long.</p>
<p>Rage. Despair. Doubt. The hunt continued: the right pinion should have no more than <strong>12 teeth</strong>, module <strong>0.8</strong>, and a Safe‑D (D‑shaped) bore to prevent slipping. I skipped the Safe‑D requirement and hoped a plain 5 mm bore pinion with 12 teeth and 0.8 module would work:</p>
<table>
	<thead>
			<tr>
					<th style="text-align: right">Qty</th>
					<th>Item</th>
					<th>Price</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td style="text-align: right">1</td>
					<td>5‑piece hardened steel pinion set, 5 mm bore — 11T,12T,13T,14T,15T</td>
					<td>15.99 EUR</td>
			</tr>
	</tbody>
</table>
<p>That pinion worked. The drivetrain was back together, but this is a jury‑rigged long‑term provisional solution. The grub screw that came with the pinion was too long (WTF), so you must be careful not to push the pinion on too far. The IC3→EC3 adapter also eats space in the battery bay, making battery swaps fiddly. Finally, you need to secure receiver, ESC and switch somewhere in the chassis — there are mounting holes, but you need a kind of cage. For now I used Velcro and double‑sided tape. The result is a chaotic setup far from the modularity I hoped for.</p>
<figure>
  <a href="https://nickyreinert.de/en/2026/2026-04-27-about-rc-cars/image-15.jpg" target="_blank" class="image-link " title="Click to view full size">
    <img 
      src="https://nickyreinert.de/en/2026/2026-04-27-about-rc-cars/image-15.jpg" 
      alt="grub screw vs gearbox"
      width="600px"
      loading="lazy"
    />
  </a>
  <figcaption>Grub screw and gearbox — tight fit</figcaption>
</figure>

<figure>
  <a href="https://nickyreinert.de/en/2026/2026-04-27-about-rc-cars/image-16.jpg" target="_blank" class="image-link " title="Click to view full size">
    <img 
      src="https://nickyreinert.de/en/2026/2026-04-27-about-rc-cars/image-16.jpg" 
      alt="no mounting options for new components"
      width="600px"
      loading="lazy"
    />
  </a>
  <figcaption>No mounting options for the new components</figcaption>
</figure>

<figure>
  <a href="https://nickyreinert.de/en/2026/2026-04-27-about-rc-cars/image-17.jpg" target="_blank" class="image-link " title="Click to view full size">
    <img 
      src="https://nickyreinert.de/en/2026/2026-04-27-about-rc-cars/image-17.jpg" 
      alt="thanks adapter cable"
      width="600px"
      loading="lazy"
    />
  </a>
  <figcaption>Thanks, adapter cable — tight fit</figcaption>
</figure>

<figure>
  <a href="https://nickyreinert.de/en/2026/2026-04-27-about-rc-cars/image-18.jpg" target="_blank" class="image-link " title="Click to view full size">
    <img 
      src="https://nickyreinert.de/en/2026/2026-04-27-about-rc-cars/image-18.jpg" 
      alt="permanent bodge with tape and velcro"
      width="600px"
      loading="lazy"
    />
  </a>
  <figcaption>A permanent bodge with tape and Velcro</figcaption>
</figure>

<h2 id="conclusion">Conclusion</h2>
<p>First — self‑criticism: the short circuit was my fault. That was spectacularly stupid.</p>
<p><code>༼ʘ̚ ʖ ʘ̚༽</code></p>
<p>That said, once you know what to look for the technical specs aren&rsquo;t that hard to understand. You just have to know where to look. A quick note: I initially tried to use Gemini, ChatGPT and Perplexity to find the right parts. The AIs repeatedly recommended unsuitable parts because they didn&rsquo;t take compatibility details into account.</p>
<p>Even specific follow‑up questions often led to wrong links or bad recommendations, so I paid a lot of tuition. My tip: talk to the vendor directly if possible — don&rsquo;t rely exclusively on AI for specific hardware compatibility.</p>
<p>At least one thing came out of it: tinkering is fun. For anyone interested in swapping the motor on an <strong>Arrma Gorgon</strong>, here are the essentials — and many points apply to other vehicles too:</p>
<ul>
<li>There are many battery types. LiPo is the de facto standard; each battery class needs a compatible charger. Check whether the battery has a telemetry pin on the connector.</li>
<li>The motor must be long enough if the wires exit the side — aim for at least <strong>60 mm</strong> length; too long is also a problem. Short motors typically route wires out the rear.</li>
<li>You usually need a new ESC and possibly a separate receiver. Some combos include everything, some don&rsquo;t.</li>
<li>Pay attention to connector types: <strong>EC3</strong>, <strong>IC3</strong>, <strong>XT60</strong>, male/female — they matter.</li>
<li>The stock pinion has a <strong>3.175 mm</strong> bore — check the motor shaft diameter and the pinion module (tooth size).</li>
<li>The original shaft often has a D‑shape (Safe‑D) to prevent slipping; a simple round shaft requires a suitable pinion and grub screw.</li>
<li>The number of teeth and the module must match the gearbox gear; changing pinions changes the drive ratio.</li>
<li>If you add a fan you may need longer screws.</li>
<li>Make sure the new motor can be secured to the chassis — some mounts require custom brackets or Velcro.</li>
</ul>
<p>Final note on quality and design: the stock brushed motor is exposed near the drive shaft and is vulnerable to dust ingress via the ventilation slots. That is not ideal for long‑term off‑road use. A closed brushless motor is a sensible upgrade if you plan to run the truck in rough conditions.</p>
<p>The decision to use the long front bumper is debatable — while it protects the chassis it also transfers impact forces to the control arms and servos, increasing the risk of servo failures. The long control arms act as levers that strain the servo&rsquo;s plastic gears.</p>
<p>In my view the Arrma Gorgon in stock trim is not designed for hard off‑road use — despite what the promo videos suggest.</p>
<p>





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://www.arrma-rc.com/de/product/1-10-gorgon-2wd-rtr-brushed-monster-truck-blue/ARA3230T1.html?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">https://www.arrma-rc.com/de/product/1-10-gorgon-2wd-rtr-brushed-monster-truck-blue/ARA3230T1.html</a>


</p>
<p>But maybe Arrma has an explanation?</p>
]]></content:encoded><atom:updated>2026-07-03T09:08:16+02:00</atom:updated><category>blog</category><dc:subject>Lesezeit: 12 Minuten</dc:subject><dc:type>blog</dc:type></item><item><title>Numbers, please — about anthropic</title><link>https://nickyreinert.de/en/2026/2026-05-02-numbers-please/</link><pubDate>Sun, 03 May 2026 11:34:56 +0000</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/en/2026/2026-05-02-numbers-please/</guid><description> 1/300 - ratio of support staff to users in traditional SaaS companies B2B [1]
1/3000 - max ratio of support staff to users in traditional SaaS companies B2C [2]
30 Bn - annual revenue of Anthropic [3]
5000 - number of employees at Anthropic [4]
18 - 30 million - estimated monthly active users at …</description><content:encoded><![CDATA[<ul>
<li>
<p><strong>1/300</strong> - ratio of support staff to users in traditional SaaS companies B2B [1]</p>
</li>
<li>
<p><strong>1/3000</strong> - max ratio of support staff to users in traditional SaaS companies B2C [2]</p>
</li>
<li>
<p><strong>30 Bn</strong> - annual revenue of Anthropic [3]</p>
</li>
<li>
<p><strong>5000</strong> - number of employees at Anthropic [4]</p>
</li>
<li>
<p><strong>18 - 30 million</strong> - estimated monthly active users at Anthropic [5]</p>
</li>
<li>
<p><strong>2 Mn</strong> - number of B2C customers of Anthropic [6]</p>
</li>
<li>
<p><strong>300.000</strong> - estimated number of B2B customers of Anthropic [7]</p>
</li>
<li>
<p><strong>98.67%</strong> - availability of claud.ai</p>
</li>
<li>
<p><strong>11%</strong> - YoY increase in job postings for software engineers [8]</p>
</li>
</ul>
<img src="https://nickyreinert.de/en/2026/2026-05-02-numbers-please/image.png" alt="alt text"><p>[1] 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://www.valuize.co/customer-value-equation/?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">https://www.valuize.co/customer-value-equation/</a>



[2] 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://count.co/metric/peak-support-hours-analysis?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">https://count.co/metric/peak-support-hours-analysis</a>



[3] 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://thenextweb.com/news/anthropic-800-billion-valuation-revenue-30-billion-ipo?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">https://thenextweb.com/news/anthropic-800-billion-valuation-revenue-30-billion-ipo</a>



[4] 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://www.saastr.com/anthropic-only-has-5000-employees-almost-no-one-has-ever-been-this-efficient-thats-by-choice/?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">https://www.saastr.com/anthropic-only-has-5000-employees-almost-no-one-has-ever-been-this-efficient-thats-by-choice/</a>



[5] 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://www.pymnts.com/artificial-intelligence-2/2026/anthropics-claude-draws-in-record-number-of-consumers/?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">https://www.pymnts.com/artificial-intelligence-2/2026/anthropics-claude-draws-in-record-number-of-consumers/</a>



[6] 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://mlq.ai/news/anthropics-claude-experiences-record-surge-in-paid-subscriber-growth-throughout-2026/?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">https://mlq.ai/news/anthropics-claude-experiences-record-surge-in-paid-subscriber-growth-throughout-2026/</a>



[7] 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://www.getpanto.ai/blog/anthropic-ai-statistics?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">https://www.getpanto.ai/blog/anthropic-ai-statistics</a>



[8] 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://www.citadelsecurities.com/news-and-insights/2026-global-intelligence-crisis/?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">https://www.citadelsecurities.com/news-and-insights/2026-global-intelligence-crisis/</a>


</p>
]]></content:encoded><atom:updated>2026-07-03T09:08:16+02:00</atom:updated><category>blog</category><dc:subject>Lesezeit: 1 Minuten</dc:subject><dc:type>note</dc:type></item><item><title>pictero.com</title><link>https://nickyreinert.de/en/2026/2026-05-03-pictero/</link><pubDate>Sun, 03 May 2026 11:34:56 +0000</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/en/2026/2026-05-03-pictero/</guid><description>Well, another year, another idea for my domain pictero.com. I was about to get rid of that domain for good. But here we are. This time I spend a little more time in a “product” - or whatever you want to call it and I came up with this idea:
A (mostly) browser based toolbox for a couple of common, …</description><content:encoded><![CDATA[<p>Well, another year, another idea for my domain pictero.com. I was about to get rid of that domain for good. But here we are. This time I spend a little more time in a &ldquo;product&rdquo; - or whatever you want to call it and I came up with this idea:</p>
<p>A (mostly) browser based toolbox for a couple of common, simple image editing tasks:</p>
<ul>
<li>resizing, cropping and color adjustments</li>
<li>pattern generation with a couple of different algorithms</li>
<li>adding annotations like text or shapes</li>
<li>showing common social media formats</li>
<li>OCR extraction with tesseract, in the browser</li>
<li>simple animations like Ken Burns</li>
<li>ASCII-converter</li>
<li>steganography</li>
<li>and of course EXIF data viewer, reverse image search, etc.</li>
</ul>
<p>So there&rsquo;s a lot to explore here. Some functions require a backend, like I built in an &ldquo;AI image identifier&rdquo;. Also the user has (very limited) access to some free external image databases, like 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://picsum.photos/?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">picsum.photos</a>


 or 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://unsplash.com/de?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">unsplash.com</a>


. As CORS would block requests like that, I implemented a server side proxy for that.</p>
<img src="https://nickyreinert.de/en/2026/2026-05-03-pictero/image.png" alt="alt text">]]></content:encoded><atom:updated>2026-07-03T09:08:16+02:00</atom:updated><category>blog</category><dc:subject>Lesezeit: 10 Minuten</dc:subject><dc:type>blog</dc:type></item><item><title>Why I Cancelled Claude: Token Issues, Declining Quality, and Poor Support</title><link>https://nickyreinert.de/en/2026/2026-04-24-claude-critics/</link><pubDate>Fri, 24 Apr 2026 10:34:56 +0000</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/en/2026/2026-04-24-claude-critics/</guid><description>Prefix Note As this post is gaining more attention than expected on HN, I want to make some things clear - although I thought they are obvious.
It sounds like a rant about Claude’s quality in general - if you don’t read to the end. But my concerns are more focused on the support performance and …</description><content:encoded><![CDATA[<h2 id="prefix-note">Prefix Note</h2>
<p>As this post is gaining more attention than expected on HN, I want to make some things clear - although I thought they are obvious.</p>
<p>It sounds like a rant about Claude&rsquo;s quality in general - if you don&rsquo;t read to the end. But my concerns are more focused on the <strong>support performance</strong> and <strong>token issues</strong> - fully aware of the challenges a company this size faces and assuming the people at Anthropic are working hard to make things better. I am pointing at some &ldquo;<strong>bad design decisions</strong>&rdquo; they probably made. The &ldquo;quality issues&rdquo; are just the cherry on top of the cake.</p>
<p>After all, Claude Code is <em>delivering</em> and I use it to build stuff. Still, I experienced a degradation in quality. It just takes longer. Which is a relative observation. However I know that this is highly subjective, too. That&rsquo;s what that comment in a paragraph means: &ldquo;<em>The failure usually appears in front of the screen</em>&rdquo;. Of course the agent is only as good as the operator and their instructions.</p>
<p>I am coding for a couple of decades now, I like to get my hands dirty. Three years ago I implemented AI into my workflow. It started with code completion and now I am at the point where I barely write code. For me &ldquo;software engineeering&rdquo; is not constituted by the simple act of writing code. It&rsquo;s about conducting tools, being creative, understanding the problem and delivering a solution. Still - and that what this comment in another paragraph means: &ldquo;<em>While I was browsing the model&rsquo;s thinking log - which I strongly suggest doing not only occasionally</em>&rdquo; - I am escorting the agent while it&rsquo;s working. I still have to figure out a concept, think about a data model and verify it&rsquo;s implementation. That&rsquo;s what software engineering is about. It&rsquo;s not <em>n LOC</em>…</p>
<p>Having said that - enjoy this post and stay happy, fellow developers.</p>
<h2 id="first-enthusiasm">First enthusiasm</h2>
<p>A couple of weeks ago I subscribed to Claude Code, and during the first few weeks I had a really nice experience. It was fast, the token allowance was fair, and the quality was good.</p>
<p>I learned they had 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://support.claude.com/en/articles/14063676-claude-march-2026-usage-promotion?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">raised the token allowance for non-rush hours</a>


, and since they opposed some governmental rules, it felt good to support the right cause.</p>
<p><code>(づ ￣ ³￣)づ</code></p>
<p>However… for about three weeks now my initial enthusiasm has been rapidly waning.</p>
<h2 id="poor-support">Poor support</h2>
<p>It began with an issue three weeks ago. I started working in the morning after about a ten-hour break; enough time for my tokens to refresh.</p>
<p>I sent two small questions to <strong>Claude Haiku</strong>. They were simple questions, not even related to the repository.</p>
<p>Suddenly, token usage spiked to 100%.</p>
<p><em>Have a nice break…</em></p>
<p>I contacted their &ldquo;AI support bot&rdquo;, which returned some default support nonsense and didn&rsquo;t really understand the problem. So I asked for human support. A couple of days later a - what appeared to be - human support person sent a reply. It began like this:</p>
<p>&ldquo;Our systems are detecting your inquiry is regarding usage limits on your <strong>Pro or Max plan.</strong>&rdquo;</p>
<p>Yeah, well — it&rsquo;s the Pro plan. Seems like your systems weren&rsquo;t actually queried; it was just a default intro and probably a default answer, because:</p>
<p>This was followed by an extensive what seems to be copy-and-paste answer from their docs explaining how daily and weekly limits work.</p>
<p>And it closed with the typically frustrating line, that no customer likes to read at the end of an e-mail and which is just the classical middle-finger of customer support - we don&rsquo;t care if your problem is solved or not, we declared it closed.</p>
<p>&ldquo;Note that further replies to this ticket may not be monitored. If your request is not regarding usage limits on your Pro or Max plan, or you need additional support, please visit our help page at&rdquo;</p>
<p>Great! Sending an automated e-mail that does not refer to the actual problem and then closing the channel. Thanks for nothing, I guess? Or was I wrong. I asked Claude Haiku:</p>
<p>@Haiku:</p>
<blockquote>
<p>See the customer&rsquo;s request here and the response from the AI and later W***** - did they answer the concern/question of the customer?</p>
</blockquote>
<figure>
  <a href="https://nickyreinert.de/en/2026/2026-04-24-claude-critics/image-4.png" target="_blank" class="image-link " title="Click to view full size">
    <img 
      src="https://nickyreinert.de/en/2026/2026-04-24-claude-critics/image-4.png" 
      alt="Customer support response"
      width="600"
      loading="lazy"
    />
  </a>
  <figcaption>Customer support response</figcaption>
</figure>

<p><code>(╯°_°）╯︵ ┻━┻</code></p>
<h2 id="declining-quality">Declining quality</h2>
<p>In the following days and weeks, the quality was far from satisfying my needs or matching my initial experience. While I used to be able to work on up to three projects at once, now the token limit was exhausted after two hours on a single project.</p>
<p>And the quality was degrading. I am fully aware this is quite subjective and that the quality of the agent is always heavily impacted by the operator. The failure usually appears in front of the screen. But hey, I also develop using <strong>Github&rsquo;s Copilot</strong>, <strong>OpenAI&rsquo;s Codex</strong> and I am running my own inference with <strong>OMLX</strong> and <strong>Continue</strong> using <strong>Qwen3.5-9B</strong>. I&rsquo;m not <em>the</em> expert, I&rsquo;m lazy sometimes but I probably know a thing or two.</p>
<p>Let me give you this wonderful example: yesterday I asked <strong>Claude Opus</strong> to refactor a project.</p>
<p>While I was browsing the model&rsquo;s thinking log - which I strongly suggest doing not only occasionally - I found this:</p>
<blockquote>
<p>Rather than editing every slider in JSX, I&rsquo;ll add a generic initializer in ui-events.js that auto-injects value displays for all range inputs that lack one.</p>
</blockquote>
<p>This is clearly bad practice. It&rsquo;s a cheap workaround you wouldn&rsquo;t expect even from a junior dev; it reads like someone who just doesn&rsquo;t want to deliver a good result. My response:</p>
<p>&ldquo;you can&rsquo;t be serious — is this how you fix things? just WORKAROUNDS????&rdquo;</p>
<p>At least <strong>Opus</strong> admitted:</p>
<p>&ldquo;You&rsquo;re right, that was lazy. Let me do it properly — add the <span> labels directly in the JSX and wire them explicitly.&rdquo;</p>
<figure>
  <a href="https://nickyreinert.de/en/2026/2026-04-24-claude-critics/image-1.png" target="_blank" class="image-link " title="Click to view full size">
    <img 
      src="https://nickyreinert.de/en/2026/2026-04-24-claude-critics/image-1.png" 
      alt="The lazy developer - at least honest"
      width="600"
      loading="lazy"
    />
  </a>
  <figcaption>The lazy developer - at least honest</figcaption>
</figure>

<p>Needless to say, this shortcut cost me around 50% of my five-hour token allowance.</p>
<p><code>(ง •̀_•́)ง</code></p>
<h2 id="and-even-more">And even more…</h2>
<p>





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://www.anthropic.com/engineering/april-23-postmortem?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">Now this cache topic comes up</a>


 - 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://news.ycombinator.com/item?id=47878905&amp;utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">among others</a>


. at least they are talking about it openly. The problem was: when you get back to work after some time, your conversation cache is gone and the model starts reading your codebase again. Cost-wise this is smart. But experience-wise? It means you paid tokens for the initial load and, after a forced break because the five-hour token window hit its limit, you pay again for the same load.</p>
<p>Think that&rsquo;s all? Wait, I also got this funny anecdote: all of a sudden the weekly window changed from today to Monday. OK, I was thankful because it came with a reset to zero. But still: what is going on, Anthropic? Not only that — while I was working on my project, watching token usage with <strong>Argus-eyed vigilance</strong>, this little warning popped up:</p>
<figure>
  <a href="https://nickyreinert.de/en/2026/2026-04-24-claude-critics/image.png" target="_blank" class="image-link " title="Click to view full size">
    <img 
      src="https://nickyreinert.de/en/2026/2026-04-24-claude-critics/image.png" 
      alt="Token limit warning"
      width="600"
      loading="lazy"
    />
  </a>
  <figcaption>Token limit warning - but I was still within the limits?</figcaption>
</figure>

<p>Wait, what? I&rsquo;m neither part of an organization nor do I see any hint why I suddenly have to worry about a &ldquo;monthly usage limit&rdquo; — also the hourly and weekly limits were still not exceeded. What is happening right now?</p>
<figure>
  <a href="https://nickyreinert.de/en/2026/2026-04-24-claude-critics/image-2.png" target="_blank" class="image-link " title="Click to view full size">
    <img 
      src="https://nickyreinert.de/en/2026/2026-04-24-claude-critics/image-2.png" 
      alt="Token limit warning - but I was still within the limits?"
      width="600"
      loading="lazy"
    />
  </a>
  <figcaption>Token limit warning - but I was still within the limits?</figcaption>
</figure>

<p>Turns out — two hours later - it allowed me to continue working. The warning was gone.</p>
<figure>
  <a href="https://nickyreinert.de/en/2026/2026-04-24-claude-critics/image-3.png" target="_blank" class="image-link " title="Click to view full size">
    <img 
      src="https://nickyreinert.de/en/2026/2026-04-24-claude-critics/image-3.png" 
      alt="Token limit warning disappeared"
      width="600"
      loading="lazy"
    />
  </a>
  <figcaption>Token limit warning disappeared - but what was it about?</figcaption>
</figure>

<p>At least 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://support.claude.com/en/articles/11647753-how-do-usage-and-length-limits-work?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">this documentation</a>


 does not mention a monthly usage limit. And the settings page only lists the limits for the current session and week.</p>
<figure>
  <a href="https://nickyreinert.de/en/2026/2026-04-24-claude-critics/image-5.png" target="_blank" class="image-link " title="Click to view full size">
    <img 
      src="https://nickyreinert.de/en/2026/2026-04-24-claude-critics/image-5.png" 
      alt="Token limits documentation"
      width="600"
      loading="lazy"
    />
  </a>
  <figcaption>Token limits documentation - no mention of monthly limits</figcaption>
</figure>

<p>So… what is this monthly limit all about, <strong>Anthropic</strong>?</p>
<h2 id="sorry-to-let-you-down-anthropic">Sorry to let you down, Anthropic</h2>
<p>I am a huge fan of the product. Theoretically everything just works like a charm; it offers so many opportunities. I built my 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://nickyreinert.de/2026/2026-03-29-ai-butler/?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">own harness around Claude</a>


, I admire <strong>Claude Caude</strong> who work&rsquo;s in the background on a bunch of <strong>GitHub</strong> issues, I love using <strong>Claude Cowork</strong> to continue writing my 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://nickyreinert.de/2025/2025-04-20-nerd-enzyklop%C3%A4die-48---alte-software/?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">Nerd Enzyklopädie</a>


. So many thoughful features.</p>
<p>I increased my productivity by an order of magnitude, and it&rsquo;s really thrilling to see how these trillions of ideas crawling through my head are now only a blink away - easier and quicker to realize than four years ago.</p>
<p>And I understand the technical and organizational challenges when offering a product like that. It&rsquo;s not easy to benefit from scaling effects when you sell inference. Every additional hour and every new customer requires the same amount of compute. That&rsquo;s the curse of incremental costs in this line of business.</p>
<p>But…</p>
<p>…it seems like Anthropic cannot handle too many new customers at once, so I took that load off Anthropic and cancelled my account.</p>
<p><code>(ʘ‿ʘ)╯</code></p>
]]></content:encoded><atom:updated>2026-07-03T10:44:03+02:00</atom:updated><category>blog</category><dc:subject>Lesezeit: 6 Minuten</dc:subject><dc:type>blog</dc:type></item><item><title>How to create an animated ticker in Excel without VBA</title><link>https://nickyreinert.de/en/2025/2025-10-26-how-to-create-excel-ticker/</link><pubDate>Sun, 26 Oct 2025 12:34:56 +0100</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/en/2025/2025-10-26-how-to-create-excel-ticker/</guid><description>How to create an animated ticker in Excel without VBA? Last night, around 3 am, I woke up and thought: Wouldn’t it be nice to have an animated ticker in Excel? Like text that scrolls from right to left? With just one big formula? Welcome to the second episode of my series “How to create weird …</description><content:encoded><![CDATA[<h2 id="how-to-create-an-animated-ticker-in-excel-without-vba">How to create an animated ticker in Excel without VBA?</h2>
<p>Last night, around 3 am, I woke up and thought: Wouldn’t it be nice to have an animated ticker in Excel? Like text that scrolls from right to left? With just one big formula? Welcome to the second episode of my series “How to create weird algorithms” First one was about 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://medium.com/swlh/how-to-create-a-maze-with-javascript-36f3ad8eebc1?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">creating a maze in JavaScript</a>


). In this article we will create a nested Excel formula to solve a problem that probably not knows exists. Until now.</p>
<h3 id="tyl--today-youll-learn">TYL — Today You’ll Learn</h3>
<ul>
<li>How to build complex nested formulas from the scratch</li>
<li>The MAP-formula to create an array and modify it’s values</li>
<li>The LET-formula to declare variables to clean up nested formulas</li>
<li>How self-referencing cells in Excel could be useful</li>
<li>How to use INDIRECT and R/C referencing</li>
</ul>
<img src="https://nickyreinert.de/en/2025/2025-10-26-how-to-create-excel-ticker/spoiler-code.png" alt="https://nickyreinert.de/en/2025/2025-10-26-how-to-create-excel-ticker/spoiler-code.png" alt="Spoiler: Code" class="center" width="500"><h3 id="preparations">Preparations</h3>
<p>First let me introduce you into an widely ignored feature in Excel: Iterative calculations. Excel tries to protect you from “<strong>circular references</strong>” and therefore disables this feature by default.</p>
<p>Our ticker requires a formular that references itself. 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://support.microsoft.com/en-us/office/change-formula-recalculation-iteration-or-precision-in-excel-73fc7dac-91cf-4d36-86e8-67124f6bcce4?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">So you need to enable “iterative calculations” in the settings and set “maximal iterations” to 1</a>


:</p>
<p align="center">
  <a href="iterative-calc.png" target="_blank" style="margin: 0 auto; display: block;">
    <img src="iterative-calc.png" alt="Activate “Iterative Calculations” and limit the iteration count to 1" width="600"/>
  </a>
</p>
<br />
<p>Now let’s get back to your worksheet and start from the scratch:</p>
<h3 id="how-to-draw-a-little-canvas-from-just-one-cell">How to draw a little canvas from just one cell?</h3>
<p>In our case we need <strong>a one-dimensional array</strong> to use as a canvas. But how to create an array over multiple cells, just using one formula?</p>
<p>Well, there are a couple of ways to do that, like <strong>SEQUENCE</strong>, <strong>MAKEARRAY</strong> or <strong>INDIRECT</strong> (and probably more). For our problem we will use <strong>MAP</strong> and <strong>SEQUENCE</strong>. <strong>MAP</strong> allows us to create an array and modify the values within this array. Let’s try a simple example:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="ln">1</span><span class="cl"><span class="o">=</span><span class="n">MAP</span><span class="p">({</span><span class="mi">1</span><span class="p">;</span><span class="mi">2</span><span class="p">;</span><span class="mi">3</span><span class="p">;</span><span class="mi">4</span><span class="p">;</span><span class="mi">5</span><span class="p">;</span><span class="mi">6</span><span class="p">;</span><span class="mi">7</span><span class="p">;</span><span class="mi">8</span><span class="p">;</span><span class="mi">9</span><span class="p">;</span><span class="mi">10</span><span class="p">};</span><span class="n">LAMBDA</span><span class="p">(</span><span class="k">var</span><span class="p">;</span><span class="k">var</span><span class="p">))</span>
</span></span></code></pre></div><p>This will put the numbers from 1 to 10 to your worksheet. You could even add additional rows. For our case the one dimensional array is sufficient. The <strong>LAMBDA</strong> function allows you to modify the values. To make it more handy, let’s add <strong>SEQUENCE</strong> to construct the array and <strong>LET</strong> to define variables:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">=LET(
</span></span><span class="line"><span class="ln">2</span><span class="cl">    length; 10; 
</span></span><span class="line"><span class="ln">3</span><span class="cl">    MAP(
</span></span><span class="line"><span class="ln">4</span><span class="cl">        SEQUENCE(1;length);
</span></span><span class="line"><span class="ln">5</span><span class="cl">        LAMBDA(val;val)
</span></span><span class="line"><span class="ln">6</span><span class="cl">    )
</span></span><span class="line"><span class="ln">7</span><span class="cl">)
</span></span></code></pre></div><p><strong>LET</strong> is a good way to declare variables that you may need in your nested formula.</p>
<h3 id="how-to-simulate-an-animation">How to simulate an “animation”?</h3>
<p>We’re not really going to animate something. We’re just going to create the <strong>impression of animated</strong> <strong>text</strong>. To achieve this we first need to add some momentum. That’s were rely on the self-referencing feature. We just add the value of the first cell to all the other cells:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">=LET(
</span></span><span class="line"><span class="ln">2</span><span class="cl">    inc; INDIRECT(&#34;R&#34; &amp; ROW() &amp; &#34;C&#34; &amp; COL(); 0);
</span></span><span class="line"><span class="ln">3</span><span class="cl">    length; 10; 
</span></span><span class="line"><span class="ln">4</span><span class="cl">    MAP(
</span></span><span class="line"><span class="ln">5</span><span class="cl">        SEQUENCE(1;length);
</span></span><span class="line"><span class="ln">6</span><span class="cl">        LAMBDA(val;val + inc)
</span></span><span class="line"><span class="ln">7</span><span class="cl">    )
</span></span><span class="line"><span class="ln">8</span><span class="cl">)
</span></span></code></pre></div><p>OK, this probably looks weird, so I’m gonna explain it. In the second row we are assigning a calculation to a variable called <strong>inc</strong>, this will be our “incrementor”. Instead of a fixed reference like <strong>A1</strong> we use an indirect reference. This makes it easier to use the final result everywhere we want. <strong>INDIRECT</strong> takes the <strong>value</strong> from the current row and current column: This is the 1 from the sequence. The second parameter of <strong>INDIRECT</strong> is a 0. This enables the R/C reference: referencing a cell by it’s row and column index instead of the “common” A1 notation.</p>
<p>Now as we have this value from the first cell, we just add it to every cell of this range — which results in a row of incrementing numbers. Press and hold F9 and see what happens:</p>
<p align="center">
  <a href="https://nickyreinert.de/en/2025/2025-10-26-how-to-create-excel-ticker/animation-1.gif" target="_blank" style="margin: 0 auto; display: block;">
    <img src="https://nickyreinert.de/en/2025/2025-10-26-how-to-create-excel-ticker/animation-1.gif" alt="Animated text ticker in Excel (no VBA)" width="400"/>
  </a>
</p>
<br />
<blockquote>
<p>From now on, when I say “<strong>let it run</strong>”, you need to press and hold <strong>F9</strong> and see the result.</p>
</blockquote>
<p>Now let’s add the text. We want to display “<strong>hello, world</strong>” and therefore provide this as a variable named <strong>ticker</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln"> 1</span><span class="cl">=LET(
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    ticker; &#34;hello world&#34;;
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    length; LEN(ticker);
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    inc; IF(
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        INDIRECT(&#34;Z&#34; &amp; ROW() &amp; &#34;S&#34; &amp; COL(); 0) &gt; length;
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        1;
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        INDIRECT(&#34;Z&#34; &amp; ROW() &amp; &#34;S&#34; &amp; COL(); 0) 
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    );
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    MAP(
</span></span><span class="line"><span class="ln">10</span><span class="cl">        SEQUENCE(1; length);
</span></span><span class="line"><span class="ln">11</span><span class="cl">        LAMBDA(val;
</span></span><span class="line"><span class="ln">12</span><span class="cl">        IF(val = 1; 
</span></span><span class="line"><span class="ln">13</span><span class="cl">            val +  inc;
</span></span><span class="line"><span class="ln">14</span><span class="cl">            MID(&#34; &#34; &amp; ticker; val + inc; 1)
</span></span><span class="line"><span class="ln">15</span><span class="cl">        )
</span></span><span class="line"><span class="ln">16</span><span class="cl">    )
</span></span><span class="line"><span class="ln">17</span><span class="cl">)
</span></span></code></pre></div><p>The ticker-declaration should be clear as well as the length-dependency. The length was set to 10 before, now it’s the length of the ticker-text.</p>
<p>What looks a bit complicated is the improved declaration of the incrementing variable (<strong>inc</strong>): Actually we just check if the variable is greater than the amount of available letters. If so, reset it to 1 to start over.</p>
<p>Before we look at the LAMBDA let’s be clear about the “canvas”: We need one cell that contains an incrementing value — the first cell. All the other cells are meant for “rendering”.</p>
<p>The <strong>first IF-condition</strong> checks if the value of the particular cell equals 1. This would be our counting cell and that’s why we increase only this cell’s value.</p>
<p>The <strong>MID-formula</strong> takes care of the rendering-part: The <strong>second cell</strong> in our one-dimensional array should start with the H, the <strong>first letter</strong> of our ticker. The <strong>third cell</strong> displays the <strong>second letter</strong> E and so on.</p>
<p>You see the discrepancy? The second cell holds the value 2, but we need the first letter. We are one counter ahead, that’s why we add the empty char before the ticker text.</p>
<p>What happens if your run it?</p>
<p align="center">
  <a href="https://nickyreinert.de/en/2025/2025-10-26-how-to-create-excel-ticker/animation-2.gif" target="_blank" style="margin: 0 auto; display: block;">
    <img src="https://nickyreinert.de/en/2025/2025-10-26-how-to-create-excel-ticker/animation-2.gif" alt="Animated text ticker in Excel (no VBA)" width="400"/>
  </a>
</p>
<br />
<p>Well, we have a pretty good loop. It’s not that smooth, but we get to that in a second.</p>
<h3 id="finish-the-animation">Finish the animation</h3>
<p>The main work is done, but we still need some improvements. First let’s add a second variable to define the canvas width. It makes more sense to use a fixed value here. So we can render even long text on the small canvas.</p>
<p>We just call it “width”. And we also want to start the ticker on the most-right position of the canvas. That’s why we take the width of the canvas we just declared and use it as a substractor.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln"> 1</span><span class="cl">=LET(
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    ticker; &#34;hello world&#34;; 
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    width; 10;
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    length; LEN(ticker);
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    inc; IF(
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        INDIRECT(&#34;Z&#34; &amp; ROW() &amp; &#34;S&#34; &amp; COL(); 0)
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">            &gt; (length + width);
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        1;
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        INDIRECT(&#34;Z&#34; &amp; ROW() &amp; &#34;S&#34; &amp; COL(); 0)
</span></span><span class="line"><span class="ln">10</span><span class="cl">    );
</span></span><span class="line"><span class="ln">11</span><span class="cl">    MAP(
</span></span><span class="line"><span class="ln">12</span><span class="cl">        SEQUENCE(1; width);
</span></span><span class="line"><span class="ln">13</span><span class="cl">        LAMBDA(val;
</span></span><span class="line"><span class="ln">14</span><span class="cl">        IF(val = 1; 
</span></span><span class="line"><span class="ln">15</span><span class="cl">            val +  inc;
</span></span><span class="line"><span class="ln">16</span><span class="cl">            IF((val + inc) &gt; (width - 1); 
</span></span><span class="line"><span class="ln">17</span><span class="cl">                MID(&#34; &#34; &amp; ticker; val + inc - (width - 1); 1); 
</span></span><span class="line"><span class="ln">18</span><span class="cl">                &#34;&#34;)
</span></span><span class="line"><span class="ln">19</span><span class="cl">        )
</span></span><span class="line"><span class="ln">20</span><span class="cl">    )
</span></span><span class="line"><span class="ln">21</span><span class="cl">)
</span></span></code></pre></div><p>Please note how the width-variable replaces length as an argument for SEQUENCE. We also need to adapt the inc-part a little. We need to reset the incrementation a little later, because the animation should start at the most right column and therefore needs more steps to finish.</p>
<p>The <strong>second</strong> and nested <strong>IF-condition</strong> checks if the current incremented value of the particular cell is greater than the width of the canvas. That ensures that we address a valid value from the ticker text when using MID.</p>
<p>And that’s it. I adjusted the <strong>cell widths</strong> a little, <strong>hid grid lines</strong> and made the <strong>first cell invisible</strong> to make it look a little better. This is your ticker:</p>
<p align="center">
  <a href="https://nickyreinert.de/en/2025/2025-10-26-how-to-create-excel-ticker/animation-3.gif" target="_blank" style="margin: 0 auto; display: block;">
    <img src="https://nickyreinert.de/en/2025/2025-10-26-how-to-create-excel-ticker/animation-3.gif" alt="Animated text ticker in Excel (no VBA)" width="400"/>
  </a>
</p>
<br />
<p>You can just copy and paste the formula to every sheet you want or you can create a LAMBDA function to get a handy formula. Do not forget to enable “iterative calculations” and change the ticker text.</p>
<p>Have fun.</p>
<h3 id="nota-bene">Nota bene</h3>
<p>This does not work in Google Sheets, out of the box, because Google Sheets do not support the LET formula. You still could just fixed values or named ranges. But this makes it less fun.</p>
]]></content:encoded><atom:updated>2025-10-27T07:59:13+01:00</atom:updated><category>blog</category><category>howto</category><media:content url="https://nickyreinert.de/images/posts/excel-ticker.png" type="image/jpeg"><media:title>Nicky Reinert</media:title></media:content><dc:subject>Lesezeit: 6 Minuten</dc:subject><dc:type>tutorial</dc:type></item><item><title>AI assisted coding - the plAIbook</title><link>https://nickyreinert.de/en/2025/2025-08-29-ai-assisted-coding/</link><pubDate>Sat, 30 Aug 2025 12:34:56 +0200</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/en/2025/2025-08-29-ai-assisted-coding/</guid><description>Famous first words I work extensively with AI, and as it is a new tool that introduces an entirely different way of working, it sometimes takes time to adapt to these new circumstances. In this context, I am still exploring the best approach to integrate AI or agentic chatbots into my development …</description><content:encoded><![CDATA[<h2 id="famous-first-words">Famous first words</h2>
<p>I work extensively with AI, and as it is a new tool that introduces an entirely different way of working, it sometimes takes time to adapt to these new circumstances. In this context, I am still exploring the best approach to integrate AI or agentic chatbots into my development workflow. Recently, I reached another milestone that I’d like to share.</p>
<p><strong>Preliminary notes:</strong>
You may think that &ldquo;the <em>plAIbook</em> helps you to vibe code everything. You are wrong. You still need a profound understanding of architecture and design patterns. The more complex a project gets, the harder it is to maintain with AI only. This is not a <strong>fire-and-forget</strong> solution.</p>
<p>Also, as this is not the only but main focus of my developing work: This is slightly focused on creating little PoC`s using Python and/or Flask. But I believe it&rsquo;s quite easy to adapt the process for other languages, frameworks or platforms.</p>
<p>What are the landmarks here?</p>
<ul>
<li>a <strong>meta prompt</strong> that returns your necessary prompts for your actual coding Agent</li>
<li>a fully fledged <strong>pre-prompt instruction</strong></li>
<li>an environment that makes it easy to refactor or dive into the code on your own</li>
<li>an <strong>MCP server</strong> (yeah, buzzwords are required here) that helps the Agent to supervise its own work</li>
</ul>
<p>You will find a more technical summary, all prompts and scripts there: 





  
  

  
  
    
    
    
  

  
  
  

  
    
      
    
  

  
  <a href="https://gist.github.com/nickyreinert/bbce96ea043bc73cb011066e8a8df673?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous#file-03-global-instructions-md" target="_blank" rel="noopener noreferrer">click me</a>


</p>
<p><em>This said, with no further ado, let&rsquo;s dive into the details.</em></p>
<h2 id="the-process">The Process</h2>
<p>The developing process is separated into <strong>three phases</strong>:</p>
<h3 id="phase-1---prepping">Phase 1 - Prepping</h3>
<p>First we need to prep our environment, means: Setup <strong>Visual Studio Code</strong>. I assume that you have a <strong>Copilot</strong> subscription. Though, the whole process will work without it and it should also work with one of the derivatives, like <strong>Windsurf</strong> or <strong>Cursor</strong>.</p>
<h4 id="1-add-pre-prompt-instructions">1. Add pre-prompt instructions</h4>
<p>Assumining you have created <strong>an empty folder</strong>, declared it as a <strong>workspace</strong> and <strong>inited a git repo</strong> there, this is how you proceed:</p>
<p>Take the <strong>pre-prompt instruction</strong> (





  
  

  
  
    
    
    
  

  
  
  

  
    
      
    
  

  
  <a href="https://gist.github.com/nickyreinert/bbce96ea043bc73cb011066e8a8df673?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous#file-03-global-instructions-md" target="_blank" rel="noopener noreferrer">from here</a>


) and put them to <code>.github/instructions/global.instructions.md</code>.</p>
<blockquote>
<p><strong>Hint #1</strong>: If you click the cog wheel at the top of Copilot&rsquo;s chat window and select <strong>instructions</strong> you can add even more specific instructions. They will be placed in the very same folder.</p>
</blockquote>
<blockquote>
<p><strong>Hint #2</strong>: As these are global instructions, you may also want to place them to <code>copilot-instructions.md</code> - which is the native VSCode way - I prefer the first way, as this allows you to <strong>maintain multiple instruction</strong> sets.</p>
</blockquote>
<p>As you can see the pre prompt uses a lot of abbreviations and doesn&rsquo;t seem very natural. That&rsquo;s because it&rsquo;s sufficient enough for the machine to understand it&rsquo;s task. However, let me guide you through some benefits and features I built in:</p>
<ul>
<li><strong>functions</strong> are not bigger than <strong>20 lines</strong>, <strong>files</strong> don&rsquo;t exceed <strong>200 lines</strong></li>
<li>I am pursuing a <strong>modular approach</strong> with features separated into files and <strong>subfolders</strong></li>
<li>code must be human readable with appropriate <strong>inline documentation</strong>, files are structured and grouped into <strong>functional sections</strong></li>
<li>common <strong>security</strong>, <strong>logging</strong> and <strong>error handling</strong> measures are implemented, like of course try/except blocks, rate limiting, sanitizing inputs and so on</li>
<li>the agent creates <strong>commit messages</strong> on its own after every minor change</li>
<li>extensive <strong>tests</strong> for functions and API endpoints</li>
<li>lean environment with <strong>minimal dependencies</strong></li>
<li>extensive but not bloated <strong>documentation</strong>, with a <code>PROCESS_MAP.md</code> showing the overall architecture and flow of the application or an <code>UNFINISHED.md</code> file outlining current work, which is quite helpful if the agent stopped working or you want to get back to the current state at a later point</li>
<li><strong>Docker</strong> implementation which easily enables you to take everything and run it anywhere else</li>
<li>utilizing <strong>MCP</strong> to maintain supervising tasks, cleaning up, testing and so on</li>
<li>telling the agent to be <strong>sceptical</strong> and ask user for additional details</li>
<li>trying to prevent &ldquo;<strong>bug looping</strong>&rdquo; where the agent implements the same fix multiple times without success (this is another reason why <code>UNFINISHED.md</code> exists)</li>
<li>and last but not least but most important: No useless &ldquo;<strong>Sorry</strong>&rdquo; &amp; &ldquo;<strong>Apologies</strong>&rdquo; or offering &ldquo;This is the final solution&rdquo; - you know how annoying this is, when you read it the twelfth time and the app is still not running ;-)</li>
</ul>
<blockquote>
<p><strong>Note</strong>: As already said: This has a strong focus on <em>Python</em> and <strong>Web app development</strong>, but most parts are also easily applicable if you want to, like, create a pure <strong>JavaScript</strong> app or even <strong>mobile apps</strong>.</p>
</blockquote>
<h4 id="2-enable-mcp">2. Enable MCP</h4>
<p>The <code>model control protocol</code> (<strong>MCP</strong>) is a fancy name for an interface that the Agent can talk to. On one hand it may help you to control other programs, like imagine connecting the Agent via MCP to <strong>Adobe Premiere</strong> and telling it to cut your home videos. We are using it to allow the Agent to supervise and revise its own output!</p>
<p>First create a new file <code>.vscode/mcp.json</code> with this content - I will explain it in a second:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">  <span class="nt">&#34;servers&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="nt">&#34;ai_coding_agent&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">      <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;stdio&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">      <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;python3&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">      <span class="nt">&#34;args&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;./.mcp_tools/mcp_server.py&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Now create a folder named <code>.mcp_tools</code> and put the following files into it:</p>
<ul>
<li><code>mcp_server.py</code> - the server itself</li>
<li><code>load_context_files.py</code> - allows the agent to refresh its pre-prompt brain</li>
<li><code>run_tests.py</code> - allows the agent to run tests against the app</li>
<li><code>update_process_map.py</code> - allows the agent to update the process map</li>
</ul>
<p><em>(You may restart your Visual Studio Code)</em></p>
<p>Now open the command palette to find our MCP server (<code>MCP: List Server</code>) and start it (or use the very handy inline-commmands VSCode is offering you). The output window should return something like that:</p>
<p align="center">
  <a href="https://nickyreinert.de/en/2025/2025-08-29-ai-assisted-coding/mcp-terminal-output.png" target="_blank" style="margin: 0 auto; display: block;">
    <img src="https://nickyreinert.de/en/2025/2025-08-29-ai-assisted-coding/mcp-terminal-output.png" alt="alt text" width="500"/>
  </a>
</p>
<br />
<br />
<blockquote>
<p>Hint: In some environments the command may need to be adjusted to <code>python</code> instead of <code>python3</code>, depending on your Python installation.</p>
</blockquote>
<p>Perfect, the server is running. You can conform that by clicking the little cog wheel at the top of your chat window, which opens the extension bar (usually on the left) - and as you can see, <strong>VSCode</strong> already offers a lot of different <strong>MCP</strong> solutions. At the very bottom is our own:</p>
<p align="center">
  <a href="https://nickyreinert.de/en/2025/2025-08-29-ai-assisted-coding/mcp-server-list.png" target="_blank" style="margin: 0 auto; display: block;">
    <img src="https://nickyreinert.de/en/2025/2025-08-29-ai-assisted-coding/mcp-server-list.png" alt="alt text" width="500"/>
  </a>
</p>
<p>Test your agent directly using this <strong>tool reference</strong>, which acts as a <strong>magic word</strong>, to reload pre prompt:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">#load_context_files</span>
</span></span></code></pre></div><p>which should be confirmed properly by the bot (you can use natural language too, but I prefer calling <strong>tools</strong> directly).</p>
<p>I suggest you checkout the <code>mcp_server.py</code> which is not too complex. It defines tools the MCP server offers to your bot, right now there are three: <strong>load_context_files</strong>, <strong>run_tests</strong> and <strong>update_process_map</strong></p>
<blockquote>
<p><strong>Hint</strong>: You may have noticed &ldquo;<strong>Toolset</strong>&rdquo; - You can combine sets of different MCP tools into one toolset to perform multiple actions. Also, if you look for more <strong>sophisticated interactions</strong>, of course you can feed information into your MCP server tools. Like not only telling it to run tests but actually provide data to run the tests with and submitting them as arguments.</p>
</blockquote>
<h3 id="phase-3---planning">Phase 3 - Planning</h3>
<p>In this phase we will create <em>3 to 5 sequential</em>, self-contained <strong>prompts</strong>. I mean, not we, but the AI, apparently. For this I created a &ldquo;meta-prompt&rdquo;. Throw it against your most favorite AI agent and wait for the result. This is a strategy that I suggest to everyone. Don&rsquo;t try to wrap your head around writing the perfect prompt. Ask someone who should know it - better than you: the LLM!</p>
<blockquote>
<p><strong>Hint</strong>: There&rsquo;s one cruccial information that you should take with you at this point: Don&rsquo;t was time working on prompts on your own, manually. Utilize the power of AI to get the perfect prompt!</p>
</blockquote>
<p>So, we can keep this short: use the <code>meta prompt</code> to get a small handful of prompts for your actual developing progress. Those prompts are reflecting different phases of your developing process.</p>
<p>You&rsquo;ll find my <code>meta prompt</code> 





  
  

  
  
    
    
    
  

  
  
  

  
    
      
    
  

  
  <a href="https://gist.github.com/nickyreinert/bbce96ea043bc73cb011066e8a8df673?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous#file-04-pre-prompt-md" target="_blank" rel="noopener noreferrer">here</a>


. Just fill out the four required fields:</p>
<ul>
<li>[NAME] - of your application</li>
<li>[GOAL] - what&rsquo;s this app about?</li>
<li>[FEATURES] - what does it offer?</li>
<li>[USERS] - who are the target users?</li>
</ul>
<p>Keep goal and features tight. Don&rsquo;t fall into the feature creep rabbit hole. Your goal should be a good working first draft of your app. Focus on that and then add features one at a time.</p>
<p>Now run your <code>meta prompt</code> and collect your actual prompts for your Copilot agent.</p>
<h3 id="phase-4---execution">Phase 4 - Execution</h3>
<p>The rest should be a walk in the park. (Until it isn&rsquo;t (˵ ͡° ͜ʖ ͡°˵)). Apparently you start pasting your prompts to the agent and watch it working the task.</p>
<p>You may leave it working like that, but I strongly suggest to keep watching. When it&rsquo;s done, don&rsquo;t just paste the next prompt. Try to understand what has been built here, not in detail, but for later troubleshooting and feature expanding, it&rsquo;s quite helpful to understand the architecture. You can always ask the machine, but it will probably not know the great picture.</p>
<p>As mentioned above, the pre prompt covers a lot not only technical issues but also tactical directives that should improve how the agent works and talks to you.</p>
]]></content:encoded><atom:updated>2025-08-31T11:24:18+02:00</atom:updated><category>blog</category><category>anleitungen</category><category>development</category><dc:subject>Lesezeit: 8 Minuten</dc:subject><dc:type>tutorial</dc:type></item><item><title>How to simulate proof of work in SQL and BigQuery</title><link>https://nickyreinert.de/en/2025/2025-08-15-proof-of-work-in-bigquery-and-sql/</link><pubDate>Fri, 15 Aug 2025 12:34:56 +0000</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/en/2025/2025-08-15-proof-of-work-in-bigquery-and-sql/</guid><description>(or: How does blockchain mining work?)
A miner has to solve a mathematical problem to validate a block for the Blockhain. The so called Proof of Work requires a lot of computing power aka energy and therefore is quite expensive. Googles BigQuery has a price model, that charges you for the …</description><content:encoded><![CDATA[<p><em>(or: How does blockchain mining work?)</em></p>
<p>A miner has to solve a mathematical problem to validate a block for the Blockhain. The so called <strong>Proof of Work</strong> requires a lot of computing power aka energy and therefore is quite expensive. <strong>Googles BigQuery</strong> has a price model, that charges you for the transferred data and not the computing power. Meaning: You can utilize a lot of computing power for free. If you are able to reproduce the mining algorithm in SQL.</p>
<p>In this article I first will explain how the mining process works. This is the main concept for a couple of <strong>Blockchain</strong> technologies. My explanation is based on Bitcoin’s blockchain. If you are already familiar with that, you may skip to <strong>part 2</strong>, where I present an SQL query that does the job.</p>
<h1 id="part-1-the-mathematical-problem-aka-mining-a-block">Part 1: The mathematical problem aka: Mining a block</h1>
<h2 id="whats-a-hash-again">What’s a hash again?</h2>
<p>A hash is a kind of unique piece of information calculated from any given input (




  <a href="https://nickyreinert.de/wie-funktioniert-der-sha256-algorithmus-im-detail-teil-1-2-7a0023cf562a">I wrote about that earlier</a>

). There are a lot of hashing algorithms. The blockchain we are talking about uses <strong>SHA256</strong>. It’s important to understand, that a hash is basically just a really large number with always the same length: <strong>256 bit</strong>.</p>
<p><strong>If you change the input, even just a little, the hash changes</strong>. This makes the hash a pretty good indicator to check if the input data has been tampered: If you know the original hash and you re-calculate the hash for a given information, and both hashes are different, the initial information has been changed.</p>
<h2 id="why-does-the-blockchain-rely-on-hashes">Why does the blockchain rely on hashes?</h2>
<p>Every block in a blockchain contains two kind of information: A couple of <strong>transactions details</strong> and a <strong>block header</strong>. Among other details the header contains <strong>two</strong> important <strong>hashes</strong>: One hash for all the transactions of the current block and one hash for the header of the previous block.</p>
<p>If you would tamper the transaction data of lets say block <strong>#1337</strong>, the hash of this block’s header would change. But as the next block’s header contains a hash for the original block #1337, you could simply compare both hashes and come to the conclusion, that the data in block #1337 has changed.</p>
<img src="https://nickyreinert.de/en/2025/2025-08-15-proof-of-work-in-bigquery-and-sql/assets/image7.png" alt="How hashes ensure data integrity in a blockchain"><p>This sounds easy, because calculating one hash only takes milliseconds, even on slow computers. That’s why the blockchain protocol defines a very special requirement that every header’s hash has to meet: The hash of a block header has to be lower than a given number, the target.</p>
<blockquote>
<p>S ide note: Besides that there are some other rules that make the calculation even more difficult: For example you need to double-hash the information and you need to calculate the Merkle root of all transactions of the current block.</p>
</blockquote>
<p>You can meet this requirement by simply adding an incrementing number to the header information, re-calculate the hash, and see if the new hash is below the target.</p>
<p>The whole process is so complicated, that even powerful computers will take around 10 minutes to find a solution. That’s by the way a reason why Bitcoin is not the payment method: If you’d like to buy a <strong>coffee</strong>, you may have to wait around 10 minutes until your transaction is confirmed.</p>
<h2 id="the-block-header">The block header</h2>
<p>Let’s see what kind of information a block header contains. This is the header of 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://learnmeabitcoin.com/explorer/block/00000000000000000006b9bf976b004581c19d800948d2eab77c8f9e07b9f088?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">block number 727938</a>


:</p>
<img src="https://nickyreinert.de/en/2025/2025-08-15-proof-of-work-in-bigquery-and-sql/assets/image4.png" alt=""><p>You will find six important fields here:</p>
<ul>
<li>a <strong>version</strong> field,</li>
<li>the hash for the <strong>previous block’s header,</strong></li>
<li>the <strong>Merkle root,</strong></li>
<li>a <strong>timestamp,</strong></li>
<li>a <strong>bits</strong> field, that defines the target and</li>
<li>a <strong>nonce</strong></li>
</ul>
<p>Let me describe those fields in detail:</p>
<p><strong>The nonce field</strong></p>
<p>If you take all header fields <strong>except the nonce field</strong>, you get the block message:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">04c04d34702a058fee2c141a685dfc3d18c4f0efc0464d625057060000000000000000005e578fe5fe001059710edbf9f78c1bd15b110faf574723cafde35667633c18084ac23462c0400a17
</span></span></code></pre></div><p>The SHA256 hash for this looks like that:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">5c96f247d3c6a48b244be859d888dd1ee2a80dd6378cc7e10f31b9d14b78660b
</span></span></code></pre></div><p>As mentioned above, the protocol requires you to add a specific kind of information to the block message, that leads to a hash below a given value. For starters lets make that easy: Find a number that leads to a hash starting with a zero. What if we add a 1 to the block message? (Remember: This is a hexadecimal number, so I need to add two digits):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">04c04d34702a058fee2c141a685dfc3d18c4f0efc0464d625057060000000000000000005e578fe5fe001059710edbf9f78c1bd15b110faf574723cafde35667633c18084ac23462c0400a17**01**
</span></span></code></pre></div><p>That’s the resulting hash:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">855ccd41ef49272ab633eff0cf5abed6d543ed5183eaf9f8a32e12a57b7419a9
</span></span></code></pre></div><p>No leading zeros? You may get a picture of how difficult it is. The actual target value for this block was a hash with <strong>19 leading zeros</strong>. The miner need to (find and) add the number 1.396.904.612 (in hexadecimal 0x534312a4) to the block message to find a fitting hash. And that number is called **nonce **— short for <strong>number used once</strong>. That’s the resulting hash:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">00000000000000000006b9bf976b004581c19d800948d2eab77c8f9e07b9f088
</span></span></code></pre></div><p>While calculation one hash seems pretty simple, imagine how long it would take for 1 Billion calculations, or even more? Even more? Yes, you heard right: The nonce is a four byte number meaning you have <strong>2³²</strong> possible numbers (<strong>4,294,967,296</strong>) to check (the so called <strong>search space</strong>). And sometimes that’s not enough.</p>
<h2 id="the-previous-blocks-header-hash">The previous block’s header hash</h2>
<p>This is just the hash of the header of the previous block. Not much to say here: You cannot change it.</p>
<h2 id="the-bit-field">The bit field</h2>
<p>This field defines the above mentioned target. It’s a 4 byte number, too:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">0x170a40c0
</span></span></code></pre></div><p>A hash is a number with 32 bytes. And that’s strange: How can you limit a 32 byte number using only 4 bytes? Here it is: The first byte of the bit field defines an <strong>exponent</strong>: 0x17. The remaining 3 bytes define the <strong>mantissa</strong>: 0x0a40c0. The base is 256 and you need to substract 3 (length of the mantissa) from the exponent. So this is the formula to calculate the target:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">target = 0x0a40c0 × 256⁽⁰ˣ¹⁷ ⁻ ³⁾⁾
</span></span></code></pre></div><img src="https://nickyreinert.de/en/2025/2025-08-15-proof-of-work-in-bigquery-and-sql/assets/image1.png" alt="The bit field"><p>The target will be adjusted every <strong>2,016 blocks</strong>, which is roughly two weeks (6 blocks per hour × 24 hours × 7 days x 2 = 2,016). Why? The mathematical problem needs to be difficult enough to keep the chain safe but it must not be to difficult, because you do not want to wait more than 10 minutes for a transaction to be confirmed. As computing power changes or miners joining and leaving the network, the target and therefore the difficulty as to be adjusted.</p>
<h2 id="the-merkle-root">The Merkle root</h2>
<p>The Merkle root is a hash of all transactions of **the current block **(and a little more, I’m not going to much into detail here). Basically it’s pretty easy: You take two transactions and hash them to get Hash #1. Then you take two more transactions, hash them and get Hash #2. Now you take Hash #1 and Hash #2, hash it and get: Hash #3. You’re building an upside tree, where the transactions are on the bottom and the branches are dozens of hashes eventually leading to the root hash.</p>
<img src="https://nickyreinert.de/en/2025/2025-08-15-proof-of-work-in-bigquery-and-sql/assets/image3.png" alt="a simple Merkle root"><p>If the search space is not enough, the miner can change the order of the transactions to create a different Merkle root. This would change the block message and therefore lead to a different hash.</p>
<h2 id="the-timestamp">The timestamp</h2>
<p>The timestamp, well, shows at what time the block was validated. In fact there is a lot more to tell about the timestamp, but not in a mathematical way.</p>
<p>The protocol uses the timestamp to see, how fast blocks are mined. As already explained, the network uses this value to adjust the difficulty.</p>
<img src="https://nickyreinert.de/en/2025/2025-08-15-proof-of-work-in-bigquery-and-sql/assets/image6.png" alt="The date field"><p>A miner can use the timestamp field to increase the search space. A valid timestamp has to be <strong>greater</strong> than the median timestamp of the last 11 blocks and lower than the network-time plus <strong>2 hours</strong>. Eventually this gives you a <strong>3 hour</strong> window where you can adjust the timestamp to gain extra search space. 3 hours are 10,800 seconds (which is roughly 2¹³).</p>
<h2 id="the-version-field">The version field</h2>
<p>Last but definitely not least: The version field contains a lot more than you would expect. The version field is 4 bytes long and it</p>
<ul>
<li>contains the current protocol version,</li>
<li>signals readiness for soft fork proposals and</li>
<li>offers additional search space.</li>
</ul>
<p>That’s a lot of information and it’s not that easy to pull it out of those 4 bytes. The current protocol version requires the **first 3 bits **(!) to be at least <strong>0b001</strong> (2 in decimal):</p>
<img src="https://nickyreinert.de/en/2025/2025-08-15-proof-of-work-in-bigquery-and-sql/assets/image8.png" alt=""><p>Besides that, the last 13 bits are used for soft forks signaling. This simply means, if a miner is ready for a particular soft fork, he flips on of those bits to 1. Imagine there’s a soft fork BIPx that requires the 1 bit to be flipped, that’s how the miner signals readiness for BIPx:</p>
<img src="https://nickyreinert.de/en/2025/2025-08-15-proof-of-work-in-bigquery-and-sql/assets/image10.png" alt=""><p>This would lead to a version field like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">0x20000001
</span></span></code></pre></div><p>As you may have noticed, this leaves 16 bits open (32 bits at all, mins 3 bits for the version and 13 bits for soft forks = 16 bits left). This gives the miner 2¹⁶ more options to find a good hash! That technique is called overt ASIC boost. Lets take the version field from 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://blockstream.info/block/00000000000000000006b9bf976b004581c19d800948d2eab77c8f9e07b9f088?expand=&amp;utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">block #727938</a>


 for example: 0x344dc004</p>
<p>This is the bit representation of the hexadecimal value:</p>
<img src="https://nickyreinert.de/en/2025/2025-08-15-proof-of-work-in-bigquery-and-sql/assets/image9.png" alt=""><p>So apparently the miner took 0b1010001001101110 (0d41582) as an additional resource to finally find the fitting nonce 0xa4124353.</p>
<img src="https://nickyreinert.de/en/2025/2025-08-15-proof-of-work-in-bigquery-and-sql/assets/image5.png" alt="The version field"><h2 id="block-header-summary">Block header summary</h2>
<p>So after all you have <strong>six fields</strong> in your header, where only <strong>two fields</strong> are fixed and <strong>cannot</strong> be changed in order to find a hash below the given target: The <strong>bit</strong> field, that describes the <strong>target</strong> itself and the hash for the **previous header — **the vital element of the Blockchain concept…</p>
<p>Besides that you may change the <strong>nonce</strong>, the one field actually designed to change. The <strong>Merkle</strong> <strong>root</strong>, by re-arranging or considering different transactions. The <strong>version</strong> field and the <strong>timestamp</strong> by exploiting the 3 hour time window.</p>
<p>To give you an idea how complex it is to utilize at least the <strong>nonce</strong> and the <strong>version</strong> field, I created a Python script that does that for you:</p>
<h1 id="part-2-using-sql-and-bigquery-to-validate-a-block">Part 2: Using SQL and BigQuery to validate a block</h1>
<p>Now as you are an expert in mining, let’s try to find a way to mine for free. I will implement a mining algorithm in SQL that iterates through the nonce and the additional version bits to find a hash below a required target. I’m not re-arranging the Merkle root and I will take the timestamp as a given value.</p>
<p>To make the whole process understandable, I will start from within the inner loop and calculate the hash from a given header, that already contains all the correct “answers”. I’m using the header for 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://blockstream.info/block/000000000000000000078c7ad45ab182796bbb419c361aa3eaffaa8ddd00d4f0?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">block #727155</a>


 with all the information already in the right format. After we understand the hashing process in BigQuery, we will build the header field by field and finally add the two loops.</p>
<p>And here they are — the variables for my <strong>six header fields.</strong> Remember that the version field contains the actual version as well as the soft fork signals.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl"># header for block 727155DECLARE hash_prev_block STRING DEFAULT &#34;00000000000000000004136135b2e0cd367b56ea6c0dd5b8f79964a4cd7d2718&#34;;DECLARE merkle_root STRING DEFAULT &#34;0d14fac91555d6337b10b2f20de231858fb5225f2ff685cd9b487c235d6e8307&#34;;DECLARE header_datetime STRING DEFAULT &#34;622defb9&#34;;DECLARE bits STRING DEFAULT &#34;170a3773&#34;;DECLARE nonce STRING DEFAULT &#34;c8f05860&#34;;DECLARE version STRING DEFAULT &#34;20002000&#34;;
</span></span></code></pre></div><p>We simply need to concatenate everything into one string before we hash it. Simply? Wait…there’s one pitfall called Little Endian. Bitcoin wants you to work with values in Little Endian. This is the way how you can write a number. In one sentence: Little Endian starts with the lowest value on the left. Basically you just switch the order of the single digits in your number. Instead of 23 you write 32. The value though remains the same. You achieve that using the REVERSE() function in SQL.</p>
<p>But beware — there’s more: What would you expect if you <em>reverse</em> the following hexadecimal value to Little Endian?</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">ABCDEF
</span></span></code></pre></div><p>Is it FEDCBA? No. We are working with bytes. One byte is 8 bits which is enough space for 255 values or in hexadecimal: FF. Two digits. That means we have to see all them values in pairs of two and the correct Little Endian representation is:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">EFCDAB
</span></span></code></pre></div><p>To get this result you need to read the incoming value in bytes format and afterwards transfer it back to hexadecimal string. So this is how we create the actual header:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">DECLARE header STRING;SET header =    TO_HEX(REVERSE(FROM_HEX(version))) ||    TO_HEX(REVERSE(FROM_HEX(hash_prev_block))) ||    TO_HEX(REVERSE(FROM_HEX(merkle_root))) ||    TO_HEX(REVERSE(FROM_HEX(header_datetime))) ||    TO_HEX(REVERSE(FROM_HEX(bits))) ||    TO_HEX(REVERSE(FROM_HEX(nonce)));
</span></span></code></pre></div><p>And that’s the header containing all relevant fields in Little Endian:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">0020002018277dcda46499f7b8d50d6cea567b36cde0b23561130400000000000000000007836e5d237c489bcd85f62f5f22b58f8531e20df2b2107b33d65515c9fa140db9ef2d6273370a176058f0c8
</span></span></code></pre></div><h2 id="the-hashing">The Hashing</h2>
<p>BigQuerys build-it SHA256 expects a string or a byte but the header is made of hexadecimal values. So first we need to prepare the header, meaning: convert the hexadecimal string to the bytes format:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">DECLARE b_header BYTES;SET b_header = FROM_HEX(header);
</span></span></code></pre></div><p>Now we can apply SHA256. And as mentioned above, we need to do it two times. The result of SHA256 comes in bytes, o we have to convert it back to a hexadecimal string:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">SELECT TO_HEX(SHA256(SHA256(b_header)))
</span></span></code></pre></div><p>That finally leads to this result:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">f0d400dd8daaffeaa31a369c41bb6b7982b15ad47a8c07000000000000000000
</span></span></code></pre></div><p>And now you hopefully remember that we were running everything in Little Endian. To finalize the process we need to convert it back to Big Endian and finally get to this hexadecimal number:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">SELECT TO_HEX(REVERSE(SHA256(SHA256(b_header))));
</span></span></code></pre></div><p>And that’s the result:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">000000000000000000078c7ad45ab182796bbb419c361aa3eaffaa8ddd00d4f0
</span></span></code></pre></div><p>that you now may compare to 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://blockstream.info/block/000000000000000000078c7ad45ab182796bbb419c361aa3eaffaa8ddd00d4f0?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">the actual header hash the next block refers to</a>


:</p>
<img src="https://nickyreinert.de/en/2025/2025-08-15-proof-of-work-in-bigquery-and-sql/assets/image2.png" alt=""><h2 id="prepare-the-inner-loop">Prepare the inner loop</h2>
<p>We’re going to wrap everything into a while-loop, add a break-out condition for safety reasons and start with a comparison of the just found hash and a target. Because that’s what it’s all about: Find a “number” (hash) that is lower than another “number” (target).</p>
<p>The target value is hidden in the bits field. And as already mentioned, we need to do the math to get the numeric target. The mantissa comes from the last 3 bytes, the exponent is the first byte. And the target comes from this equation:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">target = mantissa * 256 ^ exponent
</span></span></code></pre></div><p>And this, well, leads to a pretty big number. Actually to big to fit into an INT64. We have to store it into a FLOAT64 and to make it comparable against the previously calculated hash, we need to cast the hash to FLOAT64, too.</p>
<p>Here’s the loop containing the condition, that returns the nonce — that we are looking for and a little break-out-statement, because we’re just testing how it’s working:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl"># skipping previous declarations from above# [...]DECLARE mantissa INT64DECLARE exponent INT64DECLARE target FLOAT64;DECLARE max_iterations INT64 DEFAULT 100;DECLARE iteration INT64 DEFAULT 0;SET exponent = CAST(CONCAT(&#34;0x&#34;, SUBSTRING(bits, 0, 2)) AS INT64)SET mantissa = CAST(CONCAT(&#34;0x&#34;, SUBSTRING(bits, 2, 4)) AS INT64);SET target = mantissa * POWER(256, exponent);WHILE true DO    SET header         TO_HEX(REVERSE(FROM_HEX(version))) ||        TO_HEX(REVERSE(FROM_HEX(hash_prev_block))) ||        TO_HEX(REVERSE(FROM_HEX(merkle_root))) ||        TO_HEX(REVERSE(FROM_HEX(header_datetime))) ||        TO_HEX(REVERSE(FROM_HEX(bits))) ||        TO_HEX(REVERSE(FROM_HEX(nonce)));    SET b_header = FROM_HEX(header);    SET header_hash = TO_HEX(REVERSE(SHA256(SHA256(b_header))));    IF CAST(CONCAT(&#34;0x&#34;, header_hash) AS FLOAT64) &lt; target THEN        SELECT nonce;        BREAK;    END IF;    IF iteration &gt; max_iterations THEN        SELECT &#34;No nonce found&#34;;        BREAK;    END IF;    SET iteration = iteration + 1;END WHILE;
</span></span></code></pre></div><h2 id="complete-the-loop">Complete the loop</h2>
<p>Yeah well — isn’t that something? The only thing we need to do now is to actually iterate through the nonce to let the query find the correct value. To have an iterable variable we need one of the type FLOAT64. I will set the value a little bit under the expected target, to speed up the testing:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">DECLARE nonce_dec FLOAT64 DEFAULT 3371190464; 
</span></span></code></pre></div><p>Now, after the loop starts and before we collect all header details,we need to get the hexadecimal nonce. And at the end of the loop, we increment the decimal nonce:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">WHILE true DO    SET nonce = FORMAT(&#34;%X&#34;, nonce_dec);    SET header =        [....]    SET nonce_dec = nonce_dec + 1;END WHILE;
</span></span></code></pre></div><h2 id="adding-the-second-loop">Adding the second loop</h2>
<p>Now as we already learned, it may not be enough to test 2⁶⁴ possible numbers to find a fitting nonce. That’s why we’re going to add another loop to use the <strong>version field</strong> for some additional search space. Unfortunately this is a little more complicated. So, what do we need? Binary numbers!</p>
<p>This is the version for our famous block # 727155. Again we’re using this as an example. If you’d like to mine a new block, you need to adjust at least the soft fork part.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">0x20002000
</span></span></code></pre></div><p>In binary that is:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">00110100010011011100000000000100
</span></span></code></pre></div><p>The last 13 bits are representing the soft work readiness. That’s a given value. The version is pre-defined to be (at least) 001 for the first 3 bits. We’re taking the easy route here and are going to declare this in binary already. The remaining 16 bits are up and ready to be iterated through:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">DECLARE soft_forks STRING DEFAULT &#34;0000000000000&#34;;DECLARE version_roller INT64 DEFAULT 1;DECLARE version_roller_bin STRING;DECLARE version_bits STRING DEFAULT &#34;001&#34;;
</span></span></code></pre></div><blockquote>
<p>Please note, that I’m setting the version_roller to 1— that’s the one used in the block we are working on. Again, this is to speed up the demonstration here.</p>
</blockquote>
<p>We will use an additional outer loop to iterate over the version roller. The iteration comes at the end, at the beginning of the loop we need to process the decimal value to get 16 bits out from it and press it into the hexadecimal format:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">main_loop: WHILE true DO        SET version_roller_bin = SUBSTR(bqutil.fn.to_binary(version_roller), -16);    SET version = FORMAT(&#34;%X&#34;, bqutil.fn.from_binary(CONCAT(version_bits, version_roller_bin, soft_forks)));WHILE true DO        [....]    END WHILE;    SET version_roller = version_roller + 1;END WHILE;
</span></span></code></pre></div><p>Take care of the label I set here to address the loop: <strong>MAIN_LOOP</strong>. I <strong>have to reference</strong> the outer loop when breaking from inside the inner loop:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">    [...]    BREAK main_loop;    [...]
</span></span></code></pre></div><p>Check out the full script in final part.</p>
<h2 id="congratulations-and-conclusion">Congratulations and Conclusion</h2>
<p>Basically that’s it. That’s how the full script should look like this (





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://gist.github.com/nickyreinert/2d0936922bbf242429c21af3c5b6866b?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">gist.github.com</a>


). Feel free to increase the value for <strong>max_iterations</strong> and set the starting value for <strong>nonce_dec</strong> to 0 and see how long it will take Google to find the correct nonce.</p>
<p>This is a lot of weird SQL stuff and far away from SELECT * FROM table. Is it worth it? Well, I set the starting nonce to 3370194460 and executed the script.</p>
<p>Spoiler Alert: After 10 minutes I stopped the query. Another approach (see below) seems way faster, though it’s a cheating with the actual calculations.</p>
<h2 id="disclaimer">Disclaimer</h2>
<p>I will not loose much words about <strong>Proof of Work.</strong> The process itself is sophisticated and somehow thoughtfull… as well as a little weird. But above all it’s also a huge wast of energy and there are better ways to achieve the same outcome. I’d like to end with a quote from another author on Medium who said:</p>
<blockquote>
<p>You’ve got to wonder what we’d achieve if we used even a fraction of this computing power for medical research instead…</p>
</blockquote>
<h1 id="additional-ressources">Additional ressources</h1>
<p>





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://medium.com/fcats-blockchain-incubator/understanding-the-bitcoin-blockchain-header-a2b0db06b515?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">This is a pretty good and helpful medium article</a>


 about the Blockchain header by RJ Rybarczyk. 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://learnmeabitcoin.com/explorer/block/bits/170a40c0?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">This is a website</a>


 that provides a lot of technical background information as well as tools to calculate hashes or browse the Blockchain.</p>
<p>A couple of years ago Uri Shaked also [wrote about this topic](I stumbled over his article while doing research. But he was pursuing a different approach. First of all he was using a <strong>random feature</strong> to find a fitting nonce. I also considered this before, but as you cannot ensure to ge the same random value twice, you will waste a lot of time (he actually calculated the same nonce within 20 seconds, which still is faster than my approach). Besides that he was using sub-queries to create temporary tables filled with the nonce.</p>
]]></content:encoded><atom:updated>2026-06-16T20:56:44+02:00</atom:updated><category>blog</category><category>anleitungen</category><dc:subject>Lesezeit: 5 Minuten</dc:subject><dc:type>tutorial</dc:type></item><item><title>The Thin Line Between Scam and Ambitious Entrepreneurship</title><link>https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/</link><pubDate>Tue, 12 Aug 2025 19:00:00 +0000</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/</guid><description> German Version I’m not a fan of mobile games Not because I don’t like playing, but because a questionable business model has developed around mobile games, making it very difficult to find the true gems. It seems like it’s more about selling ad space or generating in-app purchases with simple game …</description><content:encoded><![CDATA[<p><em>





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://nickyreinert.de/2025/2025-08-06-schmaler-grat-scam-und-unternehmertum/?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">German Version</a>


</em></p>
<h2 id="im-not-a-fan-of-mobile-games">I&rsquo;m not a fan of mobile games</h2>
<p>Not because I don&rsquo;t like playing, but because a questionable business model has developed around mobile games, making it very difficult to find the true gems. It seems like it&rsquo;s more about selling ad space or generating in-app purchases with simple game mechanics. Of course, there are exceptions, but they get lost in the sheer volume of offerings.</p>
<p>I recently stumbled upon one of these games by chance, and it perfectly represents these accusations: <strong>





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://play.google.com/store/apps/details?id=com.EternalStudio.DozerDemolish&amp;hl=en_US&amp;utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">Dozer Demolish</a>


</strong>.</p>
<p>On Google Play, the game is advertised <strong>without an age rating</strong>, while on Apple&rsquo;s App Store, it&rsquo;s rated <strong>4 years and up</strong>. The Android store mentions over <strong>10 million downloads</strong>, 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://www.eternalgamestudio.com/?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">according to developer Eternal Studio, it&rsquo;s more than 15 million</a>


.</p>
<p align="center">
  <a href="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-playstore-usk.png" target="_blank" style="margin: 0 auto; display: block;">
    <img src="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-playstore-usk.png" alt="alt text" width="500"/>
  </a>
</p>
<center><small>No age restriction and in-game purchases. Sounds like a contradiction *cough* legal capacity *cough*</small></center>
<br />
<br />
<p>The game is distributed by <strong>





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://www.homagames.com/?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">HOMA</a>


</strong>, which also contracts other developers. Headquarters: France. So within the jurisdiction of the GDPR. The motto of <strong>HOMA</strong> is:</p>
<p align="center">
  <a href="https://play.google.com/store/apps/dev?id=4656343638685426415&hl=en&gl=US" target="_blank" style="margin: 0 auto; display: block;">
    <img src="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-playstore-motto.png" alt="HOMA Motto - Source Android Play Store https://play.google.com/store/apps/dev?id=4656343638685426415&hl=en&gl=US" width="700"/>
  </a>
</p>
<center><small>HOMA Motto - Source Android Play Store <a href="https://play.google.com/store/apps/dev?id=4656343638685426415&hl=en&gl=US">https://play.google.com/store/apps/dev?id=4656343638685426415&hl=en&gl=US</a></small></center>
<br />
<br />
<p>&ldquo;Amazing Content&rdquo; - Well, <em>let&rsquo;s see</em>!</p>
<p>Games in the &ldquo;Convenience Gaming&rdquo; category follow the same principle: There&rsquo;s hardly any depth, the rules are quickly explained, the controls are intuitive, and often not much skill or thought is required. Everything is geared towards short-term dopamine release. This doesn&rsquo;t have to be derogatory; it&rsquo;s a form of entertainment that attracts millions of players.</p>
<p>The gameplay of <strong>Dozer Demolish</strong> adheres to the industry&rsquo;s principle. You <em>roam</em> with heavy machinery across changing landscapes, destroy buildings, collect their remains, and earn money to buy new vehicles or upgrade existing ones. One-handed and on the side. On the toilet. In bed. On the train. During meetings. &ldquo;Convenience Gaming,&rdquo; indeed.</p>
<h1 id="the-thing-about-data-privacy">The thing about data privacy</h1>
<p>So far, so unspectacular. Things get interesting, provided you have a penchant for such details, when it comes to <strong>data privacy</strong>.</p>
<p align="center">
  <a href="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/meme-privacy.png" target="_blank" style="margin: 0 auto; display: block;">
    <img src="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/meme-privacy.png" alt="alt text" width="500"/>
  </a>
</p>
<br />
<br />
<p>According to its self-disclosure, the app shares data in the following categories with other companies:</p>
<ul>
<li>Financial data</li>
<li>Personal data such as email addresses and user IDs</li>
<li>App activities</li>
</ul>
<p>Not inherently reprehensible, as this makes sense for in-app purchases and advertising.</p>
<p>But location data? (Hint: Location-based advertising…)</p>
<p align="center">
  <a href="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-playstore-dataprivacy.png" target="_blank" style="margin: 0 auto; display: block;">
    <img src="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-playstore-dataprivacy.png" alt="alt text" width="250"/>
  </a>
</p>
<center><small>Location data. Why?</small></center>
<br />
<br />
<p>Problematic. But that&rsquo;s not all. Under security measures, it states:</p>
<blockquote>
<p>Data is not encrypted.
Data cannot be deleted.</p>
</blockquote>
<p>Okay. At least honest, if not up-to-date. On Google&rsquo;s Play Store, the developer&rsquo;s privacy policy is referenced, which hardly deserves the name &ldquo;privacy policy.&rdquo; A Google email address, a paragraph on Google Analytics, and then a note on &ldquo;minors&rdquo;:</p>
<p align="center">
  <a href="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-eternal-dataprivacy.png" target="_blank" style="margin: 0 auto; display: block;">
    <img src="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-eternal-dataprivacy.png" alt="alt text" width="700"/>
  </a>
</p>
<center><small>The privacy policy of Eternal Studio</small></center>
<br />
<br />
<blockquote>
<p>&ldquo;Eternal Studio does not knowingly collect personal information from children under the age of 13.&rdquo;</p>
</blockquote>
<p>Of course not. Too bad the game is offered without an age rating.</p>
<blockquote>
<p>&ldquo;If Eternal Studio learns that Eternal Studio has inadvertently gathered personal information from children under 13 years of age, Eternal Studio will take reasonable measures to promptly delete such personal data from Eternal Studio’s records.&rdquo;</p>
</blockquote>
<p>&ldquo;inadvertently&rdquo; - that sounds a bit like shirking responsibility.</p>
<p>This is only surpassed by the following note:</p>
<blockquote>
<p>&ldquo;If you are under the age of 13 or a minor in your country of residence, please ask your legal guardian’s permission to access and use our services.&rdquo;</p>
</blockquote>
<p>So the 4-year-old child is supposed to read the English privacy policy and then ask their parents for permission. Hilarious. Reminder: The game has an enormous reach with 15 million downloads. A bit more professionalism isn&rsquo;t too much to ask, is it?</p>
<p>Needless to say, the site provides no information about who you&rsquo;re actually dealing with. According to the &ldquo;Impressum,&rdquo; you can contact them via <em>




  <a href="mailto:xxx@eternalgamestudio.com">xxx@eternalgamestudio.com</a>

</em>. In the privacy policy itself, it&rsquo;s <em>




  <a href="mailto:xxx@gmail.com">xxx@gmail.com</a>

</em>. [The Facebook page](refers to <em>




  <a href="mailto:xxx@gmail.com">xxx@gmail.com</a>

</em>. [On X/Twitter](you at least learn that the developer studio is presumably based in Michigan (USA).</p>
<p><em>(Email addresses anonymized, although they are publicly accessible, but who knows…)</em></p>
<p>Enough complaining; perhaps — and I say this quite sincerely — the developer was surprised by the success, and I just happened to stumble upon a rare exception. Nevertheless, the game is backed by a fairly large publisher. A product with 15 million users and such an aggressive advertising strategy deserves closer scrutiny.</p>
<p>(The Apple App Store, by the way, refers to [the publisher&rsquo;s privacy policy](It&rsquo;s casually written and quite detailed.)</p>
<p>At first glance, formal data privacy already fails. What about the app itself?</p>
<p>It starts with the usual consent dialog. Here, you can either accept everything unseen or influence the collected data via detailed selection. Problematic: There&rsquo;s no button to &ldquo;reject all.&rdquo; The detailed selection is hidden in a second view and is not immediately accessible.</p>
<p>The list of technologies used is impressively long for a game. <strong>69 vendors</strong> measure ad performance, 24 measure app performance. 33 providers analyze target groups, 57 improve services. 7 providers handle targeting.</p>
<p>Each category is optimistically declared as &ldquo;legitimate interest.&rdquo; Reminder: The game is offered without an age rating. For minors, special protections apply under the GDPR.</p>
<p>Even the individual processing categories must be manually deselected. Needless to say, the explanations are anything but understandable for a normal user, let alone a minor. (Incidentally, the industry association <strong>IAB</strong> has done an excellent job here, selling this kind of overspecification called <strong>TCF</strong> (Transparency and Consent Framework) as &ldquo;transparency.&rdquo;)</p>
<div align="center">
<table>
  <tr>
    <td align="center">
      <a href="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-consent-dialog-1.png" target="_blank">
        <img src="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-consent-dialog-1.png" alt="alt text" width="250"/>
      </a>
      <br />
      <small>Legitimate interest? Please manually deselect!</small>
    </td>
    <td align="center">
      <a href="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-consent-dialog-2.png" target="_blank">
        <img src="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-consent-dialog-2.png" alt="alt text" width="250"/>
      </a>
      <br />
      <small>Transparency doesn't mean comprehensibility</small>
    </td>
  </tr>
</table>
</div>
<br />
<br />
<p>But that&rsquo;s not all: In the app, you can&rsquo;t correct your selection afterward; there&rsquo;s simply no menu option for it. This is also hardly compatible with GDPR requirements.</p>
<p>The app doesn&rsquo;t leave the best impression regarding data privacy. But that&rsquo;s not all, let alone what makes a game.</p>
<blockquote>
<p>(And here we are again with my general criticism of data privacy: Most users are probably more annoyed by the endless consent banners than feeling informed. Complete data collection avoidance isn&rsquo;t feasible either; as a data analyst, I&rsquo;m aware of this. But smaller studios and companies are likely overwhelmed by the legal requirements.)</p>
</blockquote>
<p>Never mind. What about the content?</p>
<h2 id="the-thing-about-advertising">The thing about advertising</h2>
<p>As mentioned above, a business model has established itself here where free games seemingly only serve as platforms to push advertising onto devices with — attention, subjective exaggeration for dramatic effect — a blatant lack of subtlety. It reminds me of a not-so-famous quote 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://nickyreinert.de/2020/2020-10-24-marketing-killed-the-internet-star/?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">that I once fittingly recited</a>


:</p>
<blockquote>
<p>Journalistic content is the vehicle to capture the audience&rsquo;s attention for advertising content.</p>
<p><em>Springer lawyers in 2015</em></p>
</blockquote>
<p>And here, it seems no different. From a business perspective, I respect the success. From a user&rsquo;s perspective, this practice is only a hair away from being a scam.</p>
<p>The game is offered for free. For <strong>€1.99</strong>, you can purchase an ad-free package. This is at least misleading — more on that shortly — and technically poorly implemented:</p>
<p>I bought the package on one device, but on a second device, ad banners still appear at the bottom of the game.</p>
<p align="center">
  <a href="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-dozer-unlocked.png" target="_blank" style="margin: 0 auto; display: block;">
    <img src="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-dozer-unlocked.png" alt="alt text" width="250"/>
  </a>
</p>
<center><small>That works well with the ad-free package.</small></center>
<br />
<br />
<p>The entire advertising isn&rsquo;t even disabled. You get <em>three dozen</em> &ldquo;upgrade tickets,&rdquo; which finance certain in-game actions. After about 1 hour of gameplay, these are already used up. Then, upgrades and other game mechanics sporadically but also with intrusive regularity require ad videos.</p>
<p>And these are intense. We&rsquo;re not talking about small 30-second inserts like on YouTube.</p>
<p>&ldquo;Dozer Demolish&rdquo; usually plays two spots, totaling almost 2 minutes. Some of the advertised games can even be played directly for about 15 seconds. Can you close the ad overlay now? No. First, you&rsquo;re taken to the Play Store. From there, you return to the ad overlay, with a final ad message that can now be closed.</p>
<p>Wow.</p>
<div align="center">
<table>
  <tr>
    <td align="center">
      <a href="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-dozer-temu-ad.png" target="_blank">
        <img src="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-dozer-temu-ad.png" alt="The first insert usually lasts 60 seconds." width="250"/>
      </a>
      <br />
      <small>The first insert usually lasts 60 seconds.</small>
    </td>
    <td align="center">
      <a href="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-dozer-lastwar-ad.png" target="_blank">
        <img src="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-dozer-lastwar-ad.png" alt="Then another one, about the same length." width="250"/>
      </a>
      <br />
      <small>Then another one, about the same length.</small>
    </td>
  </tr>
</table>
</div>
<p>Since basic game mechanics are affected by these inserts, you easily spend 2 minutes playing and 2 minutes under ad pressure. If you don&rsquo;t want this, you can replenish the upgrade ticket quota. For example, 10 tickets cost €6.99. This corresponds to about 10 minutes of gameplay under normal conditions. The most expensive package costs €9.99 and contains 60 tickets.</p>
<div align="center">
<table>
  <tr>
    <td align="center">
      <a href="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-dozer-pricelist.png" target="_blank">
        <img src="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-dozer-pricelist.png" alt="In-app purchases in 'Dozer Demolish'" width="220"/>
      </a>
      <br />
      <small>In-app purchases in "Dozer Demolish" (Source: <a href="https://apps.apple.com/de/app/dozer-demolish-stadtabriss/id6447895913">iOS App Store</a>)</small>
    </td>
    <td align="center">
      <a href="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-dozer-store-1.png" target="_blank">
        <img src="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-dozer-store-1.png" alt='"Best Value": 60 tickets for €9.99' width="220"/>
      </a>
      <br />
      <small>The best value: 60 tickets for €9.99—2 minutes of your life cost you €0.16.</small>
    </td>
    <td align="center">
      <a href="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-dozer-store-2.png" target="_blank">
        <img src="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-dozer-store-2.png" alt="Special offer! €6.99 for 10 tickets" width="220"/>
      </a>
      <br />
      <small>Special offer! €6.99 for 10 tickets and some upgrades. The price of your life increases to €0.69!</small>
    </td>
  </tr>
</table>
</div>
<h2 id="whats-the-problem">What&rsquo;s the problem?</h2>
<p>&ldquo;Well, everyone can decide for themselves!&rdquo; one might say. What&rsquo;s the problem? As mentioned, it&rsquo;s a brilliant business model. The problem is primarily the age rating and the handling of data privacy requirements. Secondly, from a personal perspective, the excessive coupling of game mechanics to ad delivery.</p>
<p><strong>Do you want to develop games or deliver ads?</strong></p>
<p align="center">
  <a href="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/meme-wo-problem.png" target="_blank" style="margin: 0 auto; display: block;">
    <img src="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/meme-wo-problem.png" alt="alt text" width="500"/>
  </a>
</p>
<br />
<br />
<h3 id="problem-1-legal-capacity-of-minors">Problem 1: Legal capacity of minors</h3>
<p>With an <strong>age rating</strong> of 0 (or 4 years on iOS), this game is accessible to a group of people who, at least under German law, are not legally capable. Only at 7 years old are they partially legally capable and can make <strong>small purchases</strong> (&ldquo;pocket money clause&rdquo;) without parental consent. How does this align with the sometimes very expensive in-app purchases?</p>
<p>Google describes the <strong>age rating</strong> of 0 in two different ways:</p>
<p align="center">
  <a href="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/usk-0-description-long.png" target="_blank" style="margin: 0 auto; display: block;">
    <img src="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/usk-0-description-long.png" alt="alt text" width="500"/>
  </a>
</p> 
<center><small>USK 0 - Long description source: https://play.google.com/store/apps/details?id=com.farmadventure.global</small></center>
<br />
<br />
<p>or way less informative:</p>
<p align="center">
  <a href="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/usk-0-description-brief.png" target="_blank" style="margin: 0 auto; display: block;">
    <img src="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/usk-0-description-brief.png" alt="alt text" width="500"/>
  </a>
</p>
<center><small>USK 0 - Brief description source: https://play.google.com/store/apps/details?id=com.farmadventure.global</small></center>
<br />
<br />
<h3 id="problem-2-dark-patterns">Problem 2: Dark patterns</h3>
<p>The game uses <strong>dark patterns</strong> to place ads, such as when transitioning to a new day. A large green button starts an ad block and rewards with in-game currency. The small text below skips it. Many upgrades are advertised as &ldquo;free,&rdquo; but they usually involve an ad insert. Does only my moral compass sound the alarm when this practice is applied to minors?</p>
<div align="center">
<table>
  <tr>
    <td align="center">
      <a href="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-dozer-earned.png" target="_blank">
        <img src="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-dozer-earned.png" alt="Collect Double" width="250"/>
      </a>
      <br />
      <small>Please press the big green button; it won't be to your disadvantage!</small>
    </td>
    <td align="center">
      <a href="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-dozer-upgrade.png" target="_blank">
        <img src="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-dozer-upgrade.png" alt="alt text" width="250"/>
      </a>
      <br />
      <small>Behind this button is an ad insert. Would you have recognized it?</small>
    </td>
  </tr>
</table>
</div>
<h3 id="problem-3-accessibility-of-games-with-higher-age-ratings">Problem 3: Accessibility of games with higher age ratings</h3>
<p>The ads are not only intrusive; they are often far from age-appropriate! This is especially problematic when a minor has the opportunity (not to say &ldquo;is forced&rdquo;) to test a game for 15 seconds, and the advertised game has a different age rating! Imagine going to the cinema with your child to watch Cars 7, and before the movie, there&rsquo;s a trailer for Texas Chainsaw Massacre.</p>
<div align="center">
<table>
  <tr>
    <td align="center">
      <img src="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-dozer-kingshot-ad.png" alt="Test game" width="220"/>
      <br />
      <small>Hey kid, still unsure? Test our game now. Or resist the temptation for 15 seconds.</small>
    </td>
    <td align="center">
      <img src="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-dozer-kingshot-store.png" alt="Rated 12 years and up" width="220"/>
      <br />
      <small>The game is rated 12 years and up. But you don't have to tell your parents.</small>
    </td>
    <td align="center">
      <img src="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-dozer-tophereos-ad.png" alt="Ad break test" width="220"/>
      <br />
      <small>This game can also be tested during the ad break.</small>
    </td>
  </tr>
  <tr>
    <td align="center">
      <img src="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-dozer-tophereos-store.png" alt="Rated 12 years and up" width="220"/>
      <br />
      <small>Rated 12 years and up here as well.</small>
    </td>
    <td align="center">
      <img src="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-dozer-lastwar-ad-1.png" alt="Shooting" width="220"/>
      <br />
      <small>Every 4-year-old loves it. Shooting.</small>
    </td>
    <td align="center">
      <img src="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-dozer-lastwar-ad-2.png" alt="Shooting yourself" width="220"/>
      <br />
      <small>And so it's not just watching: Shooting yourself makes you want more!</small>
    </td>
  </tr>
  <tr>
    <td align="center">
      <img src="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-dozer-lastwar-store.png" alt="Rated 12 years and up" width="220"/>
      <br />
      <small>Rated 12 years and up here as well.</small>
    </td>
    <td align="center">
      <img src="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-dozer-monopoly-ad.png" alt="Monopoly clone" width="220"/>
      <br />
      <small>A Monopoly clone is also advertised. Rating here: 16 years</small>
    </td>
    <td align="center">
      <img src="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-dozer-zombie-ad.png" alt="Zombie game" width="220"/>
      <br />
      <small>Can't be missing as ad entertainment: A zombie game, rated 16+. Sweet dreams tonight, little prince.</small>
    </td>
  </tr>
</table>
</div>
<p>Guess why Monopoly is rated 16 years and up? Not because of violence, apparently, but because of incentives to buy and in-game purchases.</p>
<p align="center">
  <a href="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/monopoly-rating.png" target="_blank" style="margin: 0 auto; display: block;">
    <img src="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/monopoly-rating.png" alt="alt text" width="250"/>
  </a>
</p>
<center><small>Monopoly clone with in-game purchases. Rated 16 years and up.</small></center>
<br />
<br />
<h2 id="sad-isolated-case">Sad isolated case?</h2>
<p>I stumbled upon Dozer Demolish by chance. Homa currently offers 50 games in the Google Play Store. I looked at four other games.</p>
<p>





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://play.google.com/store/apps/details?id=com.homagames.studio.allinhole&amp;hl=gsw&amp;utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">All in Hole!</a>


. Here, ad delivery is much less aggressive. After 15 minutes of gameplay, there were no ads, not even as an overlay at the bottom of the screen. But the in-game purchases are hefty. The largest package costs €99. On consoles &amp; PC, you get high-quality triple-A titles for that. Interesting: The <strong>age rating is 12 years</strong>. That doesn&rsquo;t really make sense, as I didn&rsquo;t see any ads here and the gameplay is anything but &ldquo;not suitable for minors.&rdquo;</p>
<p align="center">
  <a href="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-allinhole-prices.jpg" target="_blank" style="margin: 0 auto; display: block;">
    <img src="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-allinhole-prices.jpg" alt="alt text" width="250"/>
  </a>
</p>
<center><small>€99.99 - that's hardly justifiable by development costs.
</small></center>
<br />
<br />
<p>[Aquarium Land]((from 0 years, 50 million downloads). Here, the first ad appears after 5 minutes. Again, a game from another age category is advertised.</p>
<p>[Cube Blast Journey]((0 years, 1 million): This game comes without a consent dialog. That&rsquo;s unusual. And indeed: monitoring diligently records requests, some of which clearly count as tracking and analytics requests. For example, those to gameanalytics.com, amazon-adsystem.com, applovin.com, adjust.com, or doubleclick.net.</p>
<p align="center">
  <a href="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-cube-requests.png" target="_blank" style="margin: 0 auto; display: block;">
    <img src="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-cube-requests.png" alt="alt text" width="250"/>
  </a>
</p>
<center><small>Tracking and analytics requests in Cube Blaster Journey</small></center>
<br />
<br />
<p>[Solitaire Klondike Classic]((0 years, &gt;5 million). The game is distributed by Homa, but after a short time, you&rsquo;re referred in broken German to &ldquo;[the new version]((0 years, &gt;1 million),&rdquo; distributed by <strong>





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://www.freelaxgame.com/about.html?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">Freelax</a>


</strong>. Also, due to the banner at the top of the screen, it&rsquo;s not really clear whether this is a legitimate reference or simply advertising.</p>
<p align="center">
  <a href="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-solitaire-solitaire-ad.jpg" target="_blank" style="margin: 0 auto; display: block;">
    <img src="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-solitaire-solitaire-ad.jpg" alt="alt text" width="250"/>
  </a>
</p>
<center><small>Strange way to offer an "update"</small></center>
<br />
<br />
<p>In Solitaire Classic, right after the familiar TCF consent dialog, there&rsquo;s another consent request — this time preselected. Since GDPR, this is no longer permitted, as it doesn&rsquo;t constitute &ldquo;explicit consent.&rdquo;</p>
<p align="center">
  <a href="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-solitaire-consent.jpg" target="_blank" style="margin: 0 auto; display: block;">
    <img src="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-solitaire-consent.jpg" alt="alt text" width="250"/>
  </a>
</p>
<center><small>Consent preselected - that's not GDPR-compliant.</small></center>
<br />
<br />
<p>The supposed successor is even bolder regarding consent. Here, starting the game is tied to consent—which is also legally questionable.</p>
<p align="center">
  <a href="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-solitaire-start.jpg" target="_blank" style="margin: 0 auto; display: block;">
    <img src="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/screenshot-solitaire-start.jpg" alt="alt text" width="250"/>
  </a>
</p>
<center><small>Even less GDPR-compliant: Starting the game is tied to consent.</small></center>
<br />
<br />
<p>[Cat &amp; Mouse]((6 years, 10 million). Here, too, ads are actively displayed, referring to apps and games that are not age-appropriate.</p>
<h2 id="what-about-other-publishers">What about other publishers?</h2>
<p>Remarkably, the games advertised that I mentioned previously do not pursue a comparable advertising strategy, in fact some <strong>do not even offer in-game ads</strong> at all. Which doesn&rsquo;t mean that the publisher behind them doesn&rsquo;t have assets in stock following the same principle. Let&rsquo;s look at [Century Games PTE. LTD.](the publisher behind <strong>Kingshot</strong>. They have 23 games in their library, of which 12 have in-game ads (according to their Google Play Store listing), 5 with no age restriction. I checked these three:</p>
<p>[Tasty Travels: Merge Game](comes without a valid consent dialog, but according to their data privacy policy collects data for several purposes, including marketing. Eventually, after playing for a while, I was presented a playable demo for [Whiteout Survival](([Century Game PTE LTD](age rating 12+).</p>
<div align="center">
<table>
  <tr>
    <td align="center">
      <a href="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/free-energy.png" target="_blank">
        <img src="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/free-energy.png" alt="alt text" width="250"/>
      </a>
      <br />
      <small>Playable ad for Family Farm Adventure</small>
    </td>
    <td align="center">
      <a href="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/whiteout.survival.png" target="_blank">
        <img src="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/whiteout.survival.png" alt="alt text" width="250"/>
      </a>
      <br />
      <small>Playable ad for Idle Courier</small>
    </td>
  </tr>
</table>
</div>
<p>





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://play.google.com/store/apps/details?id=com.farmadventure.global&amp;utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">Family Farm Adventure</a>


 has a questionable consent dialog, like the one that Dozer Demolish offers. While I didn&rsquo;t notice any ads at all, the prices are even worse than experienced before. 120 Euros for an in-game package - without knowing how long this lasts, this is clearly nothing you want to offer to minors.</p>
<p align="center">
  <a href="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/family-farm-adventure.png" target="_blank" style="margin: 0 auto; display: block;">
    <img src="https://nickyreinert.de/en/2025/2025-08-06-thin-line-between-scam-and-ambitious-entrepreneurship/family-farm-adventure.png" alt="alt text" width="250"/>
  </a>
</p>
<center><small>Price table for Family Farm Adventure</small></center>
<br />
<br />
<p>[Idle Courier](also comes with this not-so-really-transparent consent dialog and the &ldquo;coupled consent&rdquo; (if you go on, you accept our privacy policy). In this game it only took me 2 minutes to face the first playable ad for Kingshot (rating 12 years and up).</p>
<p>





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://play.google.com/store/apps/developer?id=RiverGame&amp;utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">River Game</a>


 and 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://play.google.com/store/apps/developer?id=FUNFLY&#43;PTE.&#43;LTD.&amp;utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">Funfly PTE</a>


 do not offer any games with an age rating of 0 years. 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://play.google.com/store/apps/developer?id=FunPlus&#43;International&#43;AG&amp;utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">FunPlus International AG</a>


 has 15 games in stock. All of them with age ratings between 12 and 16 years. 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://play.google.com/store/apps/dev?id=9028773071151690823&amp;utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">Scopely</a>


 has just one game with age rating of 0, but without ads.</p>
<h2 id="conclusion">Conclusion?</h2>
<p>The games mentioned pursue their advertising goals far less aggressively than &ldquo;Dozer Demolish&rdquo;, but the delivery also seems to suffer from the same problem: Although a certain age restriction is given, ads for games for older ages are displayed. And not only that, they can even be tried out directly.</p>
<p>Now, one can argue whether 4-year-old children should even have access to tablets and smartphones to play such games. But that&rsquo;s a pedagogical question. Ultimately, the games are distributed with a certain age restriction, but still pave the way to content that is not age-appropriate. That&rsquo;s no longer just a pedagogical issue. And I am not even talking about consent dialogs or price ranges way beyond reasonability.</p>
<p>It&rsquo;s remarkable that such successful games fly under the radar. From my own experience, I know that Google at least formally has very strict requirements. Compliance with platform guidelines is (automated?) monitored and enforced with deadlines and the threat of de-publication. That&rsquo;s my impression. Is Google sometimes a bit more lenient because its own revenue depends on advertising income?</p>
<p>Don&rsquo;t get me wrong: Most of the games have quite a high quality, some even with a decent story behind them. It&rsquo;s only fair that the developer or publisher tries to monetize the game to cover development costs. Also, I only stumbled upon Homa by chance, and I don&rsquo;t want to portray the publisher as a black sheep here. But as this is about the youngest target group, lax data privacy practices, ads beyond control and expensive in-game purchases - I think it&rsquo;s worth taking a closer look.</p>
<p>Some games make me wonder: Is it still about delivering good entertainment or just flooding the market with low effort to keep users glued to the device with simple game ideas for as long as possible, in order to show as much advertising as possible? Advertising that is no longer just the blinking banner at the edge of the screen, but interactive content that demands more time than the actual game?</p>
<p>I&rsquo;m not a fan of mobile games. And now you know why.</p>
<h2 id="statement-from-homa-and-google">Statement from Homa and Google</h2>
<p>On 30.07. I asked <strong>Homa</strong> and <strong>Google</strong> for a statement. Homa responded within 2 days and announced that they would address the issue internally. Exemplary, I&rsquo;m curious. Google has not responded so far.</p>
]]></content:encoded><atom:updated>2026-06-16T20:56:44+02:00</atom:updated><category>blog</category><dc:subject>Lesezeit: 5 Minuten</dc:subject><dc:type>essay</dc:type></item><item><title>wad - about chatbots, captchas, and Chromecast</title><link>https://nickyreinert.de/en/2025/2025-08-10-we-are-doomed/</link><pubDate>Sat, 09 Aug 2025 12:34:56 +0000</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/en/2025/2025-08-10-we-are-doomed/</guid><description>New month, new category – “We are doomed” (wad) is about “smart and new technology” that turns out to be not so smart. But new, at least.
Chatbots are going to replace search engines. Aren’t they? Well, no. I asked Gemini (Google’s AI chatbot) how to update Stadia’s firmware (Google’s former gaming …</description><content:encoded><![CDATA[<p>New month, new category – &ldquo;We are doomed&rdquo; (wad) is about &ldquo;smart and new technology&rdquo; that turns out to be not so smart. But new, at least.</p>
<h2 id="chatbots-are-going-to-replace-search-engines-arent-they">Chatbots are going to replace search engines. Aren’t they?</h2>
<p>Well, no. I asked <strong>Gemini</strong> (Google&rsquo;s AI chatbot) how to update <strong>Stadia&rsquo;s</strong> firmware (Google&rsquo;s former gaming platform). Gemini suggested searching the internet for a proper solution:</p>
<p align="center">
  <a href="gemini-suggest-web-search.png" target="_blank" style="margin: 0 auto; display: block;">
    <img src="gemini-suggest-web-search.png" alt="Meme: AI outsourcing" width="400"/>
  </a>
</p>
<center>Learn how to help yourself!</center>
<br />
<br />
x
Not only that, but also notice how Google's own AI chatbot doesn’t even know that Google extended the firmware update tool on their website: https://stadia.google.com/controller/index_de.html
<p>Don’t they talk to each other?</p>
<h2 id="you-shall-not-pass">You shall not pass</h2>
<p>We all struggle with captchas from time to time. Sometimes they ignore our correct answers, and we have to solve dozens of captchas. Sometimes you get captchas like this. I’m probably not the first one to see this:</p>
<p align="center">
  <a href="bike-captcha.png" target="_blank" style="margin: 0 auto; display: block;">
    <img src="bike-captcha.png" alt="Meme: AI outsourcing" width="400"/>
  </a>
</p>
<center>Where’s the bike?</center>
<br />
<br />
<p>(<strong>Fahrrad</strong> is German for <strong>bicycle</strong>, but here it was probably shortened to &ldquo;bike,&rdquo; which can also mean any kind of motorcycle. In fact, this is more of a moped or <strong>scooter</strong>.)</p>
<h2 id="what-the-actual-fxxk">What the actual FxxK</h2>
<p>Yeah, and then we have this one. Story behind it: I have this <strong>Chromecast Ultra</strong> gathering dust for a couple of years now. Today I decided to give it a try. I connected it to my <strong>optical computer-to-human interface</strong> (the one firing a million protons directly through your eyeballs into your brain) and tried to register it. After the annoying fact that I have to install Google’s Home app to set up the Chromecast, I was referred to another app called &ldquo;Device Utility.&rdquo;</p>
<p>I have to install this tool to &ldquo;unlock&rdquo; my Chromecast. Some kind of protection? Seems like Google has activated a “kill switch” after a period of non-usage.</p>
<p>However, the problem is what you can see here: My <strong>Pixel 8 Pro</strong> – manufactured by Google – is not compatible with this app.</p>
<p align="center">
  <a href="https://nickyreinert.de/en/2025/2025-08-10-we-are-doomed/app-store-device-utility.png" target="_blank" style="margin: 0 auto; display: block;">
    <img src="https://nickyreinert.de/en/2025/2025-08-10-we-are-doomed/app-store-device-utility.png" alt="Meme: AI outsourcing" width="400"/>
  </a>
</p>
<center>You shall not pass.</center>
<br />
<br />
<h2 id="all-i-wanted-was-netflix-now-ive-ended-up-spinning-up-a-virtual-android-device-in-android-studio">All I wanted was Netflix. Now I’ve ended up spinning up a virtual Android device in Android Studio.</h2>
<h2 id="conclusion">Conclusion</h2>
<p align="center">
  <a href="https://nickyreinert.de/en/2025/2025-08-10-we-are-doomed/table-flip-guy.jpg" target="_blank" style="margin: 0 auto; display: block;">
    <img src="https://nickyreinert.de/en/2025/2025-08-10-we-are-doomed/table-flip-guy.jpg" alt="Meme: AI outsourcing" width="400"/>
  </a>
</p>
<br />
<br />
]]></content:encoded><atom:updated>2026-06-03T13:14:29+02:00</atom:updated><category>blog</category><dc:subject>Lesezeit: 5 Minuten</dc:subject><dc:type>opinion_piece</dc:type></item><item><title>My 2,718281828459045235 Cents - State Of AI: Year 3 of the hype. Things I learned</title><link>https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/</link><pubDate>Fri, 01 Aug 2025 12:34:56 +0100</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/</guid><description>What about coding People don’t get tired of announcing that AI will replace developers. Mostly salespeople. Microsoft claims that AI is already writing 30–50% of their code. They also admit, that quality of C+ is behind expectations. In 2030 they plan to reach a rate of 95% , though human oversight …</description><content:encoded><![CDATA[<h2 id="what-about-coding">What about coding</h2>
<p>People don&rsquo;t get tired of announcing that AI will replace developers. Mostly salespeople. 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://www.techrepublic.com/article/news-microsoft-meta-code-written-by-ai/?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">Microsoft claims that AI is already writing 30–50%</a>


 of their code. They also admit, that quality of C+ is behind expectations. 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://www.xda-developers.com/microsofts-ceo-ai-wrote-30-percent-companys-code/?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">In 2030 they plan to reach a rate of 95%</a>


, though human oversight and authorship remains important. But they are silent about further details. <strong>I am not totally disagreeing</strong>, but I am pretty sure that there&rsquo;s still a bumpy road ahead. You may argue: Well, then you don&rsquo;t know how to properly operate AI. Problem usually sits in front of the screen, as they say. Fair enough. That&rsquo;s why I want to share some thoughts and experiences.</p>
<p align="center">
    <a href="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/copilot-inline-auto-completion.png" target="_blank" style="margin: 0 auto; display: block;">
        <img src="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/copilot-inline-auto-completion.png" alt="Copilot - inline auto completion" width="500"/>
    </a>
</p>
<center>Copilot - inline auto completion</center>
<br />
<br />
<p>AI is increasing my efficiency to a very high degree (I am referring to common LLM solutions here, but sometimes prefer the term AI, knowing that this can be misleading). But it also requires good skills as a developer. And if you don’t keep your head up, you lose more time &ldquo;playing around&rdquo; than you win by using AI.</p>
<p align="center">
    <a href="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/pile-of-unfinished-side-projects.png" target="_blank" style="margin: 0 auto; display: block;">
        <img src="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/pile-of-unfinished-side-projects.png" alt="Meme: Pile of unfinished side projects" width="500"/>
    </a>
</p>
<br />
<br />
<p>Speaking of playing around: These days I was just doing that. AI enables me to do this more frequently. It&rsquo;s so much easier to wrap your head around foreign ground. And it&rsquo;s fun, without crawling through endless Google search results or SO threads. So I am having this idea of a simple side project, it&rsquo;s one of those little things I get stuck on. And I was in need of an SVG showing a bike. I asked several LLM to generate one.</p>
<p>I am keeping it short. They all grandiosely failed.</p>
<p><strong>Gemini</strong> does not support SVG creation on its web interface. <strong>Claude</strong> does. And this was the first result: (I am sparing you the detailed prompts, basically I just asked for a simple bike SVG, no extensive prompt, follow-up prompts were quite brief, but detailed enough to get the point across)</p>
<p align="center">
    <a href="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/claude-svg-bike-try-1.png" target="_blank" style="margin: 0 auto; display: block;">
        <img src="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/claude-svg-bike-try-1.png" alt="Claude: SVG bike - try 1" width="500"/>
    </a>
</p>
<center>Claude: SVG bike - try 1</center>
<br />
<br />
<p>OK. Close.  What else you got?</p>
<p align="center">
    <a href="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/claude-svg-bike-try-2.png" target="_blank" style="margin: 0 auto; display: block;">
        <img src="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/claude-svg-bike-try-2.png" alt="Claude: SVG bike - try 2" width="500"/>
    </a>
</p>
<center>Claude: SVG bike - try 2</center>
<br />
<br />
<p>We are getting closer. But still not a bike.</p>
<p align="center">
    <a href="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/claude-svg-bike-try-3.png" target="_blank" style="margin: 0 auto; display: block;">
        <img src="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/claude-svg-bike-try-3.png" alt="Claude: SVG bike - try 3" width="500"/>
    </a>
</p>
<center>Claude: SVG bike - try 3</center>
<br />
<br />
<p>No. Last try?</p>
<p align="center">
    <a href="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/claude-svg-bike-try-4.png" target="_blank" style="margin: 0 auto; display: block;">
        <img src="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/claude-svg-bike-try-4.png" alt="Claude: SVG bike - try 4" width="500"/>
    </a>
</p>
<center>Claude: SVG bike - try 4</center>
<br /><br />
<p>Ok. Close. Grok. Your turn.</p>
<p align="center">
    <a href="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/grok-svg-bike-try-1.png" target="_blank" style="margin: 0 auto; display: block;">
        <img src="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/grok-svg-bike-try-1.png" alt="Grok: SVG bike - try 1" width="500"/>
    </a>
</p>
<center>Grok: SVG bike - try 1</center>
<br />
<br />
<p>Expressionism. ChatGPT. Last chance to hold the lance for AI.</p>
<p align="center">
    <a href="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/chatgpt-svg-bike-try-1.png" target="_blank" style="margin: 0 auto; display: block;">
        <img src="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/chatgpt-svg-bike-try-1.png" alt="ChatGPT: SVG bike - try 1" width="500"/>
    </a>
</p>
<center>ChatGPT: SVG bike - try 1</center>
<br /><br />
<p>What&rsquo;s that? You need a doctor?</p>
<p align="center">
    <a href="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/chatgpt-svg-bike-try-2.png" target="_blank" style="margin: 0 auto; display: block;">
        <img src="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/chatgpt-svg-bike-try-2.png" alt="ChatGPT: SVG bike - try 2" width="500"/>
    </a>
</p>
<center>ChatGPT: SVG bike - try 2</center>
<br /><br />
<p>Nope. Next try!</p>
<p align="center">
    <a href="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/chatgpt-svg-bike-try-3.png" target="_blank" style="margin: 0 auto; display: block;">
        <img src="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/chatgpt-svg-bike-try-3.png" alt="ChatGPT: SVG bike - try 3" width="500"/>
    </a>
</p>
<center>ChatGPT: SVG bike - try 3</center>
<br /><br />
<p>Yeah, that&rsquo;s impressive! But wait. That&rsquo;s not SVG. Caught you cheating. Use it as a template, try again!</p>
<p align="center">
    <a href="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/chatgpt-svg-bike-try-4.png" target="_blank" style="margin: 0 auto; display: block;">
        <img src="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/chatgpt-svg-bike-try-4.png" alt="ChatGPT: SVG bike - try 4" width="500"/>
    </a>
</p>
<center>ChatGPT: SVG bike - try 4</center
><br /><br />
<p>What?  Last try. You can do it!</p>
<p align="center">
    <a href="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/chatgpt-svg-bike-try-5.png" target="_blank" style="margin: 0 auto; display: block;">
        <img src="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/chatgpt-svg-bike-try-5.png" alt="ChatGPT: SVG bike - try 5" width="500"/>
    </a>
</p>
<center>ChatGPT: SVG bike - try 5</center>
<br /><br />
<p>Cheating. Again.</p>
<p><strong>OK. Unfair comparison.</strong> I know. This is clearly not about coding, but it makes a point. I am not blaming an LLM for not being able to create a simple bike SVG.  Especially because SVG creation is not really something a developer does and it&rsquo;s not what LLM are meant for in the first place. But it&rsquo;s a perfect metaphor for how the process quickly goes sideways:</p>
<blockquote>
<p>Sidenote: Recommended associated read: 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://simonwillison.net/2025/Aug/10/qwen3-4b/?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">Simon Willison had a similar journey with Qwen</a>


 at a much tinier scale, though.</p>
</blockquote>
<p>Those bots have an over-confident way of approaching things. They always try to flatter the user (I learned this wonderful word <strong>“sycophancy”</strong> these days – and forgot it the next day). And they rotate around problems. Did you notice how ChatGPT gets back to the pixelated image after one unsuccessful try?</p>
<p align="center">
  <a href="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/meme-sicophancy.png" target="_blank" style="margin: 0 auto; display: block;">
    <img src="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/meme-sicophancy.png" alt="Meme Sicophancy" width="500"/>
  </a>
</p>
<center>Sicophancy -  my second favorite word, first one is: Serendipity… hehe… pity!</center>
<br />
<br />
<p>While they are perfect in solving &ldquo;closed and recurring tasks&rdquo;, they suffer from the &ldquo;open-ended&rdquo; problem. [Microsoft just published a study about jobs that can best benefit from AI](And most of them are exactly this: Translator and interpreters.  Salespeople - easy, just train a model on product data and you are good. And so on. But a good developer has something that AI does not have (right now): Creativity and foresight.</p>
<p><em>(Don&rsquo;t get me wrong, I don&rsquo;t want to discredit the capabilities of those jobs!)</em></p>
<p align="center">
  <a href="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/microsoft-ai-study.png" target="_blank" style="margin: 0 auto; display: block;">
    <img src="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/microsoft-ai-study.png" alt="Microsoft AI Study: Applicability of AI to jobs" width="500"/>
  </a>
</p>
<center>Microsoft AI Study: Applicability of AI to jobs</center>
<br />  <br />
<p>Another devastating example where <strong>Gemini-Pro</strong> failed on a large scale: I asked for a simple Python script to download an XML sitemap and parse a couple of URLs from it. Please trust me when I say I did the prompting correctly. Also, Python and XML are not exactly an exotic combination. However, after 1 hour, over 80 prompts, and I-don&rsquo;t-know-how-many tokens, I gave up. Gemini wasn&rsquo;t able to deliver (I am using <code>gemini-cli</code> for that). It kept circling around the same problem, ignoring correction advice, and repeatedly promising, &ldquo;Now everything should work,&rdquo; many, many times. If you want to see more interesting examples of the mixed quality of LLMs at coding, 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://www.youtube.com/watch?v=uks8tggX4L0&amp;utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">check out this wonderful video</a>


.</p>
<p>If you are into <strong>troubleshooting</strong> or <strong>exploring</strong>, you may have experienced this: You ask the AI to fix a problem or explain how to achieve something. It will provide you with a solution. Which is usually quite overwhelming. You start implementing this &ldquo;pamphlet&rdquo; from step 1 to step 42 or so and at step 2 you get stuck.</p>
<p align="center">
  <a href="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/ai-troubleshooting-phenomenon.png" target="_blank" style="margin: 0 auto; display: block;">
    <img src="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/ai-troubleshooting-phenomenon.png" alt="AI troubleshooting deviation phenomenon" width="500"/>
  </a>
</p>
<center>AI troubleshooting deviation phenomenon</center>
<br />  <br />
<p>Why? The system you are working on returns an error. We have to rethink our solution process and feed that back to the LLM. <strong>We can&rsquo;t blame the machine</strong>, because the reason is usually: You did not provide enough context about the environment and circumstances. But it’s also important to say: How do you know what details are missing?</p>
<p>You can&rsquo;t. Especially in troubleshooting, it&rsquo;s part of the process. The process!</p>
<p align="center">
  <a href="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/meme-the-process.png" target="_blank" style="margin: 0 auto; display: block;">
    <img src="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/meme-the-process.png" alt="Meme: The Process" width="500"/>
  </a>
</p>
<br />  <br />
<p>The process of troubleshooting is not having a page of steps to follow and after 10 minutes you are done. If you are a system administrator, you know what troubleshooting means. It’s a complex <strong>forth and back, trial-and-error rocky road</strong>. You get better with time, that&rsquo;s what a good, seasoned, senior system administrator is: Someone who has seen a lot of problems and knows how to approach them.</p>
<p>Sure: AI has seen a lot of problems, too. <em>It&rsquo;s literally in their &ldquo;brains&rdquo; as they are trained on a lot of problems</em>. Still, what they haven’t learned yet is: guiding you through processes step by step. Or at least offering it! Instead: wasting 1,000 tokens on a 42-ish-step solution.</p>
<p>You may think it’s part of the business model: Why should the bot efficiently guide you through a process, if it can provide you with a 42-ish-step solution, which won’t work and then: Your context window is out of tokens. <strong>Purchase the pro plan or wait 6 hours to get this thing done.</strong></p>
<blockquote>
<p>I asked ChatGPT to look over my article, do a fact check. It claimed that this part sounds to conspicious and I should mention that this is due to model design and use, not malice. So, <strong>disclaimer</strong>: My goal is not to imply a business model behind this phenomenon. I&rsquo;m just kidding, okay?</p>
</blockquote>
<p align="center">
  <a href="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/meme-ai-solving-strategy.png" target="_blank" style="margin: 0 auto; display: block;">
    <img src="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/meme-ai-solving-strategy.png" alt="Meme: AI solving strategy" width="500"/>
  </a>
</p>
<br />  <br />
<p>Wich finally leads me to more than ranting and complaining with this word of advice: End every prompt like this. It will save you a lot of time and tokens:</p>
<p><strong>Guide me through the process step by step, don’t answer all at once, wait for my confirmation and feedback before you continue.</strong></p>
<p>But what about coding, at all? There are so many platforms offering perfect low-code or even no-code solutions. People telling you they built whole businesses without writing a single line of code.</p>
<p>Don&rsquo;t believe it? You tried it yourself? Try.</p>
<p>I am not saying that AI/LLM are not capable of e.g. writing code. They are - totally, unironically!</p>
<p>But <strong>&ldquo;real apps&rdquo; are complex</strong>. They are not just an HTML one-pager with a nice parallax effect, some neat JavaScript interactions and CSS. There are backends, data models, APIs, authentication, security, data storage and so on.</p>
<p>These days I was writing this little app that ought to visualize biking trips on a map. It grew a little out of hand. Starting with Python in a Jupyter notebook, I ended up with a full-blown web app. And you know what? I did it almost the &ldquo;vibe coding&rdquo; way. See? I am also one of those guys with this impressive stories! I only spent time thinking about user experience and features.</p>
<p>And at some point I thought: &ldquo;Hey buddy, what did you actually do there under the hood?&rdquo;</p>
<p align="center">
  <a href="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/meme-what-have-i-done.png" target="_blank" style="margin: 0 auto; display: block;">
    <img src="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/meme-what-have-i-done.png" alt="Image of the app" width="500"/>
  </a>
</p>
<br />  <br />
<p>Yes. AI was able to build this complex app (FWIW: It&rsquo;s <strong>Claude 4</strong> that right now provides best results for coding - not being paid for that).</p>
<p>I am using 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://gist.github.com/nickyreinert/bbce96ea043bc73cb011066e8a8df673?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">an extensive pre-prompt instructions file</a>


, telling the agent to use a structure of separated files, speaking function names and so on. Still I was facing a huge code base after some hours of &ldquo;vibe coding&rdquo;. The main app file was over <strong>5,000 lines</strong> of code. I reached a limit, where even the agent wasn’t really able to process all the context. It was a mess. Ok, blame me. My instructions probably weren’t that precise. &ldquo;The problem sits in front of the screen&rdquo;, as they say.</p>
<p>Well, we still can use the agent to refactor, can&rsquo;t we?</p>
<p><strong>Yes, we can</strong>. I did it. And it didn’t really work out. It was creating stubs and redundant functions and endpoints.</p>
<p>The end of the story: I had to crawl through the code myself. Like back in the days.  And it was not a difficult thing, I just spent two or three hours or so, strategically scraped through the codebase, identified redundant code, moved it out of the main function, cleaned up file structure. Like back in the days. Sure, with the help of AI, but this time only as a sidekick.</p>
<p>And this is what I am saying: You still need to be a good developer to understand code architecture and how code works. AI will not be able to deliver a fully fledged market-ready app for you. Not now.</p>
<p>However, this is not only a rant or my two cents, it&rsquo;s also about &ldquo;things I learned&rdquo;, so let’s summarize:</p>
<h3 id="seven-rules-of-aingagement">Seven Rules of AIngagement</h3>
<ol>
<li>Always include something like <strong>“step by step”</strong> in your prompts if you are about troubleshooting or exploring (always means: &ldquo;if applicalble&rdquo;)</li>
<li>Use a <strong>pre-prompt instructions</strong> file to guide the LLM/Coding Agent!</li>
<li><strong>Split the work!</strong> Use one agent to get the pre-prompt instructions that you feed to another agent instance, let another agent build you the first starting prompt, and so on - I&rsquo;ve established a mixed toolset, <strong>Copilot</strong> on one hand, <strong>Gemini-CLI</strong> on the other and some browser-based agents on the third hand</li>
<li>I also learned that it is really helpful when <strong>the agent maintains a “protocol”</strong> that contains the initial task and then, in very short bullet points, the current status of the task – this way you can always ask the agent to continue with the last task without having to re-explain everything</li>
<li>And this may not bring me many fans: Also separate tasks and codebases, to keep the context window small. This is the rise of some kind of <strong>microservices-ish architecture</strong>, because “monolithic” apps can&rsquo;t be eaten by the agent.</li>
<li>Discipline when it comes to version management. Let the LLM fix a thing, then commit the changes. <strong>Fix. Commit. Fix. Commit.</strong> You catch my drift.</li>
<li>Add a <strong>&ldquo;Got it? Any Questions?&rdquo;</strong> at the end of your prompt! You would be surprised how this changes whether the agent starts right away (remember: <strong>Sycophancy</strong>!) or gets back to you with some surprisingly important questions!</li>
</ol>
<p align="center">
  <a href="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/seven-rules-of-aingagement.png" target="_blank" style="margin: 0 auto; display: block;">
    <img src="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/seven-rules-of-aingagement.png" alt="Seven Rules Of Aingagement" width="500"/>
  </a>
</p>
<center>fancy self-made executive summary: Seven Rules Of AIngagement</center>
<br />
<br />
<h2 id="what-about-non-coding">What about &ldquo;non coding&rdquo;?</h2>
<p>Yeah, ok, this is a sweeping blow. I also have a opinion here.</p>
<p>Do you remember, back in the days, when you were &ldquo;Googling&rdquo; for information? First people started looking for the whole term, like &ldquo;how to build a web app with AI&rdquo;. But the Google algorithm was improving and so did the prompts. People reduced their search terms to &ldquo;web app AI&rdquo;. You don&rsquo;t necessarily need filler words to understand the intention of a search. And not only filler words, also spelling or grammar.</p>
<p>Right now the same happens to AI, apparently. Sometimes I catch myself typing prompts with so many errors and grammar mistakes. But it&rsquo;s doing its job. I wonder if this leads to &ldquo;degradation&rdquo; or better &ldquo;optimization&rdquo; of our language. Have you heard of 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://en.wikipedia.org/wiki/Gibberlink?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer"><strong>Gibberlink</strong></a>


 or 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://medium.com/@diegodotta/neuralese-the-most-spoken-language-youll-never-speak-a42522f68ff3?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer"><strong>Neuralese</strong></a>


? They are supposed to be &ldquo;languages&rdquo; or &ldquo;concepts&rdquo; that are used by AI to communicate with each other. Like <strong>more efficient versions</strong> of human languages, without all the noise and &ldquo;unnecessary&rdquo; words. As we are talking to AI more and more, what if, at some point, we adapt our language to the AI?</p>
<p>Second thing that will change is probably how we will use our &ldquo;computers&rdquo;. There are not many user interfaces right now. We have the <strong>GUI - the graphical user interface</strong>, it&rsquo;s the colorful windows and buttons you see on the screen. You can touch them or use your mouse cursor to select text. We have the <strong>TUI - text user interface</strong>, which is most common for CLI (command line interface) applications. You can type commands and see the output in text form. And we have the <strong>VUI - voice user interface</strong>, which is becoming more and more common with the rise of smart speakers and voice assistants. All of them have their strengths and weaknesses. Building a <strong>PowerPoint</strong> app from the command line is probably quite difficult (<em>side note: it&rsquo;s possible and it&rsquo;s fun to automate data analytics pipelines using Python libraries and create extensive PowerPoint slide sets!</em>). Grepping through multiple servers&rsquo; request logs using voice or a mouse? No one does it.</p>
<p><em>(Leaving out [brain computer interfaces](here, because they are still in the research phase and not widely used yet.)</em></p>
<p>There are exceptions, though. Do you know those movies where characters start typing commands to do simple tasks, like zooming in? They have the most expensive and advanced technologies, but somehow forgot to add a &ldquo;Zoom&rdquo; button, so they have to program this rare feature in real time—without even having a shell open. Absolute geniuses!</p>
<p align="center">
    <a href="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/zoom-by-commands.png" target="_blank" style="margin: 0 auto; display: block;">
        <img src="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/zoom-by-commands.png" alt="I am zooming in by typing on the keyboard" width="500"/>
    </a>
</p>
<center>"Let me quickly zoom in" - *starts typing on they keyboard (Scene from "SEALS Team", Paramount, 2017, S01E16</center>
<br />
<br />
<p>Right now, we are using AI in two ways: Most common ways are TUI and VUI. And this is interesting. Because in most cases this breaks the flow of work. You have to write long prompts, edit them, add more details. And after 5 minutes your context window is so full, you start all over again. Then you have to copy results back, interpret them, translate them into &ldquo;graphical&rdquo; interactions. And so on. Doesn&rsquo;t really sound like the peak of possible efficiency, does it?</p>
<p><em>(Good thing: You are forced to think about what you want to achieve. Which should be a no-brainer (even pre-AI): <strong>Think before you act!</strong> With AI it&rsquo;s even more important.)</em></p>
<p>Well, here comes the <strong>MCP</strong> into play - the <strong>Model Context Protocol</strong>, which connects the agent to other systems, like - sticking with this example - PowerPoint. You prompt your task, the AI controls PowerPoint and builds stuff. But at the end, you still need to read, type or speak your commands.</p>
<p>What we are missing is a <strong>GUI</strong> for AI. And as long as we don&rsquo;t have brain computer interfaces, we will find a way around this. Microsoft is trying to achieve a similar thing with 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://support.microsoft.com/de-de/windows/mit-recall-ihre-schritte-zur%C3%BCckverfolgen-aa03f8a0-a78b-4b3e-b0a1-2eb8ac48701c?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">their Recall feature</a>


, which raised a lot of anger and concerns about data privacy - which is totally understandable. Recording your screen, processing it on some server at some unknown location? Seriously?</p>
<p>But this will be the future. Because this allows reactive graphical user interfaces and I am not saying dynamic ones. Thinking of a user interface that changes because of some previous actions - that doesn&rsquo;t sound very useful, right? Imagine your car changes the functions of your brake and accelerator pedal. It&rsquo;s more an interface that &ldquo;follows&rdquo; the user, not makes the user follow the interface. Form follows function.</p>
<p>Sounds bold, I know. But do you really think that we will still push tons of words into browser windows in 10, 20 years?</p>
<p align="center">
  <a href="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/meme-ai-outsourcing.png" target="_blank" style="margin: 0 auto; display: block;">
    <img src="https://nickyreinert.de/en/2025/2025-08-01-mtc---state-of-ai/meme-ai-outsourcing.png" alt="Meme: AI outsourcing" width="500"/>
  </a>
</p>
<center>Hopefully my boss doesn't read this</center>
<br />
<br />
]]></content:encoded><atom:updated>2026-06-16T20:56:44+02:00</atom:updated><category>blog</category><dc:subject>Lesezeit: 5 Minuten</dc:subject><dc:type>essay</dc:type></item><item><title>Monitor SSL Traffic On Android</title><link>https://nickyreinert.de/en/2025/2025-07-01-monitor-ssl-traffic-on-android/</link><pubDate>Tue, 01 Jul 2025 12:34:56 +0100</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/en/2025/2025-07-01-monitor-ssl-traffic-on-android/</guid><description>Fantastic Preface Monitoring SSL Traffic of Android Apps is quite a challenge these days. While it’s an important way to understand how Apps work, find security flaws or possible data breaches, a weak encrypted traffic also is a potential security risk.
These days, most apps use a thing called …</description><content:encoded><![CDATA[<h2 id="fantastic-preface">Fantastic Preface</h2>
<p>Monitoring SSL Traffic of Android Apps is quite a challenge these days. While it&rsquo;s an important way to understand how Apps work, find security flaws or possible data breaches, a weak encrypted traffic also is a potential security risk.</p>
<p>These days, most apps use a thing called &ldquo;certificate pinning&rdquo;, which makes it quite difficult to monitor traffic. You need a rooted device, to bypass certificate pinning. However, this leads to a cascade of challenges and you easily get sucked into a rabbit hole with obstacles, turning points and furstrating dead ends.</p>
<p>&ldquo;Lukily there&rsquo;s AI guiding me through the process!&rdquo; You may think. Well. No.</p>
<p>Gemini requires arm-twisting to make you help:</p>
<blockquote>
<p>I cannot provide a step-by-step guide on how to bypass certi&quot;ficate pinning on a rooted Android device for malicious or unethical purposes. My purpose is to be helpful and harmless, and assisting in such activities falls outside of my ethical guidelines.</p>
</blockquote>
<p>ChatpGPT is a little too helpful. it&rsquo;s providing you with a overwhelming large emoticion spiked list of 20-or-so steps. Usually you stuck at step 5 and there the chaos begins (like this article, which is a bit contradictory, right?).</p>
<blockquote>
<p>Note of advice: That&rsquo;s common behavior for most fo the agents. In cases like that, your prompt should always contain an instruct ion like&quot; Guide me through the process stepy by step, dont answer all at once, but wait for my confirmation to continue.&quot;</p>
</blockquote>
<h2 id="preperations">Preperations</h2>
<p>Let&rsquo;s assume we are working on <strong>Window</strong> and having <strong>Android Studio</strong> up and running, including the propper <strong>Android SDK</strong>. So lets start with setting up a virtual Android device <strong>wihtouth Google Play</strong>. Google Play-enabled devices cannot easly be rooted. Let&rsquo;s drive with the <strong>Pixel 6 Pro</strong> and <strong>Intel x86_64</strong> (you may choose a different image, just make sure it matches you host&rsquo;s hardware setup).</p>
<p align="center">
  <a href="https://nickyreinert.de/en/2025/2025-07-01-monitor-ssl-traffic-on-android/android-studio-device-selection.png">
    <img src="https://nickyreinert.de/en/2025/2025-07-01-monitor-ssl-traffic-on-android/android-studio-device-selection.png" alt="Android Studio - setting up virtual device" width="400"/>
  </a>
</p>
<p align="center">
    <a href="https://nickyreinert.de/en/2025/2025-07-01-monitor-ssl-traffic-on-android/android-studio-system-image-selection.png">
      <img src="https://nickyreinert.de/en/2025/2025-07-01-monitor-ssl-traffic-on-android/android-studio-system-image-selection.png" alt="Android Studio - selecting system image" width="400"/>
    </a>
</p>
<h2 id="app-installation">App Installation</h2>
<p>As we dont have the Google Play store at hand, we need to find way around. There are two options. The easiest one: Use a service/plattform like <strong>apkmirror.com</strong> or <strong>uptodown.com</strong>. I prefer the first one, as uptodown comes with a wrapper that - once installed on the device - installs the actual APK.</p>
<p>The second way is to use a device that has the app installed, either a virtual one or your actual phone, and pull the APK from there.</p>
<p>We will enable developing mode and USB debugging on our <em>source device</em> and connnect it to our host system. Using allmighty <strong>Android Debugging Bridge</strong> <code>adb</code> we can pull the APK from the device. First make sure your device is connected and recognized by adb:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="nb">cd</span> /AndroidSDK/platform-tools <span class="c1"># your Androd SDK may be in a different location!</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">adb devices <span class="c1"># to check if your phone is connected</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">adb shell list pm packages -f <span class="p">|</span> grep Foobar <span class="c1"># to list all installed apps with their APK path and filter by your app&#39;s name</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">adb pull /data/app/com.Foobar/base.apk <span class="c1"># to pull the APK from your device</span>
</span></span></code></pre></div><p>These days most apps are splitted into different APK files (for different purposes like screen size, language, etc.). We  need to pull all APK&rsquo;s. Let&rsquo;s first find out the APK path of our app:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">adb shell pm path de.foobar.app.mobile
</span></span></code></pre></div><p>This will probably return a list like</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">package:/data/app/de.foobar.app.mobile-1/base.apk
</span></span><span class="line"><span class="ln">2</span><span class="cl">package:/data/app/de.foobar.app.mobile-1/split_config.en.apk
</span></span><span class="line"><span class="ln">3</span><span class="cl">package:/data/app/de.foobar.app.mobile-1/split_config.xxhdpi.apk
</span></span></code></pre></div><p>We need to ´adb pull` all of them one by one.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">adb install -r de.foobar.app.mobile <span class="c1"># if we have one APK only</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">adb install-multiple -r base.apk split_config.en.apk split_config.xxhdpi.apk <span class="c1"># if we have multiple APKs</span>
</span></span></code></pre></div><p>That&rsquo;s it!</p>
<h2 id="rooting-the-virtual-device">Rooting the Virtual Device</h2>
<p>Now lets prepare your virtual device, which is usually a quick step. First, apparantly, disconnect your source device from your host. Then start the virtual device in Android Studio:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="nb">cd</span> /AndroidSDK/emulator
</span></span><span class="line"><span class="ln">2</span><span class="cl">emulator -list-avds <span class="c1"># optional: list all available virtual devices</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">emulator -avd AndroidRoot <span class="c1"># that&#39;s the name of the virtual device, we created earlier</span>
</span></span></code></pre></div><p>This will take some seconds. Once done, we need to make sure the device is rooted:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">adb root
</span></span></code></pre></div><p align="center">
  <a href="https://nickyreinert.de/en/2025/2025-07-01-monitor-ssl-traffic-on-android/adb-enable-root.png">
    <img src="https://nickyreinert.de/en/2025/2025-07-01-monitor-ssl-traffic-on-android/adb-enable-root.png" alt="ADB - Enable root" width="400"/>
  </a>
</p>
<p>We can test the success of this command by checking the device&rsquo;s shell:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">adb shell
</span></span></code></pre></div><p>On the device check the output of these commands:</p>
<p align="center">
  <a href="https://nickyreinert.de/en/2025/2025-07-01-monitor-ssl-traffic-on-android/adb-shell-check-su.png">
    <img src="https://nickyreinert.de/en/2025/2025-07-01-monitor-ssl-traffic-on-android/adb-shell-check-su.png" alt="ADB Shell - Check Superuser" width="400"/>
  </a>
<p>We are in! For our monitoring we need to enable the proxy on the device. This is done by setting the proxy settings in the device&rsquo;s network settings.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">adb shell settings put global http_proxy 10.0.2.2:8080
</span></span></code></pre></div><p>(You may reset the proxy setting with <code>adb shell settings put global http_proxy :0</code>)</p>
<h2 id="building-the-monitoring-stack">Building the monitoring stack</h2>
<p>Here comes the challenging part. Here&rsquo;s the rough process:</p>
<p>We will need four different terminal windows</p>
<ul>
<li>one is running the Android emulator (if you start it from the console)</li>
<li>one is running the monitoring tool (MITMProxy) on the host</li>
<li>one is running the Frida server on the device</li>
<li>one is running the Frida tool on the host, injecting a script into the app</li>
</ul>
<h3 id="installing-and-running-the-monitoring-tool">Installing and running the monitoring tool</h3>
<p>This is the easiest part. We use 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://mitmproxy.org/?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">MITMProxy</a>


 to listen to the HTTP traffic coming from the device. We can use the <strong>Python&rsquo;ic</strong> way, if you prefer you may use <code>brew</code> or the installer form their website.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">pip install mitmproxy
</span></span></code></pre></div><p>Done. Now run it and see what happens:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">mitmproxy --listen-port 8080
</span></span></code></pre></div><p>If you&rsquo;re happy, you already see HTTP traffic coming through and probably some warnings on the bottom, telling you that - of course - https traffic is not decryptable.</p>
<p>(Screenshot already shows HTTPS traffic, but you will see only HTTP traffic at first)</p>
<p align="center">
  <a href="https://nickyreinert.de/en/2025/2025-07-01-monitor-ssl-traffic-on-android/mitm-https-traffic.png">
    <img src="https://nickyreinert.de/en/2025/2025-07-01-monitor-ssl-traffic-on-android/mitm-https-traffic.png" alt="MITMProxy - HTTP Traffic" width="400"/>
  </a>
</p>
<h2 id="installing-and-running-the-frida-server">Installing and running the Frida server</h2>
<p>Now we will install the <em>Frida Server</em> on the device. 




  <a href="frida.re">Frida</a>

 - in short - is a tooblxo for developers to debug or monitor apps. You can get the latest version 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://github.com/frida/frida/releases?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">from their GitHub repo</a>


. Make sure to download the server package for Android and the correct chipset, like in my case <code>frida-server-17.2.14-android-x86_64.xz</code>. Extract its content which will get you a binary file that you push to the virtual android device:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">adb push frida-server-17.2.14-android-x86_64 /data/local/tmp/frida-server
</span></span></code></pre></div><p>Make it executable:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">adb shell chmod <span class="m">755</span> /data/local/tmp/frida-server
</span></span></code></pre></div><p align="center">
  <a href="https://nickyreinert.de/en/2025/2025-07-01-monitor-ssl-traffic-on-android/frida-installing-server.png">
    <img src="https://nickyreinert.de/en/2025/2025-07-01-monitor-ssl-traffic-on-android/frida-installing-server.png" alt="Frida Server - Installing" width="700"/>
  </a>
</p>
<p>No <em>shell into</em> the device and run the server (the <code>&amp;</code> at the end makes it running in the background):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">adb shell
</span></span><span class="line"><span class="ln">2</span><span class="cl">emu64xa:/data/local/tmp/frida-server <span class="p">&amp;</span>
</span></span></code></pre></div><p align="center">
  <a href="https://nickyreinert.de/en/2025/2025-07-01-monitor-ssl-traffic-on-android/frida-running-server.png">
    <img src="https://nickyreinert.de/en/2025/2025-07-01-monitor-ssl-traffic-on-android/frida-running-server.png" alt="Frida Server - Running" width="700"/>
  </a>
</p>
<h2 id="injecting-the-script">Injecting &ldquo;The Script&rdquo;</h2>
<p>We also need to install the <strong>Frida-toolbox</strong> on our host machine. There are several ways, we stick with the <strong>Python&rsquo;ic</strong>-way:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">pip install frida-tools
</span></span></code></pre></div><p>Not much to explain here. After you done, we need to find a script that we &ldquo;inject&rdquo;. This script will circumvent the certificate pinning of the app. There are many scripts out in the wild and you can even ask AI to write you one. I am using this one:</p>
<p>`





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://gist.github.com/akabe1/5632cbc1cd49f0237cbd0a93bc8e4452%60%60?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">https://gist.github.com/akabe1/5632cbc1cd49f0237cbd0a93bc8e4452``</a>


</p>
<p>Download it to a nicer name like <code>frida_multiple_unpinning.js</code>.</p>
<p>Now we tell Frida on our host to run the app we installed earlier and inject th unpinnign script:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">frida -U -f com.Foobar.apk -l frida_multiple_unpinning.js
</span></span></code></pre></div><p>That&rsquo;s it! Now switch to your terminal window running the MITMProxy and you should see a lot of traffic coming through!</p>
<p>Some times, some apps, some configuration will see that there&rsquo;s something going on with their traffic. Sometimes the script will fail, the app will crash or not work at all. You need a little bit of <strong>trial and error</strong> to find the right unpinning script for your app.</p>
<p>Besides, you can pre-compile your script, which sometimes helps to avoid detection.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">frida-compile frida_multiple_unpinning.js -o compiled.py
</span></span><span class="line"><span class="ln">2</span><span class="cl">frida -U -f com.Foobar.apk -l compiled.py
</span></span></code></pre></div><h1 id="round-up">Round up</h1>
<ul>
<li>make sure you are root</li>
<li>install the APK on the device using <code>adb</code></li>
<li>start the Frida server on the device</li>
<li>start the proxy on the host</li>
<li>optional: use adb</li>
<li>start the app on the device using Frida and inject the script (<strong>make sure</strong> the app is not running, or you get error <code>Permission denied</code>)</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="nb">cd</span> /AndroidSDK/emulator
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">emulator -avd AndroidRoot
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="nb">cd</span> /AndroidSDK/platform-tools
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">adb root
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">adb install -r com.yourApp.apk
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">mitmproxy --listen-port <span class="m">8080</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">adb shell
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">$ /data/local/tmp/frida-server <span class="p">&amp;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># back on the host</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">adb shell pm list packages -3
</span></span><span class="line"><span class="ln">11</span><span class="cl">frida -U -f com.yourApp -l frida_multiple_unpinning.js
</span></span></code></pre></div>]]></content:encoded><atom:updated>2026-06-16T20:56:44+02:00</atom:updated><category>anleitungen</category><dc:subject>Lesezeit: 5 Minuten</dc:subject><dc:type>technical_tutorial</dc:type></item><item><title>Adobe Launch DTM Naming Conventions</title><link>https://nickyreinert.de/en/2025/2025-04-01-adobe-launch-dtm-naming-conventions/</link><pubDate>Tue, 01 Apr 2025 12:34:56 +0200</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/en/2025/2025-04-01-adobe-launch-dtm-naming-conventions/</guid><description>I’ve worked with Adobe Tracking Suite (which is Adobe Launch and all it’s sibblings) for quite a while and I saw many, some quite chaotic, tracking implementations and tag managers. At some point I felt the need to write down some basic rules to navigate those messy libraries. Hope that helps you, …</description><content:encoded><![CDATA[<p>I&rsquo;ve worked with Adobe Tracking Suite (which is Adobe Launch and all it&rsquo;s sibblings) for quite a while and I saw many, some quite chaotic, tracking implementations and tag managers. At some point I felt the need to write down some basic rules to navigate those messy libraries. Hope that helps you, too.</p>
<h1 id="naming-convention">naming convention</h1>
<h2 id="global">Global</h2>
<p>Use a category and naming convention that allows you to track <strong>variables</strong>, <strong>metrics</strong> and <strong>dimension</strong> from the website over the tag manger to the backend of your analytics solution and the dashboard. Every category is also assigned a particular numeric range. This will improve your debugging and troubleshooting process.</p>
<p>The following basic categories should represent your default setup.</p>
<ul>
<li>Page related information (for a single page view, from 1 to 19)</li>
<li>Site related information (for the whole website, from 020 to 39)</li>
<li>User related information (describing the visitor, from 40 to 59)</li>
<li>Misc information (meta information that usually is not part of an analysis or cannot be assigned to the other categories, from 59 to</li>
</ul>
<h2 id="data-layer">Data Layer</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="ln"> 1</span><span class="cl">	<span class="nx">datalayer</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">		<span class="nx">page</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">			<span class="nx">category</span><span class="o">:</span> 
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">			<span class="nx">name</span><span class="o">:</span> 
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">			<span class="nx">id</span><span class="o">:</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">			<span class="nx">date</span><span class="o">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">			<span class="nx">language</span><span class="o">:</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">		<span class="p">},</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">		<span class="nx">site</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">			<span class="nx">provider</span><span class="o">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">			<span class="nx">project</span><span class="o">:</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">			<span class="nx">domain</span><span class="o">:</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">		<span class="p">},</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">		<span class="nx">user</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">			<span class="nx">status</span><span class="o">:</span> <span class="p">[</span><span class="nx">anonmyous</span><span class="p">,</span> <span class="nx">logged</span><span class="o">-</span><span class="k">in</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">			<span class="nx">behavior</span><span class="o">:</span> <span class="p">[</span><span class="nx">returned</span><span class="p">,</span> <span class="k">new</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">			<span class="nx">language</span><span class="o">:</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">		<span class="p">},</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">		<span class="nx">misc</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">			<span class="nx">timestamp</span><span class="o">:</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">			<span class="nx">debug</span><span class="o">:</span> 
</span></span><span class="line"><span class="ln">22</span><span class="cl">			<span class="nx">utm</span><span class="o">:</span> 
</span></span><span class="line"><span class="ln">23</span><span class="cl">			<span class="nx">interaction_details</span><span class="o">:</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">	<span class="p">}</span>
</span></span></code></pre></div><blockquote>
<p>&ldquo;I&rsquo;ve implemented a page.category attribute, which I find invaluable. I&rsquo;m surprised so many implementations omit it. It allows for quicker traffic analysis at a higher level than individual pages. Categories help maintain order as pages inevitably become disorganized over time.&rdquo;</p>
</blockquote>
<h2 id="data-elements">Data Elements</h2>
<p>Your Data Element name should consist the numeric ID referring to the ID of your (Adobe) Analytics-implementation followed by a separator (like the pipe symbol |) and the address within the Data Layer object. Left pad the ID to a length of three to not mix up the &ldquo;order by name&rdquo; feature. The second part is the exact address of the element within the data layer. If this element does not correspond to the Data Layer, use a short description.</p>
<ul>
<li><code>000 | datalayer.page.name</code></li>
<li><code>010 | datalayer.misc.utm</code></li>
</ul>
<h2 id="dimensions">Dimensions</h2>
<p><strong>eVars</strong>, <strong>props</strong> and <strong>events</strong> are identified by a short description build from the category and the name of the information, followed by the name and the kind of variable (<strong>t</strong> for <strong>traffic variables</strong> aka <strong>props</strong>, <strong>c</strong> vor <strong>conversion variables</strong> aka <strong>eVars</strong> and <strong>e</strong> for <strong>event</strong>):</p>
<ul>
<li><code>Page Name (t010)</code></li>
<li><code>User Status (c020)</code></li>
</ul>
<h2 id="events">Events</h2>
<h3 id="naming-rules">Naming Rules</h3>
<p>You wil need three categories of rules, based on the action the perform</p>
<ul>
<li>Page View rule (mostly just one, will trigger a page view)</li>
<li>Click rules (will trigger click events)</li>
<li>Misc rule (will not trigger any call, but prepare tracking or add event listeners)</li>
</ul>
<p>Besides that, you will have three main attributes for page view and click rules that should be available in the component name itself:</p>
<ul>
<li>the according event id (custom success event, even page views can have an event ID)</li>
<li>a brief description</li>
<li>how a rule will be triggered (Page Top, Document Ready, Click, Direct Call, …)</li>
</ul>
<p>Name your rules using those three information (I prefer the pipe symbol as a separator)</p>
<p><code>[TYPE]|[TRIGGER]|[EVENT ID]|[BRIEF DESCRIPTION]</code></p>
<p>If your rule covers more than one event, post them all as a comma separated list (50,51,52). If there are too many rules, you may overthink your strategy.</p>
<p><strong>For example:</strong></p>
<ul>
<li><code>PV | DocReady | 10 | All Pages</code></li>
<li><code>PV | DocReady | 20 | Search Pages</code></li>
<li><code>CL | Trigger | 50,51,52 | Click</code></li>
<li><code>CL | Click | 60 | Bookmark Page</code></li>
<li><code>MISC | Helper Functions</code></li>
</ul>
<h4 id="reason">Reason</h4>
<p>In the rules overview this helps you to get a good first impression of all existing rules and it allows you to quickly filter the list for wanted rule types. Also, if you lookup a particular event, you quickly see the particular component that somehow works for this event.</p>
<h3 id="naming-components">Naming Components</h3>
<p>A component can be a trigger/event, condition or action. When you create a component, Adobe suggests a default name. Never use this name. The name should start with the exact sequence you defined for the rule name.</p>
<p><strong>For example:</strong></p>
<p>You have a rule to trigger page views:</p>
<p><code>PV | DocReady | 10 | All Pages</code></p>
<p>This is how you name the components of the rule</p>
<ul>
<li>trigger/event: <code>PV | DocReady | 10 | All Pages |  page top</code></li>
<li>condition: <code>PV | DocReady | 10 | All Pages |  user is logged in</code></li>
<li>action: <code>PV | DocReady | 10 | All Pages |  set variables</code></li>
<li>action: <code>PV | DocReady | 10 | All Pages |  send beacon</code></li>
<li>action: <code>PV | DocReady | 10 | All Pages |  clear variables</code></li>
</ul>
<h4 id="reason-1">Reason</h4>
<p>If you look up a particular event ID, you quickly see all according components. If you look up particular variables that belong to different events, you know quickly identify, where this variable is being used, without clicking into the component itself.</p>
<h3 id="error-handling">Error Handling</h3>
<p>If you have custom code, you should always implement try/catch error handling that also refers to the parent rule:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">let rule_name = &#39;`PV | DocReady | 10 | All Pages&#39;;
</span></span><span class="line"><span class="ln">2</span><span class="cl">try {
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl">} catch (e) {
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl">	_satellite.logger.error(rule_name + &#39; | &#39; + e);
</span></span><span class="line"><span class="ln">8</span><span class="cl">
</span></span><span class="line"><span class="ln">9</span><span class="cl">}
</span></span></code></pre></div><p>If your code grows and get more complex, you should and can, add multiple try/catch lines into your code. In this case you should add a brief description of the current area to the error message:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln"> 1</span><span class="cl">let rule_name = &#39;`PV | DocReady | 10 | All Pages&#39;;
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">try {
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">} catch (e) {
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">	_satellite.logger.error(rule_name + &#39; | init something | &#39; + e);
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">}
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl">try {
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">} catch (e) {
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">	_satellite.logger.error(rule_name + &#39; | calculate another metric | &#39; + e);
</span></span><span class="line"><span class="ln">18</span><span class="cl">
</span></span><span class="line"><span class="ln">19</span><span class="cl">}
</span></span></code></pre></div><h4 id="reason-2">Reason</h4>
<p>Now, if you face errors in the debug console, it&rsquo;s easier to find the source, even if you handle long code blocks. You don&rsquo;t need to click through the stack trace.</p>
<h3 id="logging-and-documentation">Logging and Documentation</h3>
<p>The same goes for logging. Always logg what you are doing. I don&rsquo;t need to mention, how important inline documentation is. ;)
Logging works the same as error handling:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">_satellite.logger.info(rule_name + &#39; | calculate another metric&#39;);
</span></span></code></pre></div><h4 id="reason-3">Reason</h4>
<p>Now, if you filter the browser console, it&rsquo;s easier to get rid of the noise and debug a problem.</p>
<h3 id="inactive-rules-or-data-elements">Inactive Rules or Data Elements</h3>
<p>You will and cannot avoid to have inactive/disabled rules. You should keep that amount low, because they pollute your library. If you have inactive rules, add a prefix &ldquo;INACTIVE&rdquo; to the rules name and, preferably also to it&rsquo;s components.</p>
<h4 id="reason-4">Reason</h4>
<p>If you look up code parts, data elements or anything, the search will return you rule and component names. From those component names you will not see, if a result belongs to an inactive rule. The prefix INACTIVE will help you to quickly identify those results.</p>
]]></content:encoded><atom:updated>2026-06-16T20:56:44+02:00</atom:updated><category>blog</category><dc:subject>Lesezeit: 5 Minuten</dc:subject><dc:type>guide</dc:type></item><item><title>How to develop in 2025: Improve your Workflow with VS Code, GitHub &amp; Copilot</title><link>https://nickyreinert.de/en/2025/2025-03-19-developing-vscode/</link><pubDate>Wed, 19 Mar 2025 12:34:56 +0200</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/en/2025/2025-03-19-developing-vscode/</guid><description>VS Code Workspace Settings Shortcuts First of all, I strongly suggest adding some custom shortcuts because the default ones are a bit unintuitive. For example, CTRL + SHIFT + ' gets you to the terminal, while SHIFT + FN + F6 brings you back to the editor, andSHIFT + B opens the sidebar. What a mess! …</description><content:encoded><![CDATA[<h2 id="vs-code-workspace-settings">VS Code Workspace Settings</h2>
<h3 id="shortcuts">Shortcuts</h3>
<p>First of all, I strongly suggest adding some custom shortcuts because the default ones are a bit unintuitive. For example, <code>CTRL + SHIFT + '</code> gets you to the terminal, while <code> SHIFT + FN + F6</code> brings you back to the editor, and<code>SHIFT + B</code> opens the sidebar. What a mess! A more consistent setup can definitely improve your workflow.</p>
<p>To customize your shortcuts, press <code>CMD + K</code> followed by <code>CMD + S</code> or look up &ldquo;Keybindings&rdquo; in the command palette (<code>SHIFT + CMD + P</code>). There&rsquo;s a small icon on the right to open the actual JSON file, which makes configuration much easier. What about  jumpin between the sidebar, editor, and terminal using the arrow keys while holding <code>CTRL + SHIFT</code>?</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="p">[</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">        <span class="nt">&#34;key&#34;</span><span class="p">:</span> <span class="s2">&#34;ctrl+shift+down&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;workbench.action.terminal.focus&#34;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="nt">&#34;key&#34;</span><span class="p">:</span> <span class="s2">&#34;ctrl+shift+up&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;workbench.action.focusActiveEditorGroup&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="nt">&#34;key&#34;</span><span class="p">:</span> <span class="s2">&#34;ctrl+shift+left&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;workbench.action.focusActivityBar&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="p">]</span>
</span></span></code></pre></div><p>Add more shortcuts to make your mouse-less life easier. But thats not all, I also suggest to use a tool like 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://espanso.org/?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">Espanso</a>


! It&rsquo;s a very powerfull <em>text expander</em> with two main interesting features:</p>
<ul>
<li>if you start typing certin chars, it will automaticall expand them to something you configured</li>
<li>it also provides a quick search feature that you can trigger with <code>OPTION + SPACE</code></li>
</ul>
<p>So here&rsquo;s my strategy, let&rsquo;s take some Python commands for example. I am not using unique trigger keywords, but the same one to define particular categories (





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://gist.github.com/nickyreinert/91e67a7d054878338d1f1dc9fc592ae5?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">see a full example at this Gist</a>


)</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln">1</span><span class="cl"><span class="nt">matches</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w">  </span>- <span class="nt">trigger</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#p1&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w">    </span><span class="nt">replace</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;source .venv/bin/activate&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="w">  </span>- <span class="nt">trigger</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#p&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="w">    </span><span class="nt">replace</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;python3 -m venv ./.venv&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="w">  </span>- <span class="nt">trigger</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#p&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="w">    </span><span class="nt">replace</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;pip3 install -r requirements.txt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="w">  </span>- <span class="nt">trigger</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;#p&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="w">    </span><span class="nt">replace</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;pip3 freeze &gt; requirements.tx&#34;</span><span class="w">
</span></span></span></code></pre></div><p>When I open the quick search bar, I type <code>#p</code> to get all Python commands. Ofcourse you could also add unique trigger keywords, I prefer it like that because I don&rsquo;t remember all those keywords anywawys. Now I can just show quickly browse through commands for a given category, like JavaScript, Python, C# - you name it!</p>
<h3 id="containers">Containers</h3>
<blockquote>
<p>Warning: Requires Docker!</p>
</blockquote>
<p>Next, let&rsquo;s talk about containers! I strongly recommend using containers for your projects. Not all of them, of course, sometimes a virtual environment is totally sufficient. But containers! Oh boy! They are quick, easy, and help keep your system clean. VS Code allows you to set up dev containers—a closed ecosystem where you can configure your development environment. This is especially useful when switching between different languages, frameworks, or systems.</p>
<p>For example, if you want to test Ruby without installing all the dependencies on your system, create a <code>.devcontainer</code> folder within your workspace and place two files in it: <code>devcontainer.json</code> and <code>Dockerfile</code>.</p>
<h4 id="example-setup-for-ruby">Example Setup for Ruby:</h4>
<h5 id="dockerfile"><code>Dockerfile</code></h5>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Dockerfile" data-lang="Dockerfile"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">FROM</span><span class="w"> </span><span class="s">ruby:3.2</span><span class="err">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">ENV</span> <span class="nv">LANG</span><span class="o">=</span>C.UTF-8<span class="err">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">ENV</span> <span class="nv">DEBIAN_FRONTEND</span><span class="o">=</span>noninteractive
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">RUN</span> apt-get update <span class="o">&amp;&amp;</span> apt-get install -y build-essential git curl zlib1g-dev <span class="se">\
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl">    libssl-dev libreadline-dev libyaml-dev libsqlite3-dev sqlite3 nodejs <span class="o">&amp;&amp;</span> <span class="se">\
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl">    rm -rf /var/lib/apt/lists/*<span class="err">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">ARG</span> <span class="nv">USERNAME</span><span class="o">=</span>vscode
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">ARG</span> <span class="nv">USER_UID</span><span class="o">=</span><span class="m">1000</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">ARG</span> <span class="nv">USER_GID</span><span class="o">=</span><span class="nv">$USER_UID</span><span class="err">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">RUN</span> groupadd --gid <span class="nv">$USER_GID</span> <span class="nv">$USERNAME</span><span class="err">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="k">RUN</span> useradd --uid <span class="nv">$USER_UID</span> --gid <span class="nv">$USER_GID</span> -m <span class="nv">$USERNAME</span><span class="err">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">USER</span><span class="w"> </span><span class="s">$USERNAME</span><span class="err">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">WORKDIR</span><span class="w"> </span><span class="s">/workspace</span><span class="err">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="k">RUN</span> gem install bundler<span class="err">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">EXPOSE</span><span class="w"> </span><span class="s">3000</span><span class="err">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">CMD</span> <span class="p">[</span><span class="s2">&#34;irb&#34;</span><span class="p">]</span><span class="err">
</span></span></span></code></pre></div><h5 id="devcontainerjson"><code>devcontainer.json</code></h5>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;Ruby Development&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="nt">&#34;build&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        <span class="nt">&#34;dockerfile&#34;</span><span class="p">:</span> <span class="s2">&#34;Dockerfile&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="nt">&#34;args&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">            <span class="nt">&#34;USER_GID&#34;</span><span class="p">:</span> <span class="s2">&#34;1000&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">            <span class="nt">&#34;USER_UID&#34;</span><span class="p">:</span> <span class="s2">&#34;1000&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">            <span class="nt">&#34;USERNAME&#34;</span><span class="p">:</span> <span class="s2">&#34;vscode&#34;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="nt">&#34;settings&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        <span class="nt">&#34;terminal.integrated.defaultProfile.linux&#34;</span><span class="p">:</span> <span class="s2">&#34;bash&#34;</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="nt">&#34;extensions&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="s2">&#34;rebornix.ruby&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="s2">&#34;castwide.solargraph&#34;</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="p">],</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="nt">&#34;postCreateCommand&#34;</span><span class="p">:</span> <span class="s2">&#34;bundle install&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="nt">&#34;remoteUser&#34;</span><span class="p">:</span> <span class="s2">&#34;vscode&#34;</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Save everything, and VS Code will prompt you to create and open the dev container—no need to manage Docker manually! If you don&rsquo;t see the message, open the &ldquo;Remote&rdquo; container manually by clicking the blue icon in the bottom left corner or using the shortcut <code>OPT + COMMAND + O</code>.</p>
<hr>
<h3 id="git">Git</h3>
<p>You don&rsquo;t necessarily need a GitHub account to store repositories remotely, but I encourage initializing a repository locally. This helps you track changes and maintain your work efficiently.</p>
<h4 id="configuring-multiple-github-accounts">Configuring Multiple GitHub Accounts</h4>
<p>If you use different GitHub accounts (e.g., for personal and professional projects), modify your Git config file:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">[user]</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="na">name</span> <span class="o">=</span> <span class="s">John Doe
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="s">    email = john.doe@example.com</span>
</span></span></code></pre></div><h4 id="branching-strategy">Branching Strategy</h4>
<p>If you don&rsquo;t use Git daily, consider a simple branch strategy:</p>
<ol>
<li>Use the <code>main</code> branch for stable code.</li>
<li>Use a <code>debug</code> branch for hotfixes or minor fixes.</li>
<li>Use a <code>dev</code> branch for new features.</li>
</ol>
<h5 id="example-workflow">Example Workflow:</h5>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="ln">1</span><span class="cl">git checkout dev
</span></span><span class="line"><span class="ln">2</span><span class="cl">git pull origin main
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="c1"># Make changes</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">git commit -m <span class="s2">&#34;Your commit message&#34;</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">git push origin dev
</span></span></code></pre></div><p>Once you&rsquo;re ready to merge changes:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="ln">1</span><span class="cl">git checkout main
</span></span><span class="line"><span class="ln">2</span><span class="cl">git merge dev
</span></span><span class="line"><span class="ln">3</span><span class="cl">git push origin main
</span></span></code></pre></div><p>This keeps your codebase organized and reduces conflicts. If needed, Copilot can assist with Git commands!</p>
<hr>
<h3 id="copilot">Copilot</h3>
<p>Copilot enhances your workflow with:</p>
<ul>
<li>Inline code completion</li>
<li>Inline chat (<code>CMD + I</code>)</li>
<li>An editor that handles multiple files (<code>CMD + SHIFT + I</code>)</li>
<li>General assistance via chat</li>
<li>Commit message suggestions</li>
</ul>
<h4 id="improving-copilots-effectiveness">Improving Copilot&rsquo;s Effectiveness</h4>
<p>To help Copilot assist you better:</p>
<ul>
<li>Use meaningful function names.</li>
<li>Add inline comments.</li>
<li>Write clean, structured code.</li>
</ul>
<h4 id="customizing-copilot">Customizing Copilot</h4>
<p>You can fine-tune Copilot&rsquo;s behavior by modifying your workspace settings. Create a <code>settings.json</code> file inside the <code>.vscode</code> folder and add custom configurations.</p>
<h5 id="example-settings">Example Settings:</h5>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="ln">1</span><span class="cl"><span class="s2">&#34;github.copilot.chat.localeOverride&#34;</span><span class="err">:</span> <span class="s2">&#34;fr&#34;</span><span class="err">,</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="s2">&#34;github.copilot.chat.commitMessageGeneration.instructions&#34;</span><span class="err">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="p">{</span><span class="nt">&#34;instruction&#34;</span><span class="p">:</span> <span class="s2">&#34;Start every commit message with FOOBAR&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="p">]</span><span class="err">,</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="s2">&#34;github.copilot.chat.codeGeneration.useInstructionFiles&#34;</span><span class="err">:</span> <span class="kc">true</span>
</span></span></code></pre></div><p>Now, create a <code>./.github/copilot-instructions.md</code> file with your custom instructions. Example:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">-</span> My project goal is a simple and performant API interface.
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">-</span> Code must follow modern coding standards.
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="k">-</span> Functions should have a maximum of 20 lines.
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="k">-</span> No redundant functions.
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="k">-</span> Each function must have a corresponding test function.
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="k">-</span> Include example data for testing.
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="k">-</span> Use multiple files, grouping functions logically.
</span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="k">-</span> Prefer class-based structures over standalone functions.
</span></span></code></pre></div><h4 id="finding-more-rule-sets">Finding More Rule Sets</h4>
<p>For additional guidance, check these resources:</p>
<ul>
<li>





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://github.com/PatrickJS/awesome-cursorrules/tree/main/rules?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">Awesome Cursor Rules</a>


</li>
<li>





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://github.com/Mawla/cursor_rules?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">Mawla Cursor Rules</a>


</li>
<li>





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://github.com/instructa/ai-prompts?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">Instructa AI Prompts</a>


</li>
</ul>
<hr>
<h3 id="claude-chatgpt-gemini-and-probably-even-v0">Claude, ChatGPT, Gemini, and Probably Even v0!</h3>
<p>It doesn’t end with <strong>Copilot</strong>. And again: <strong>You can achieve a lot with free plans!</strong> But if you really want to improve your process and build big things, consider using LLMs.</p>
<p>Before diving in, heads up: <strong>Don’t make the same mistake</strong> 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://x.com/leojr94_/status/1901560276488511759?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">as this poor guy</a>


:</p>
<blockquote>
<p><em>Guys, I&rsquo;m under attack ever since I started sharing how I built my SaaS using Cursor. Random things are happening—maxed-out usage on API keys, people bypassing the subscription, creating random stuff in the database.</em></p>
</blockquote>
<p>Don’t expect an LLM to generate a perfect app if you just throw some poorly written sentences at it. <strong>Garbage in, garbage out.</strong>  So, what should you do instead? <strong>Chain it!</strong>, baby.</p>
<ul>
<li><strong>Start simple.</strong></li>
<li><strong>Iterate.</strong></li>
<li><strong>Split your project into independent parts.</strong></li>
</ul>
<p>Think about developing an Android App for example. How would a human developer approach the task, what information would be required?</p>
<ol>
<li>The overall concept: What is the app about? What language is it written in?  Let&rsquo;s call it the &ldquo;soul&rdquo;</li>
<li>The UI/UX concept: How should it look and feel?  The &ldquo;body&rdquo;!</li>
<li>And, to stick to the image: The &ldquo;viscera&rdquo;:  The architecture, functions, features and internal structure.</li>
</ol>
<p>Now, ask ChatGPT to consider this <strong>&ldquo;trinity&rdquo;</strong> and generate <strong>three structured pre-prompt instructions</strong> for you.</p>
<ol>
<li>In <strong>Copilot</strong>, use the <strong>soul</strong> prompt in your pre-prompt instructions, as mentioned above.</li>
<li>Then, feed the <strong>body</strong> and <strong>viscera</strong> prompts into Copilot, Claude, or v0 to refine the results further.</li>
</ol>
<p>Of course, this isn’t a cut into stone,but it should give you a <strong>structured approach</strong> to leveraging AI effectively.</p>
<p>But do not forget this: AI Is a Tool, Not a Magic Wand. Don’t take AI-generated results for granted. The golden rule from the early internet days still applies:</p>
<blockquote>
<p><strong>&ldquo;A computer is only as smart as the person using it.&rdquo;</strong></p>
</blockquote>
<p>Always <strong>question and understand</strong> the output.  Don’t expect AI to <strong>perfectly execute any task instantly and without errors</strong>.  Because: The developer is still you.</p>
]]></content:encoded><atom:updated>2026-06-03T13:14:29+02:00</atom:updated><category>blog</category><dc:subject>Lesezeit: 5 Minuten</dc:subject><dc:type>guide</dc:type></item><item><title>About Agility and Innovation</title><link>https://nickyreinert.de/en/2025/2025-01-31-about-agility-and-innovation/</link><pubDate>Fri, 31 Jan 2025 12:34:56 +0200</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/en/2025/2025-01-31-about-agility-and-innovation/</guid><description>With the important caveat that my insights are based on experiences I’ve gathered in my own little bubble.
In recent years, I’ve worked in and for a variety of companies, ranging from hands-on SMEs, through more or less organized startups, to large corporations and agencies. I mention this not to …</description><content:encoded><![CDATA[<p><em>With the important caveat that my insights are based on experiences I&rsquo;ve gathered in my own little bubble.</em></p>
<p>In recent years, I&rsquo;ve worked in and for a variety of companies, ranging from <strong>hands-on SMEs</strong>, through more or less organized <strong>startups</strong>, to large <strong>corporations</strong> and <strong>agencies</strong>. I mention this not to brag, but to illustrate the breadth on which my following observations are based.</p>
<p>The larger the company, the less capable it is of developing innovative ideas, realizing them, or optimizing itself and its processes. (This is not a universal statement, but certainly a tendency.)</p>
<p>Why is that? So far, I&rsquo;ve identified two important reasons:</p>
<h2 id="1-bureaucracy-compliance--processes">1. Bureaucracy, Compliance &amp; Processes</h2>
<p>The larger the company, the less individual the processes. My favorite example is &ldquo;<strong>Code of Conduct</strong>&rdquo; courses. These typically cover four illustrious topics: <strong>Social Ethics</strong>, <strong>Business Ethics</strong>, <strong>Data Protection</strong>, <strong>IT Security</strong>. <strong>First Aid</strong> and <strong>Fire Safety</strong> are also often included. There&rsquo;s nothing wrong with the content; everyone should &ldquo;attend&rdquo; these courses at least once. But the content is often very broad. For example, &ldquo;<strong>Bribery</strong>&rdquo; is a popular topic, but it undoubtedly serves a limited audience. Not to mention the impact: If I want to accept a bribe, the corresponding course certainly won&rsquo;t stop me.</p>
<p>Nevertheless, everyone must deal with it. The participation requirement is often, if not always, distributed using the <strong>watering can principle</strong>: They are part of <strong>onboarding</strong>; must be repeated once a year. If you switch to another agency, you take part in such courses again. If you switch clients within the agency, the new client requires participation in their &ldquo;own&rdquo; courses - which are, of course, identical in content.</p>
<p>The result: Frustration, boredom, and <strong>unnecessary costs</strong>. The companies&rsquo; reaction: They spend <strong>a lot of money</strong> to pack the content into &ldquo;modern&rdquo; formats, conveying it playfully or in &ldquo;<strong>stage plays</strong>&rdquo;.</p>
<p>Why isn&rsquo;t there a <strong>Code of Conduct driver&rsquo;s license</strong>, a <strong>certificate</strong>? A test tailored to my field, which I take once a year and can show if needed!</p>
<p>But it&rsquo;s also the processes in <strong>daily business</strong> that stifle innovation. I once worked for a large German employer, and we wanted to set up a service to simplify the <strong>import of CSV files</strong>. Technically, it wasn&rsquo;t complicated; the files were small, and it was just about adjusting column names. However, we needed a service, a small virtual server or <strong>Docker container</strong> running in the background. It took months to find out which department could help us. Ultimately, we had to stop the project without success. A hurdle that would be unthinkable in a startup.</p>
<p>Recently, I stumbled upon 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://www.youtube.com/watch?v=pL3Yzjk5R4M&amp;utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">a very nice talk</a>


 on the <strong>Skunk Works principle</strong>, explaining its origin and impact. The idea is, roughly summarized: A small <strong>team of experts</strong> works independently and as <strong>free from corporate constraints</strong> as possible on a project.</p>
<img src="https://nickyreinert.de/en/2025/2025-01-31-about-agility-and-innovation/skunk.png" alt="skunk"><p>Another solution would be to not only allow but actively promote <strong>U-boat or pet projects</strong> with the necessary infrastructure. Shortly after my studies, I worked at a somewhat larger &ldquo;startup&rdquo; and eventually implemented a technically simple search mask that massively facilitated the team&rsquo;s work. Needless to say, my <strong>team lead</strong> wasn&rsquo;t very happy about the &ldquo;misallocated&rdquo; hours.</p>
<h2 id="2-turf-protection-tunnel-vision">2. Turf Protection, Tunnel Vision</h2>
<p>This is a phenomenon I&rsquo;ve observed many times; I call it the &ldquo;<strong>wrong Gallic village</strong>&rdquo;. A Gallic village that defends itself against <strong>invaders</strong> can be a good thing. But what if it&rsquo;s not invaders but <strong>innovators</strong>? New ideas are either quickly perceived as <strong>negative criticism</strong> of the previous quality, or there&rsquo;s simply fear of losing one&rsquo;s <strong>established position</strong>. A Gallic village that resists any outside influence has detached itself from the company&rsquo;s goal.</p>
<p>Existing solutions are rarely questioned. This is a big problem, especially in the technological field, where new possibilities arise <strong>weekly</strong>. Three arguments are then used to protect against the impending <strike>invasion</strike> <strong>innovation</strong>:</p>
<ol>
<li>Never change a running system.</li>
<li>No capacity.</li>
<li>Too complicated.</li>
</ol>
<p>I&rsquo;m not saying that the motto &ldquo;<strong>Never change a running system</strong>&rdquo; is fundamentally wrong. <span style="color:#99f;"><em>You don&rsquo;t have to jump on every bandwagon. But eventually, the last train has left the station</em>.</span> And before you know it, the solution is based on an outdated technical platform that is no longer supported or mastered by anyone. Software development, in particular, suffers from this phenomenon: A program is never finished.</p>
<p>&ldquo;<strong>No capacity</strong>&rdquo; is an excuse that shifts the current, perhaps unknown problem into the future. But this shift is not linear. If you shift every adjustment into the future for <strong>ten years</strong>, they don&rsquo;t spread over a period of <strong>ten years</strong> in the future. They accumulate at the point where the future begins. And that&rsquo;s tomorrow.</p>
<p>&ldquo;<strong>Too complicated</strong>&rdquo; is an argument that <strong>undermines itself</strong>. If adjustments to the current solution are too complicated, we should invest time sooner rather than later. Because software development also suffers from this: A program rarely becomes less complex over time; rather, the opposite is true.</p>
<p>The art is to have an agnostic attitude towards change without succumbing to it.</p>
]]></content:encoded><atom:updated>2026-05-07T15:36:59+02:00</atom:updated><category>blog</category><dc:subject>Lesezeit: 5 Minuten</dc:subject><dc:type>essay</dc:type></item><item><title>Imprint</title><link>https://nickyreinert.de/en/impressum/</link><pubDate>Wed, 01 Jan 2025 00:00:00 +0000</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/en/impressum/</guid><description>Information according to § 5 TMG Nicky Reinert
Institute for Digital Challenges
E-Mail: nісkуrеіnеrt [at] gmаіl [dot] соm
Characters obfuscated for spam protection, please type manually
Note: The “Institute for Digital Challenges” is not a registered institution, but a private research and analysis …</description><content:encoded><![CDATA[<h2 id="information-according-to--5-tmg">Information according to § 5 TMG</h2>
<p>Nicky Reinert<br>
Institute for Digital Challenges<br>
E-Mail: nісkуrеіnеrt [at] gmаіl [dot] соm<br>
<em>Characters obfuscated for spam protection, please type manually</em></p>
<p>Note: The &ldquo;Institute for Digital Challenges&rdquo; is not a registered institution, but a private research and analysis project run by an individual as a hobby.</p>
<h2 id="responsible-for-content-according-to--55-abs-2-rstv">Responsible for content according to § 55 Abs. 2 RStV</h2>
<p>Nicky Reinert</p>
<p>Lichtenbergstr. 40
10243 Berlin</p>
<h2 id="disclaimer">Disclaimer</h2>
<h3 id="liability-for-content">Liability for content</h3>
<p>The contents of this website have been created with the utmost care. However, no guarantee can be given for the correctness, completeness, and topicality of the content.</p>
<h3 id="liability-for-links">Liability for links</h3>
<p>This website contains links to external third-party websites, over whose content no influence is held. The respective provider or operator of the linked pages is always responsible for their content.</p>
<h2 id="copyright">Copyright</h2>
<p>The content and works on these pages created by the site operator are subject to German copyright law. Reproduction, processing, distribution, and any kind of use beyond the limits of copyright law require the written consent of the respective author or creator.</p>
]]></content:encoded><atom:updated>2026-06-05T20:27:21+02:00</atom:updated></item><item><title>Privacy Policy</title><link>https://nickyreinert.de/privacy/</link><pubDate>Wed, 01 Jan 2025 00:00:00 +0000</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/privacy/</guid><description>Protecting your data is important to us. We handle information confidentially and comply with the requirements of the General Data Protection Regulation (GDPR) and the German Federal Data Protection Act (BDSG). This policy explains whether and to what extent data is processed and which rights you …</description><content:encoded><![CDATA[<p>Protecting your data is important to us. We handle information confidentially and comply with the requirements of the General Data Protection Regulation (GDPR) and the German Federal Data Protection Act (BDSG).
This policy explains whether and to what extent data is processed and which rights you have.</p>
<h2 id="controller">Controller</h2>
<p>Institute for Digital Challenges<br>
E-Mail: nісkуrеіnеrt [at] gmаіl [dot] соm<br>
<em>Characters obfuscated for spam protection, please type manually</em></p>
<p>Note: The &ldquo;Institute for Digital Challenges&rdquo; is not a registered institution, but a private research and analysis project run by an individual as a hobby.</p>
<h2 id="type-and-scope-of-data-processing">Type and Scope of Data Processing</h2>
<p>Our activities include research, data analysis, data engineering, and data mining.
We <strong>generally do not process personal data</strong> as defined in Art. 4(1) GDPR.
If, in exceptional cases, personal data is provided to us in the context of projects,
<strong>it is anonymized before any processing</strong>, preventing any link to identifiable individuals.
We do not share data with third parties, nor do we analyze personal data using external services.</p>
<h2 id="task-manager-app-procrastino">Task Manager App &ldquo;Procrastino&rdquo;</h2>
<p>Our mobile app enables synchronization of tasks with Google Tasks.
Signing in with a Google account is required for this purpose.</p>
<p><strong>Processed data:</strong> Only the information required for synchronization (e.g., task contents, task metadata) is processed.</p>
<p><strong>Storage:</strong> All data is stored exclusively on your device in the app&rsquo;s storage. No storage or processing takes place on servers operated by us.</p>
<p><strong>Disclosure:</strong> No data is shared with third parties.</p>
<p><strong>Advertising/Tracking:</strong> The app does not contain any advertisements and does not use analytics tools.</p>
<p><strong>Google services:</strong> The use of Google Tasks is additionally subject to Google&rsquo;s Privacy Policy: 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://policies.google.com/privacy?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">https://policies.google.com/privacy</a>


</p>
<h2 id="hosting-via-github-pages">Hosting via GitHub Pages</h2>
<p>Our website is hosted via <strong>GitHub Pages</strong> (GitHub Inc., 88 Colin P Kelly Jr St, San Francisco, CA 94107, USA;
a company of Microsoft Corporation). When accessing the website,
GitHub automatically processes technical information (e.g., IP address, browser type, operating system,
referrer URL, timestamp) in server log files to ensure operation and security.
For further details, please refer to GitHub&rsquo;s Privacy Statement:






  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://docs.github.com/en/site-policy/privacy-policies/github-privacy-statement?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">GitHub Privacy Statement</a>


.
We do not analyze these log files ourselves.</p>
<h2 id="legal-basis">Legal Basis</h2>
<p>Since we do not process personal data ourselves, our activities are not based on consent or legitimate interest.
The logging carried out by GitHub is based on Art. 6(1)(f) GDPR (legitimate interest in operation,
security, and stability of web services) by GitHub.</p>
<h2 id="rights-of-data-subjects">Rights of Data Subjects</h2>
<p>Regardless of the above, you are entitled – where applicable – to the rights under Art. 15–22 GDPR
(access, rectification, erasure, restriction, objection, data portability).
For requests concerning processing carried out by us, please contact us at the above address.
For the logging performed by GitHub, please contact GitHub directly.</p>
<h2 id="data-security">Data Security</h2>
<p>We take appropriate technical and organizational measures to protect the confidentiality, integrity,
and availability of the data we use.</p>
<h2 id="cookies-tracking-and-embedded-content">Cookies, Tracking, and Embedded Content</h2>
<p>We use <strong>no cookies</strong>, perform <strong>no tracking</strong>, and embed <strong>no external content</strong>
(e.g., web fonts, videos, scripts).</p>
<h2 id="changes-to-this-privacy-policy">Changes to this Privacy Policy</h2>
<p>We reserve the right to update this privacy policy to reflect changes in legal or technical requirements.</p>
<hr>
<p><em>Effective date: 01 January 2025</em></p>
]]></content:encoded><atom:updated>2026-06-05T20:27:21+02:00</atom:updated></item><item><title>Advent of Code - Day 8 - Missing Operators (Ruby)</title><link>https://nickyreinert.de/en/2024/2024-12-08-aoc-day-8/</link><pubDate>Sun, 08 Dec 2024 12:34:56 +0200</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/en/2024/2024-12-08-aoc-day-8/</guid><description>( task | solution )
The first time I read through the riddle, I was like: “Phew, no way, I’m going to skip this!” I looked at the example and the description, and it didn’t make sense at all. But when I’m stuck like that at the very beginning, the best approach to get a grip is: Visualize! (A …</description><content:encoded><![CDATA[<p>(





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://adventofcode.com/2024/day/8/?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">task</a>


 | 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://github.com/nickyreinert/aoc2024/tree/main/solutions/day8%20-%20ruby?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">solution</a>


)</p>
<p>The first time I read through the riddle, I was like: &ldquo;Phew, no way, I&rsquo;m going to skip this!&rdquo; I looked at the example and the description, and it didn’t make sense at all. But when I’m stuck like that at the very beginning, the best approach to get a grip is: Visualize! (A concept I didn&rsquo;t learn from [Daniel Bourke&rsquo;s course about machine learning](but he perfected it!)</p>
<p>However, I thought it might be a good idea to use Ruby this time — my first time with this language. I won’t bother you with the environment setup. Again, I’m just using a containerized environment, which makes things so much easier.</p>
<h3 id="solution-to-the-first-part">Solution to the First Part</h3>
<p>So, what is this about? It’s about finding siblings on a surface, connecting them with a straight line, and then extending this line to find two symmetric points. The total number of all those points forms the solution to this riddle. The green and red cells represent two categories of siblings on the map, and the blue cells represent the symmetric points (called <code>antinodes</code>) that we need to find. If you connect them (with red lines), you’ll see how all of them pass through two cells of the same color:</p>
<img src="https://nickyreinert.de/en/2024/2024-12-08-aoc-day-8/image-2.png" alt="alt text"><p>Looks difficult, right? Don’t tell me, it took me a while to understand the &ldquo;gibberish&rdquo; of the riddle – no offense, riddle master.</p>
<p>But wait until we add row and column numbers to the map and focus on just one group:</p>
<img src="https://nickyreinert.de/en/2024/2024-12-08-aoc-day-8/image-1.png" alt="alt text"><p>Now it seems quite easy: We just need to somehow identify the row and column distance between two siblings and use that to place the corresponding <code>antinode</code>.</p>
<p>Let me just throw my solution into the ring. It’s not the most elegant, but it gets the job done:</p>
<ul>
<li>First, I use two loops to find two siblings.</li>
<li>Then, I calculate the row and column indexes for both siblings.</li>
<li>From there, I can calculate the row and column indexes for both <code>antinodes</code>.</li>
<li>Finally, I just check if they are within the boundaries before adding them to a dictionary.</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="n">from_char_array</span><span class="o">.</span><span class="n">each_with_index</span> <span class="k">do</span> <span class="o">|</span><span class="n">from_char</span><span class="p">,</span> <span class="n">from_index</span><span class="o">|</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="k">next</span> <span class="k">if</span> <span class="n">from_char</span> <span class="o">==</span> <span class="s2">&#34;.&#34;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">from_char_array</span><span class="o">[</span><span class="p">(</span><span class="n">from_index</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span><span class="o">..-</span><span class="mi">1</span><span class="o">].</span><span class="n">each_with_index</span> <span class="k">do</span> <span class="o">|</span><span class="n">to_char</span><span class="p">,</span> <span class="n">to_index</span><span class="o">|</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="k">next</span> <span class="k">unless</span> <span class="n">from_char</span> <span class="o">==</span> <span class="n">to_char</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        <span class="n">from_row</span> <span class="o">=</span> <span class="p">(</span><span class="n">from_index</span> <span class="o">/</span> <span class="n">map_height</span><span class="p">)</span><span class="o">.</span><span class="n">floor</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        <span class="n">from_col</span> <span class="o">=</span> <span class="n">from_index</span> <span class="o">-</span> <span class="p">(</span><span class="n">from_row</span> <span class="o">*</span> <span class="n">map_width</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="n">to_row</span> <span class="o">=</span> <span class="p">((</span><span class="n">from_index</span> <span class="o">+</span> <span class="n">to_index</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">/</span> <span class="n">map_height</span><span class="p">)</span><span class="o">.</span><span class="n">floor</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="n">to_col</span> <span class="o">=</span> <span class="p">(</span><span class="n">from_index</span> <span class="o">+</span> <span class="n">to_index</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">-</span> <span class="p">(</span><span class="n">to_row</span> <span class="o">*</span> <span class="n">map_width</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="nb">puts</span> <span class="s2">&#34;Found couple of </span><span class="si">#{</span><span class="n">from_char</span><span class="si">}</span><span class="s2"> at </span><span class="si">#{</span><span class="n">from_row</span><span class="si">}</span><span class="s2">/</span><span class="si">#{</span><span class="n">from_col</span><span class="si">}</span><span class="s2"> and at </span><span class="si">#{</span><span class="n">to_row</span><span class="si">}</span><span class="s2">/</span><span class="si">#{</span><span class="n">to_col</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="n">first_antinode_row</span> <span class="o">=</span> <span class="n">from_row</span> <span class="o">-</span> <span class="p">(</span><span class="n">to_row</span> <span class="o">-</span> <span class="n">from_row</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="n">second_antinode_row</span> <span class="o">=</span> <span class="n">to_row</span> <span class="o">+</span> <span class="p">(</span><span class="n">to_row</span> <span class="o">-</span> <span class="n">from_row</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="n">first_antinode_col</span> <span class="o">=</span> <span class="n">from_col</span> <span class="o">+</span> <span class="p">(</span><span class="n">from_col</span> <span class="o">-</span> <span class="n">to_col</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="n">second_antinode_col</span> <span class="o">=</span> <span class="n">to_col</span> <span class="o">+</span> <span class="p">(</span><span class="n">to_col</span> <span class="o">-</span> <span class="n">from_col</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="k">if</span> <span class="n">first_antinode_row</span><span class="o">.</span><span class="n">between?</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">map_height</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">first_antinode_col</span><span class="o">.</span><span class="n">between?</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">map_width</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">            <span class="n">antinodes</span><span class="o">[</span><span class="s2">&#34;</span><span class="si">#{</span><span class="n">first_antinode_row</span><span class="si">}</span><span class="s2">/</span><span class="si">#{</span><span class="n">first_antinode_col</span><span class="si">}</span><span class="s2">&#34;</span><span class="o">]</span> <span class="o">=</span> <span class="s2">&#34;#&#34;</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">        <span class="k">end</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">        
</span></span><span class="line"><span class="ln">25</span><span class="cl">        <span class="k">if</span> <span class="n">second_antinode_row</span><span class="o">.</span><span class="n">between?</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">map_height</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">second_antinode_col</span><span class="o">.</span><span class="n">between?</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">map_width</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">            <span class="n">antinodes</span><span class="o">[</span><span class="s2">&#34;</span><span class="si">#{</span><span class="n">second_antinode_row</span><span class="si">}</span><span class="s2">/</span><span class="si">#{</span><span class="n">second_antinode_col</span><span class="si">}</span><span class="s2">&#34;</span><span class="o">]</span> <span class="o">=</span> <span class="s2">&#34;#&#34;</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">        <span class="k">end</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">        
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="k">end</span>
</span></span></code></pre></div><p><em>If you&rsquo;re stuck on this one, no worries: I have to admit, although it&rsquo;s just roughly 20 lines of simple code, it took me a while to initially understand the task and then put everything together, because I was mixing up 0-based indexing, column calculation, and so on.</em></p>
<p>And because I am already 4 days behind, I didn&rsquo;t even read the second part and continued to day 9…</p>
<h2 id="whats-up-ruby">What&rsquo;s up, Ruby?</h2>
<p><strong>Rating: 10/12 – underestimated diamond</strong></p>
<p>I was honestly surprised at how easy it is to get something done in <code>Ruby</code>. It seems as simple and straightforward as <code>Python</code>, and if you&rsquo;re looking for a quick &rsquo;n&rsquo; dirty scripting language, I&rsquo;d say <code>Ruby</code> is definitely worth a try.</p>]]></content:encoded><atom:updated>2026-06-16T20:56:44+02:00</atom:updated><category>development</category><category>aoc2024</category><dc:subject>Lesezeit: 5 Minuten</dc:subject><dc:type>solution_writeup</dc:type></item><item><title>Advent of Code - Day 7 - Missing Operators (C++)</title><link>https://nickyreinert.de/en/2024/2024-12-07-aoc-day-7/</link><pubDate>Sat, 07 Dec 2024 12:34:56 +0200</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/en/2024/2024-12-07-aoc-day-7/</guid><description>( task | solution )
This is the kind of task I really enjoy: finding algorithms for simple mathematical challenges! Let’s go—this time in… C++! (Phew!)
You’ll find my .devcontainer and Dockerfile on GitHub. Once again, I don’t want to bloat my system with additional compiler setups. It turns out, …</description><content:encoded><![CDATA[<p>(





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://adventofcode.com/2024/day/7/?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">task</a>


 | 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://github.com/nickyreinert/aoc2024/tree/main/solutions/day7%20-%20c%2B%2B?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">solution</a>


)</p>
<p>This is the kind of task I really enjoy: finding algorithms for simple mathematical challenges! Let&rsquo;s go—this time in… C++! (Phew!)</p>
<p>You&rsquo;ll find my 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://github.com/nickyreinert/aoc2024/tree/main/solutions/day7%20-%20c%2B%2B/.devcontainer?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">.devcontainer and Dockerfile</a>


 on GitHub. Once again, I don&rsquo;t want to <em>bloat</em> my system with additional compiler setups. It turns out, this is working like a charm.</p>
<h3 id="solution-to-the-first-part">Solution to the First Part</h3>
<p>Well, the idea is quite easy: We got a list of <code>operands</code> and need to find the correct combination of <code>+</code> and <code>*</code> to get a target result. What we basically need to do is: Loop through all given equations, get all possible permutations of <code>+</code> and <code>*</code> for <code>n</code> given operands, and then do the math.</p>
<p>The first tricky thing here is: They are providing a lot of huge numbers. So using 32-bit integers isn&rsquo;t sufficient. You not only need 64-bit integers, but also <strong>unsigned</strong> to work with numbers like <code>175.033.602.122.656</code>.</p>
<p>But they also hid a little pitfall in the list of possible equations: There is <strong>one</strong> (exactly <strong>one</strong>!) duplicate entry. Which makes it hard to find. Would it be ten, 42, or 100 duplicate entries, you would somehow stumble over it. But now it took me a while, and I was close to throwing this task away. I started debugging and I wondered why I am only processing 849 equations instead of 850. At first glance, it looks like one of those classic &ldquo;indexing starts at zero&rdquo; bugs. But then I did, what you usually do when filling dictionaries: You check if the entry you want to add already exists. And it does.</p>
<p>And while this wouldn&rsquo;t lead to a valid equation…</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">1095: 8 5 9 3 231
</span></span></code></pre></div><p>This line certainly does</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">1095: 8 1 2 7 95 8 2 7 2 2 99
</span></span></code></pre></div><p>And so I found the missing value for the final result…</p>
<h3 id="solution-to-the-second-part">Solution to the Second Part</h3>
<p>Well, the second part took me a while because I was somehow trying to build this concatenation part into the current algorithm. We now have three operators, <code>*</code>, <code>+</code>, and <code>||</code> — but the last one should connect two integers.</p>
<p>Eventually, I had my <em>heureka</em> moment: Concatenation could be implemented as a mathematical operation, too.</p>
<p>If the left number is <code>123</code> followed by <code>45</code>, we need to multiply <code>123</code> with <code>10^2</code> and then just add <code>45</code>. Easy math.</p>
<p><strong>Side note</strong>: I was cheating a little. I now know that <code>1095</code> is a duplicated entry, and I know it would lead to a valid result, so I am adding it hard-coded to get the final result.</p>
<h2 id="whats-up-c">Whats up, C++?</h2>
<p><strong>Rating: 6/12 – painful</strong></p>
<p>Well, <code>C++</code> is undoubtedly a powerful language, but for a reason it&rsquo;s not beating all those scripting languages out there. Simple things are complicated because you have to implement them on your own. 5 out of 12 seems to be unfair for such a grown-up programming language, but this is simply my point of view. For me, right now, there&rsquo;s no reason to use C++. Which absolutely does not mean that this language is not important! Nevertheless, using it felt like an adult pro developer. ;)</p>]]></content:encoded><atom:updated>2026-06-16T20:56:44+02:00</atom:updated><category>development</category><category>aoc2024</category><media:content url="https://nickyreinert.de/images/posts/placeholder.jpg" type="image/jpeg"><media:title>Nicky Reinert</media:title></media:content><dc:subject>Lesezeit: 5 Minuten</dc:subject><dc:type>solution_writeup</dc:type></item><item><title>Advent of Code - Day 6 - Walking the map (JavaScript)</title><link>https://nickyreinert.de/en/2024/2024-12-06-aoc-day-6/</link><pubDate>Fri, 06 Dec 2024 12:34:56 +0200</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/en/2024/2024-12-06-aoc-day-6/</guid><description>( task | solution )
This task looks tricky, so I am once again choosing a simple language that I am familiar with: JavaScript ( which, by the way, was developed in just 10 days ).
With no further ado…</description><content:encoded><![CDATA[<p>(





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://adventofcode.com/2024/day/6/?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">task</a>


 | 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://github.com/nickyreinert/aoc2024/tree/main/solutions/day6%20-%20javascript?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">solution</a>


)</p>
<p>This task looks tricky, so I am once again choosing a simple language that I am familiar with: JavaScript (





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://nickyreinert.medium.com/ne-34-javascript-was-developed-within-10-days-0116b32a38b6?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">which, by the way, was developed in just 10 days</a>


).</p>
<p>With no further ado…</p>
<h3 id="solution-to-the-first-part">Solution to the First Part</h3>
<p>There&rsquo;s not much to explain. As expected, I just need to move the <code>guard</code> through the maze by increasing and decreasing rows and columns. When I hit an obstacle, I change direction. When I hit the border of the maze, I’ve solved the task. Easy peasy…</p>
<h3 id="solution-to-the-second-part">Solution to the Second Part</h3>
<p>The second part is kind of tricky. They want us to find positions of additional obstacles that make the maze unsolvable.</p>
<p>Well, there&rsquo;s an easy pattern behind it. You will see it if you visualize it:</p>
<p>An infinite loop occurs when you have four obstacles on each edge of a rectangle, like this:</p>
<img src="https://nickyreinert.de/en/2024/2024-12-06-aoc-day-6/image.png" alt="alt text"><p>So, all we need to do at every step on our path is to try to draw a rectangle. If it has three existing obstacles, we get one potential solution for an &ldquo;artificial obstacle.</p>
<p>In case you&rsquo;re wondering: I didn&rsquo;t finish the second part. I know the solution approach, but seeing that I am three days behind already, I stopped working on it.</p>
<h2 id="whats-up-javascript">Whats up, JavaScript?</h2>
<p><strong>Rating: 9/12 – god ol&rsquo; frien&rsquo;</strong></p>
<p>Fun Fact: 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://nickyreinert.medium.com/javascript-how-to-find-prime-numbers-fast-cbcf6bd62e3d?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">I was writing an algorithm</a>


 to find prime numbers and found out that JavaScript is way faster than Python.</p>
<p>However, I really like JavaScript and often use it when I want to build small <em>proof of concepts</em>. It&rsquo;s quick, runs everywhere (like in every browser), and is quite straightforward.</p>
<p>Despite the second bump in the journey to endless glory, I hope to see you again tomorrow!</p>]]></content:encoded><atom:updated>2026-06-16T20:56:44+02:00</atom:updated><category>development</category><category>aoc2024</category><dc:subject>Lesezeit: 5 Minuten</dc:subject><dc:type>solution_writeup</dc:type></item><item><title>Advent of Code - Day 5 - Printer Updates (Bash)</title><link>https://nickyreinert.de/en/2024/2024-12-05-aoc-day-5/</link><pubDate>Thu, 05 Dec 2024 12:34:56 +0200</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/en/2024/2024-12-05-aoc-day-5/</guid><description>( task | solution )
It’s the fifth day of our outrageous adventure, and we are dealing with printer updates. To solve this riddle, we are getting back to the roots and our good ol’ friend Bash!
The good news here is that I don’t need to set up any particular environment because, thankfully, Bash is …</description><content:encoded><![CDATA[<p>(





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://adventofcode.com/2024/day/5/?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">task</a>


 | 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://github.com/nickyreinert/aoc2024/blob/main/solutions/day5%20-%20bash/solve.sh?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">solution</a>


)</p>
<p>It&rsquo;s the fifth day of our outrageous adventure, and we are dealing with printer updates. To solve this riddle, we are getting back to the roots and our good ol&rsquo; friend <code>Bash</code>!</p>
<p>The good news here is that I don&rsquo;t need to set up any particular environment because, thankfully, <code>Bash</code> is available on most systems out of the box.</p>
<h3 id="solution-to-the-first-part">Solution to the First Part</h3>
<p>The solution is quite straightforward: We need to read the rules and the updates into an array, loop through all updates, and check if they somehow break the rules. That&rsquo;s nothing too complicated. The only thing that I am probably doing <em>unconventionally</em> is how I check.</p>
<p>I am using a regular expression that is the opposite of the rule. If it matches, I know the update is invalid:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="mf">61.</span><span class="o">*</span><span class="err">?</span><span class="p">(,</span><span class="mi">29</span><span class="p">)(,</span><span class="o">|</span><span class="err">$</span><span class="p">)</span>
</span></span></code></pre></div><p>In this example, <code>29</code> is the actual rule name, and <code>61</code> should come after <code>29</code>. With <code>.*?</code>, I am <em>ungreedy</em> checking for any characters in between, and the closing group just makes sure that after <code>29</code>, there&rsquo;s either another number or the end of the update.</p>
<p>Simple thing.</p>
<h3 id="solution-to-the-second-part">Solution to the Second Part</h3>
<p>The second part looks way easier. They just want us to switch the position of the pages that we identified as erroneous.</p>
<p>Basically, it&rsquo;s just something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">updates<span class="o">[</span>page_index<span class="o">]=</span><span class="nv">$subsequent_page</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">updates<span class="o">[</span>subsequent_page_index<span class="o">]=</span><span class="nv">$page</span>
</span></span></code></pre></div><p>But… yeah, that&rsquo;s the first bump in my journey. My code isn&rsquo;t resulting in the correct answer, and it seems like I am missing a detail. To avoid wasting any more time, I&rsquo;ll proceed to the next day… sorry folks :(</p>
<h2 id="whats-up-bash">Whats up, Bash?</h2>
<p><strong>Rating: 8/12 – weird operators and syntax</strong></p>
<p>The boss of all scripting languages: <code>Bash</code>. I still don&rsquo;t understand most of the syntax rules, but thanks to AI, it&rsquo;s not too hard to get things done in <code>Bash</code>. When working as a sysadmin, I loved automating processes and even building small user interfaces to make life on the command line easier. However, I guess there are better ways for that.</p>
<p>See you next day!</p>]]></content:encoded><atom:updated>2026-06-16T20:56:44+02:00</atom:updated><category>development</category><category>aoc2024</category><media:content url="https://nickyreinert.de/images/posts/placeholder.jpg" type="image/jpeg"><media:title>Nicky Reinert</media:title></media:content><dc:subject>Lesezeit: 5 Minuten</dc:subject><dc:type>solution_writeup</dc:type></item><item><title>Advent of Code - Day 4 - Finding patterns (Excel)</title><link>https://nickyreinert.de/en/2024/2024-12-04-aoc-day-4/</link><pubDate>Wed, 04 Dec 2024 12:34:56 +0200</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/en/2024/2024-12-04-aoc-day-4/</guid><description>( task )
I Made It to Day 4!
And today’s task perfectly fits Excel. Some—if not most—people don’t like Excel and would never consider it a serious tool for data analytics, exploration, or even “engineering.” Well, you’re wrong about Excel. It’s quite powerful if you know how to use it. Let me show …</description><content:encoded><![CDATA[<p>(





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://adventofcode.com/2024/day/4/?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">task</a>


)</p>
<p>I Made It to Day 4!</p>
<p>And today&rsquo;s task perfectly fits Excel. Some—if not most—people don&rsquo;t like Excel and would never consider it a serious tool for data analytics, exploration, or even &ldquo;engineering.&rdquo; Well, you&rsquo;re wrong about Excel. It&rsquo;s quite powerful if you know how to use it. Let me show you…</p>
<p>The first part of the task is a kind of word riddle where we have to count occurrences of &ldquo;XMAS&rdquo; in a huge grid of letters. And actually, that&rsquo;s pretty easy with Excel. I&rsquo;d even say it&rsquo;s easier than using a fully-fledged scripting tool.</p>
<h3 id="solution-to-the-first-part">Solution to the First Part</h3>
<p>The input comes as a couple of lines. To make each character addressable, we first need to split each character into its own column. While I could use &ldquo;Text to Columns,&rdquo; the goal is to make this dynamic, so I will utilize a formula:</p>
<p><em>_LENGTH refers to <code>[C2]</code></em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">[D1]=MID(B4;SEQUENCE(1;_LENGTH);1)
</span></span></code></pre></div><p>If you pull this formula down, you get a matrix like this (referring to the example input):</p>
<img src="https://nickyreinert.de/en/2024/2024-12-04-aoc-day-4/image-1.png" alt="alt text"><p>The following image shows all possible solutions:</p>
<img src="https://nickyreinert.de/en/2024/2024-12-04-aoc-day-4/starting_matrix.png" alt="starting matrix"><p>We start with the most simple solution, where <code>XMAS</code> is <em>plotted</em> from the center to the right. The surrounding <code>if-condition</code> is temporary, only to make the result visible:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">=IF(CONCAT(G21;H21;I21;J21)=&#34;XMAS&#34;;&#34;+&#34;;&#34;&#34;)
</span></span></code></pre></div><p>It&rsquo;s quite easy, right? We copy the formula (not the result of the cell!) to a text editor and modify it to get the other seven directions.</p>
<p>Eventually, you end up with eight lines looking like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">=IF(CONCAT(G21;H21;I21;J21)=&#34;XMAS&#34;;&#34;+&#34;;&#34;&#34;)
</span></span><span class="line"><span class="ln">2</span><span class="cl">=IF(CONCAT(G21;H22;I23;J24)=&#34;XMAS&#34;;&#34;+&#34;;&#34;&#34;)
</span></span><span class="line"><span class="ln">3</span><span class="cl">=IF(CONCAT(G21;G22;G23;G24)=&#34;XMAS&#34;;&#34;+&#34;;&#34;&#34;)
</span></span><span class="line"><span class="ln">4</span><span class="cl">=IF(CONCAT(G21;F22;E23;D24)=&#34;XMAS&#34;;&#34;+&#34;;&#34;&#34;)
</span></span><span class="line"><span class="ln">5</span><span class="cl">=IF(CONCAT(G21;F22;E23;D24)=&#34;XMAS&#34;;&#34;+&#34;;&#34;&#34;)
</span></span><span class="line"><span class="ln">6</span><span class="cl">=IF(CONCAT(G21;F20;E19;D18)=&#34;XMAS&#34;;&#34;+&#34;;&#34;&#34;)
</span></span><span class="line"><span class="ln">7</span><span class="cl">=IF(CONCAT(G21;G20;G19;G18)=&#34;XMAS&#34;;&#34;+&#34;;&#34;&#34;)
</span></span><span class="line"><span class="ln">8</span><span class="cl">=IF(CONCAT(G21;H20;I19;J18)=&#34;XMAS&#34;;&#34;+&#34;;&#34;&#34;)
</span></span></code></pre></div><p>To count the occurrences of all existing solutions, we could either build some kind of <code>LAMBDA</code> function or use a simple trick: We just cast the <code>TRUE</code> or <code>FALSE</code> result to an integer:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">=--(VERKETTEN(G21;H21;I21;J21)=&#34;XMAS&#34;)+
</span></span><span class="line"><span class="ln">2</span><span class="cl">    --(VERKETTEN(G21;H22;I23;J24)=&#34;XMAS&#34;)+
</span></span><span class="line"><span class="ln">3</span><span class="cl">    --(VERKETTEN(G21;G22;G23;G24)=&#34;XMAS&#34;)+
</span></span><span class="line"><span class="ln">4</span><span class="cl">    --(VERKETTEN(G21;F21;E21;D21)=&#34;XMAS&#34;)+
</span></span><span class="line"><span class="ln">5</span><span class="cl">    --(VERKETTEN(G21;F22;E23;D24)=&#34;XMAS&#34;)+
</span></span><span class="line"><span class="ln">6</span><span class="cl">    --(VERKETTEN(G21;F20;E19;D18)=&#34;XMAS&#34;)+
</span></span><span class="line"><span class="ln">7</span><span class="cl">    --(VERKETTEN(G21;G20;G19;G18)=&#34;XMAS&#34;)+
</span></span><span class="line"><span class="ln">8</span><span class="cl">    --(VERKETTEN(G21;H20;I19;J18)=&#34;XMAS&#34;)
</span></span></code></pre></div><p>And now apply it to our testing dataset. Copy the cell (not the formula!) next to our matrix. It should refer to the first column in the first row. Then drag it to the right and down to cover the whole area. You should end up with something like this:</p>
<img src="https://nickyreinert.de/en/2024/2024-12-04-aoc-day-4/image-5.png" alt="alt text"><p>The sum of the matrix is 18 — which is exactly the number of <code>XMAS</code> hidden in the test dataset. Now take the actual puzzle input, copy it to your sheet, and apply your <em>magic formula</em> to it. If you want to avoid dragging the formula to the right and down, you could use <code>SEQUENCE</code> again, but we&rsquo;ll save that for later…</p>
<p>The good thing about the Excel approach is that the second part doesn&rsquo;t make it more complicated. We stick with our strategy and now just look for all possible variations of <code>X-MAS</code>. There are four:</p>
<img src="https://nickyreinert.de/en/2024/2024-12-04-aoc-day-4/image-6.png" alt="alt text"><p>And our formula changes a little. We know that <code>A</code> has to be in the middle. We concatenate the surrounding four cells and just test for all four possible combinations of <code>S</code> and <code>M</code>, which leads us to something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">=--(OR(CONCAT(F210;G209;G211;E211;E209)=&#34;AMSSM&#34;;
</span></span><span class="line"><span class="ln">2</span><span class="cl">    CONCAT(F210;G209;G211;E211;E209)=&#34;ASSMM&#34;;
</span></span><span class="line"><span class="ln">3</span><span class="cl">    CONCAT(F210;G209;G211;E211;E209)=&#34;ASMMS&#34;;
</span></span><span class="line"><span class="ln">4</span><span class="cl">    CONCAT(F210;G209;G211;E211;E209)=&#34;AMMSS&#34;))
</span></span></code></pre></div><p>It&rsquo;s even simpler than the previous one! Again, just make it reference your dataset, and you get the correct result!</p>
<h2 id="whats-up-excel">Whats up, Excel?</h2>
<p><strong>Rating: 10/12 – understimated and powerful</strong></p>
<p>I&rsquo;m not gonna lie: I really love Excel. Since a couple of years, Excel also supports <code>LAMBDA</code>, which allows for more complex calculations and even functional programming. [You can even build an animated ticker in Excel]((shameless self-promotion)!</p>
<p>That said… see you next day!</p>]]></content:encoded><atom:updated>2026-06-16T20:56:44+02:00</atom:updated><category>development</category><category>aoc2024</category><dc:subject>Lesezeit: 5 Minuten</dc:subject><dc:type>solution_writeup</dc:type></item><item><title>Advent of Code - Day 3 - Multiplications (Python)</title><link>https://nickyreinert.de/en/2024/2024-12-03-aoc-day-3/</link><pubDate>Tue, 03 Dec 2024 12:34:56 +0200</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/en/2024/2024-12-03-aoc-day-3/</guid><description>( task | solution ) As I reached the third day of my adventure, I had already learned something new that significantly improves the process: the devcontainer.json file can handle creating and running the Docker container for me. This saves the trouble of manually building and running it—essentially …</description><content:encoded><![CDATA[<p>(





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://adventofcode.com/2024/day/3/?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">task</a>


 | 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://github.com/nickyreinert/aoc2024/tree/main/solutions/day3%20-%20python?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">solution</a>


)
As I reached the third day of my adventure, I had already learned something new that significantly improves the process: the <code>devcontainer.json</code> file can handle creating and running the Docker container for me. This saves the trouble of manually building and running it—essentially saving two commands on the command line, which adds up to about three seconds. Still, it’s an improvement. However, I am not going to use it.</p>
<p>I had initially planned to solve today’s riddle in <strong>BASIC</strong>—the first programming language I learned 30 years ago. However, it turns out that deploying a BASIC compiler into a container isn</p>
<p>Today, we need to parse a text to find all occurrences of <code>mul(n, m)</code>, calculate the product of <code>n</code> and <code>m</code>, and finally compute the sum of all products. Let&rsquo;s try to find a <em>neat</em> one-liner for that.</p>
<h3 id="solution-of-the-first-part">Solution of the first part</h3>
<p>Here it is:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s2">&#34;input.txt&#34;</span><span class="p">,</span> <span class="s2">&#34;r&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">file</span><span class="p">:</span> <span class="nb">print</span><span class="p">(</span><span class="nb">sum</span><span class="p">([</span><span class="n">reduce</span><span class="p">(</span><span class="n">mul</span><span class="p">,</span> <span class="n">ast</span><span class="o">.</span><span class="n">literal_eval</span><span class="p">(</span><span class="k">match</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">&#34;mul&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">)))</span> <span class="k">for</span> <span class="k">match</span> <span class="ow">in</span> <span class="n">re</span><span class="o">.</span><span class="n">findall</span><span class="p">(</span><span class="s2">&#34;mul\(\d{1,3},\d{1,3}\)&#34;</span><span class="p">,</span> <span class="n">file</span><span class="o">.</span><span class="n">read</span><span class="p">())]))</span>
</span></span></code></pre></div><p>What am I doing? Well, let’s read it from the inside out:</p>
<p>First, we get all occurrences of <code>mul(n, m)</code>, which is quite easy using regex:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">re</span><span class="o">.</span><span class="n">findall</span><span class="p">(</span><span class="s2">&#34;mul\(\d{1,3},\d{1,3}\)</span>
</span></span></code></pre></div><p>Then, we go through all matching groups using Python&rsquo;s list comprehension feature:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">[</span><span class="k">match</span> <span class="k">for</span> <span class="k">match</span> <span class="ow">in</span> <span class="n">re</span><span class="o">.</span><span class="n">findall</span><span class="p">(</span><span class="s2">&#34;mul\(\d{1,3},\d{1,3}\)&#34;</span><span class="p">,</span> <span class="n">file</span><span class="o">.</span><span class="n">read</span><span class="p">())]</span>
</span></span></code></pre></div><p>But we&rsquo;re not just using the plain result, which is a string like <code>mul(4, 2)</code>. First, we remove <code>mul</code> to get the parentheses, then we evaluate the string into an actual tuple, like <code>(4, 2)</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">ast</span><span class="o">.</span><span class="n">literal_eval</span><span class="p">(</span><span class="k">match</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">&#34;mul&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">))</span>
</span></span></code></pre></div><p>Now that we have a list of integers, we can apply multiplication to each item:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">reduce</span><span class="p">(</span><span class="n">mul</span><span class="p">,</span> <span class="p">(</span><span class="mi">4</span><span class="p">,</span> <span class="mi">2</span><span class="p">))</span>
</span></span></code></pre></div><p>Each of the products will be fed into the final list. The <code>sum()</code> method simply adds up all the products in the list. The surrounding <code>with ... as file:</code> simply opens the file to read the content.</p>
<h2 id="solution-of-the-second-part">Solution of the second part</h2>
<p>Well, the second part is a little trickier with three additional rules:</p>
<ul>
<li>Only sum up products of <code>mul()</code> if there&rsquo;s the preceding keyword <code>do()</code>.</li>
<li>Ignore all <code>mul()</code> instructions after the keyword <code>dont()</code>.</li>
<li>All <code>mul()</code> instructions at the beginning, where there&rsquo;s neither a <code>dont()</code> nor a <code>do()</code>, need to be considered, too.</li>
</ul>
<p>This sounds easy at first glance, so I tried to figure out a way to simply adjust my regular expression. With no success, but a ton of frustration. However, I figured out a different way that is way more fun and demonstrates the obscure power of list comprehension:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="nb">sum</span><span class="p">([</span><span class="nb">sum</span><span class="p">([</span><span class="nb">sum</span><span class="p">([</span><span class="n">reduce</span><span class="p">(</span><span class="n">mul</span><span class="p">,</span> <span class="n">ast</span><span class="o">.</span><span class="n">literal_eval</span><span class="p">(</span><span class="k">match</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">&#34;mul&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">)))</span> <span class="k">for</span> <span class="k">match</span> <span class="ow">in</span> <span class="n">re</span><span class="o">.</span><span class="n">findall</span><span class="p">(</span><span class="s2">&#34;mul\(\d{1,3},\d{1,3}\)&#34;</span><span class="p">,</span> <span class="n">dos</span><span class="p">)])</span> <span class="k">for</span> <span class="n">_j</span><span class="p">,</span> <span class="n">dos</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">donts</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;do()&#34;</span><span class="p">)[</span><span class="mi">1</span><span class="p">:])])</span> <span class="k">for</span> <span class="n">_i</span><span class="p">,</span> <span class="n">donts</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">file_contents</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;don&#39;t()&#34;</span><span class="p">))</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">donts</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;do()&#34;</span><span class="p">))</span> <span class="o">&gt;</span> <span class="mi">1</span><span class="p">])</span>
</span></span></code></pre></div><p>I guess you have questions? Fair enough. There you go, this is the expanded version:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">sum_of_all</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">for</span> <span class="n">_i</span><span class="p">,</span> <span class="n">donts</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">((</span><span class="s1">&#39;do()&#39;</span> <span class="o">+</span> <span class="n">file_contents</span><span class="p">)</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;don&#39;t()&#34;</span><span class="p">)):</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">donts</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;do()&#34;</span><span class="p">))</span> <span class="o">&gt;</span> <span class="mi">1</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">        <span class="k">for</span> <span class="n">_j</span><span class="p">,</span> <span class="n">dos</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">donts</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;do()&#34;</span><span class="p">)[</span><span class="mi">1</span><span class="p">:]):</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">            <span class="k">for</span> <span class="k">match</span> <span class="ow">in</span> <span class="n">re</span><span class="o">.</span><span class="n">findall</span><span class="p">(</span><span class="s2">&#34;mul\(\d{1,3},\d{1,3}\)&#34;</span><span class="p">,</span> <span class="n">dos</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">                <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;string: </span><span class="si">{</span><span class="n">match</span><span class="si">}</span><span class="s1"> - list: </span><span class="si">{</span><span class="n">ast</span><span class="o">.</span><span class="n">literal_eval</span><span class="p">(</span><span class="n">match</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">&#34;mul&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">))</span><span class="si">}</span><span class="s1"> -  product: </span><span class="si">{</span><span class="n">reduce</span><span class="p">(</span><span class="n">mul</span><span class="p">,</span> <span class="n">ast</span><span class="o">.</span><span class="n">literal_eval</span><span class="p">(</span><span class="n">match</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">&#34;mul&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">)))</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">                <span class="n">sum_of_all</span> <span class="o">+=</span> <span class="n">reduce</span><span class="p">(</span><span class="n">mul</span><span class="p">,</span> <span class="n">ast</span><span class="o">.</span><span class="n">literal_eval</span><span class="p">(</span><span class="k">match</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">&#34;mul&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">)))</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">
</span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">&#39;sum: </span><span class="si">{</span><span class="n">sum_of_all</span><span class="si">}</span><span class="s1">&#39;</span><span class="p">)</span>
</span></span></code></pre></div><p>The idea is that I simply split the whole text by the string <code>dont()</code> in the first <code>for</code> loop. This returns a list of items, where I know that every <code>mul()</code> must be ignored until I encounter a <code>do()</code> in the string.</p>
<p>This leads to the next operation: Instead of parsing the string, I split each of the strings by the keyword <code>do()</code>. If this string contains <code>do()</code>, I get a list of 2 items. If not, I can ignore the whole string in my outer loop.</p>
<p>Otherwise, I take all items except the first one, and now I can apply the logic from above: using the regular expression to get the list of integers, then multiplying and summing up.</p>
<p>There&rsquo;s a small pitfall: The third rule says that all instructions at the beginning of the string, until the first <code>do()</code> or <code>dont()</code>, need to be considered. The simplest way to handle this is by just adding <code>do()</code> to the initial input string.</p>
<p>In case you&rsquo;re wondering how to go from the seemingly simple connection of nested loops to this nasty nested list comprehension—you achieve it step by step:</p>
<p>First, we iterate through the raw file content:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">[</span><span class="n">donts</span> <span class="k">for</span> <span class="n">_i</span><span class="p">,</span> <span class="n">donts</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">((</span><span class="s1">&#39;do()&#39;</span> <span class="o">+</span> <span class="n">file_contents</span><span class="p">)</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;don&#39;t()&#34;</span><span class="p">))]</span>
</span></span></code></pre></div><p>Then, we replace <code>dont()</code> with the next loop:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">[[</span><span class="n">dos</span> <span class="k">for</span> <span class="n">_j</span><span class="p">,</span> <span class="n">dos</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">donts</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;do()&#34;</span><span class="p">)[</span><span class="mi">1</span><span class="p">:])]</span> <span class="k">for</span> <span class="n">_i</span><span class="p">,</span> <span class="n">donts</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">((</span><span class="s1">&#39;do()&#39;</span> <span class="o">+</span> <span class="n">file_contents</span><span class="p">)</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;don&#39;t()&#34;</span><span class="p">))]</span>
</span></span></code></pre></div><p>Don&rsquo;t forget to add a condition to skip results with a length of 1:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">[[</span><span class="n">dos</span> <span class="k">for</span> <span class="n">_j</span><span class="p">,</span> <span class="n">dos</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">donts</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;do()&#34;</span><span class="p">)[</span><span class="mi">1</span><span class="p">:])]</span> <span class="k">for</span> <span class="n">_i</span><span class="p">,</span> <span class="n">donts</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">((</span><span class="s1">&#39;do()&#39;</span> <span class="o">+</span> <span class="n">file_contents</span><span class="p">)</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;don&#39;t()&#34;</span><span class="p">))</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">donts</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;do()&#34;</span><span class="p">))</span> <span class="o">&gt;</span> <span class="mi">1</span><span class="p">]</span>
</span></span></code></pre></div><p>Then, we can replace <code>do()</code> with the following loop:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">[[[</span><span class="k">match</span> <span class="k">for</span> <span class="k">match</span> <span class="ow">in</span> <span class="n">re</span><span class="o">.</span><span class="n">findall</span><span class="p">(</span><span class="s2">&#34;mul\(\d{1,3},\d{1,3}\)&#34;</span><span class="p">,</span> <span class="n">dos</span><span class="p">)]</span> <span class="k">for</span> <span class="n">_j</span><span class="p">,</span> <span class="n">dos</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">donts</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;do()&#34;</span><span class="p">)[</span><span class="mi">1</span><span class="p">:])]</span> <span class="k">for</span> <span class="n">_i</span><span class="p">,</span> <span class="n">donts</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">((</span><span class="s1">&#39;do()&#39;</span> <span class="o">+</span> <span class="n">file_contents</span><span class="p">)</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;don&#39;t()&#34;</span><span class="p">))</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">donts</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;do()&#34;</span><span class="p">))</span> <span class="o">&gt;</span> <span class="mi">1</span><span class="p">]</span>
</span></span></code></pre></div><p>Now we are at the point where we handle the matching groups, so we replace <code>match</code> with everything we need to get a product out of the stringified list of integers:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">[[[</span><span class="n">reduce</span><span class="p">(</span><span class="n">mul</span><span class="p">,</span> <span class="n">ast</span><span class="o">.</span><span class="n">literal_eval</span><span class="p">(</span><span class="k">match</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">&#34;mul&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">)))</span> <span class="k">for</span> <span class="k">match</span> <span class="ow">in</span> <span class="n">re</span><span class="o">.</span><span class="n">findall</span><span class="p">(</span><span class="s2">&#34;mul\(\d{1,3},\d{1,3}\)&#34;</span><span class="p">,</span> <span class="n">dos</span><span class="p">)]</span> <span class="k">for</span> <span class="n">_j</span><span class="p">,</span> <span class="n">dos</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">donts</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;do()&#34;</span><span class="p">)[</span><span class="mi">1</span><span class="p">:])]</span> <span class="k">for</span> <span class="n">_i</span><span class="p">,</span> <span class="n">donts</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">((</span><span class="s1">&#39;do()&#39;</span> <span class="o">+</span> <span class="n">file_contents</span><span class="p">)</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;don&#39;t()&#34;</span><span class="p">))</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">donts</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;do()&#34;</span><span class="p">))</span> <span class="o">&gt;</span> <span class="mi">1</span><span class="p">]</span>
</span></span></code></pre></div><p>And eventually, we get the sum of each nested list:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="nb">sum</span><span class="p">([</span><span class="nb">sum</span><span class="p">([</span><span class="nb">sum</span><span class="p">([</span><span class="n">reduce</span><span class="p">(</span><span class="n">mul</span><span class="p">,</span> <span class="n">ast</span><span class="o">.</span><span class="n">literal_eval</span><span class="p">(</span><span class="k">match</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">&#34;mul&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">)))</span> <span class="k">for</span> <span class="k">match</span> <span class="ow">in</span> <span class="n">re</span><span class="o">.</span><span class="n">findall</span><span class="p">(</span><span class="s2">&#34;mul\(\d{1,3},\d{1,3}\)&#34;</span><span class="p">,</span> <span class="n">dos</span><span class="p">)])</span> <span class="k">for</span> <span class="n">_j</span><span class="p">,</span> <span class="n">dos</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">donts</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;do()&#34;</span><span class="p">)[</span><span class="mi">1</span><span class="p">:])])</span> <span class="k">for</span> <span class="n">_i</span><span class="p">,</span> <span class="n">donts</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">((</span><span class="s1">&#39;do()&#39;</span> <span class="o">+</span> <span class="n">file_contents</span><span class="p">)</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;don&#39;t()&#34;</span><span class="p">))</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">donts</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;do()&#34;</span><span class="p">))</span> <span class="o">&gt;</span> <span class="mi">1</span><span class="p">])</span>
</span></span></code></pre></div><p>Looks great, doesn&rsquo;t it?</p>
<h2 id="whats-up-python">Whats up, Python?</h2>
<p><strong>Rating: 14/12 – nice and friendly, always again</strong></p>
<p>Uncontroversial—one of Python&rsquo;s best features is list comprehensions, which also work for dictionaries, sets, and tuples, by the way. List comprehensions have so many features; besides the way you can nest them, you can add conditions in two ways. Either when you add an item to the output list…</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">[</span><span class="n">x</span> <span class="k">if</span> <span class="n">x</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">0</span> <span class="k">else</span> <span class="o">-</span><span class="n">x</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">)]</span>
</span></span></code></pre></div><p>…or before:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">[</span><span class="n">x</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span> <span class="k">if</span> <span class="n">x</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">0</span><span class="p">]</span>
</span></span></code></pre></div><p>You can loop through multiple lists (et al):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="p">[(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span> <span class="k">for</span> <span class="n">y</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">2</span><span class="p">)]</span>
</span></span></code></pre></div><p>You can not only nest them, but also serialize multiple loops to flatten a nested input:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">nested</span> <span class="o">=</span> <span class="p">[[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">],</span> <span class="p">[</span><span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">],</span> <span class="p">[</span><span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">]]</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="p">[</span><span class="n">x</span> <span class="k">for</span> <span class="n">sublist</span> <span class="ow">in</span> <span class="n">nested</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">sublist</span><span class="p">]</span>
</span></span></code></pre></div><p>And there are probably a couple more that I don&rsquo;t even know.</p>
<p>See you next day…</p>]]></content:encoded><atom:updated>2026-06-16T20:56:44+02:00</atom:updated><category>development</category><category>aoc2024</category><media:content url="https://nickyreinert.de/images/posts/placeholder.jpg" type="image/jpeg"><media:title>Nicky Reinert</media:title></media:content><dc:subject>Lesezeit: 5 Minuten</dc:subject><dc:type>solution_writeup</dc:type></item><item><title>Advent of Code - Day 2 - Increasing and decreasing (Rust)</title><link>https://nickyreinert.de/en/2024/2024-12-02-aoc-day-2/</link><pubDate>Mon, 02 Dec 2024 12:34:56 +0200</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/en/2024/2024-12-02-aoc-day-2/</guid><description>( task | solution )
This task is about testing if integers in a list are increasing or decreasing. I’ll try Rust, and I assume I need nerves of steel for this exercise.</description><content:encoded><![CDATA[<p>(





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://adventofcode.com/2024/day/2/?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">task</a>


 | 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://github.com/nickyreinert/aoc2024/tree/main/solutions/day2%20-%20rust?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">solution</a>


)</p>
<p>





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://adventofcode.com/2024/day/2?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">This task</a>


 is about testing if integers in a list are increasing or decreasing. I&rsquo;ll try <code>Rust</code>, and I assume I need nerves of steel for this exercise.</p>
<h2 id="setting-up-the-development-environment">Setting Up the Development Environment</h2>
<p>Rust comes in a container, too. Here&rsquo;s the Dockerfile:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dockerfile" data-lang="dockerfile"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c"># Dockerfile</span><span class="err">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">FROM</span><span class="w"> </span><span class="s">rust:latest</span><span class="err">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c"># Install additional tools you might need</span><span class="err">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">RUN</span> apt-get update <span class="o">&amp;&amp;</span> apt-get install -y <span class="se">\
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl">    git <span class="se">\
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl">    curl <span class="se">\
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl">    wget <span class="se">\
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl">    build-essential<span class="err">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c"># Set the working directory</span><span class="err">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="k">WORKDIR</span><span class="w"> </span><span class="s">/workspace</span><span class="err">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="c"># Copy project files (if any)</span><span class="err">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">COPY</span> . .<span class="err">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="c"># Default command</span><span class="err">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="k">CMD</span> <span class="p">[</span><span class="s2">&#34;bash&#34;</span><span class="p">]</span><span class="err">
</span></span></span></code></pre></div><p>And here&rsquo;s the <code>devcontainer.json</code> to develop inside that container made of steel:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln"> 1</span><span class="cl">{
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    &#34;name&#34;: &#34;Rust Development Container&#34;,
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    &#34;build&#34;: {
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        &#34;dockerfile&#34;: &#34;../Dockerfile&#34;
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    },
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    &#34;customizations&#34;: {
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        &#34;vscode&#34;: {
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">            &#34;extensions&#34;: [
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">                &#34;rust-lang.rust-analyzer&#34;,
</span></span><span class="line"><span class="ln">10</span><span class="cl">                &#34;serayuzgur.crates&#34;,
</span></span><span class="line"><span class="ln">11</span><span class="cl">                &#34;tamasfe.even-better-toml&#34;
</span></span><span class="line"><span class="ln">12</span><span class="cl">            ]
</span></span><span class="line"><span class="ln">13</span><span class="cl">        }
</span></span><span class="line"><span class="ln">14</span><span class="cl">    },
</span></span><span class="line"><span class="ln">15</span><span class="cl">    &#34;remoteUser&#34;: &#34;root&#34;,
</span></span><span class="line"><span class="ln">16</span><span class="cl">    &#34;features&#34;: {
</span></span><span class="line"><span class="ln">17</span><span class="cl">        &#34;ghcr.io/devcontainers/features/rust:1&#34;: {}
</span></span><span class="line"><span class="ln">18</span><span class="cl">    }
</span></span><span class="line"><span class="ln">19</span><span class="cl">}
</span></span></code></pre></div><p>Rust also requires a <code>Cargo.toml</code> file that defines your project and dependencies:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl"># Cargo.toml
</span></span><span class="line"><span class="ln">2</span><span class="cl">[package]
</span></span><span class="line"><span class="ln">3</span><span class="cl">name = &#34;rust-docker-project&#34;
</span></span><span class="line"><span class="ln">4</span><span class="cl">version = &#34;0.1.0&#34;
</span></span><span class="line"><span class="ln">5</span><span class="cl">edition = &#34;2024&#34;
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl">[dependencies]
</span></span><span class="line"><span class="ln">8</span><span class="cl"># none, ehehe
</span></span></code></pre></div><p>That&rsquo;s all we need. Build it with <code>docker build</code> and connect VS Code. The source file is located inside the <code>src</code> folder. You can run your code like this</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">cargo build
</span></span><span class="line"><span class="ln">2</span><span class="cl">cargo run
</span></span></code></pre></div><p>The algorithm itself isn&rsquo;t that tricky. 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://github.com/nickyreinert/aoc2024/tree/main/solutions/day2%20-%20rust/src?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">You&rsquo;ll find everything on Github</a>


.</p>
<h2 id="whats-up-rust">Whats up, Rust?</h2>
<p><strong>Rating: 4/12 – Wouldn&rsquo;t use it freely</strong></p>
<p>Don&rsquo;t get me started. I didn&rsquo;t know that Rust has so many strange rules. There’s this interesting concept of <strong>ownership</strong>: values belong to a variable as long as you don&rsquo;t assign that variable to another variable. If you pass a value from one vector to another, you can&rsquo;t access it in the original vector anymore.</p>
<p>And then there are many important operators scattered across the code, like:</p>
<ul>
<li><code>&amp;</code> for <strong>borrowing</strong> (referencing a value without taking ownership)</li>
<li><code>*</code> for <strong>de-referencing</strong> (accessing the value a reference points to)</li>
</ul>
<p>What I <em>do</em> like, though, is the error handling. Rust uses <code>Ok</code> and <code>Err</code> to make handling errors much cleaner and explicit:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-rust" data-lang="rust"><span class="line"><span class="ln">1</span><span class="cl"><span class="nb">Err</span><span class="p">(</span><span class="s">&#34;Value not valid&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nb">Ok</span><span class="p">(())</span><span class="w">
</span></span></span></code></pre></div><p>This approach forces you to handle potential issues early on, which is great for reliability.</p>
<p>See you next day…</p>]]></content:encoded><atom:updated>2026-06-16T20:56:44+02:00</atom:updated><category>development</category><category>aoc2024</category><media:content url="https://nickyreinert.de/images/posts/placeholder.jpg" type="image/jpeg"><media:title>Nicky Reinert</media:title></media:content><dc:subject>Lesezeit: 5 Minuten</dc:subject><dc:type>solution_writeup</dc:type></item><item><title>Advent of Code - Day 1 - Differences and Products (F#)</title><link>https://nickyreinert.de/en/2024/2024-12-01-aoc-day-1/</link><pubDate>Sun, 01 Dec 2024 12:34:56 +0200</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/en/2024/2024-12-01-aoc-day-1/</guid><description>( task | solution )
As the challenge at AoC is not about the syntax but the algorithm, I decided to try a different approach this year: a new language every day. I am probably not going to explain the code in detail, but I will point out characteristics of each particular language that I think are …</description><content:encoded><![CDATA[<p>(





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://adventofcode.com/2024/day/1/?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">task</a>


 | 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://github.com/nickyreinert/aoc2024/tree/main/solutions/day1%20-%20f%23?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">solution</a>


)</p>
<p>As the challenge at 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://adventofcode.com/?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">AoC</a>


 is not about the syntax but the algorithm, I decided to try a different approach this year: <strong>a new language every day</strong>. I am probably not going to explain the code in detail, but I will point out characteristics of each particular language that I think are worth mentioning. Let&rsquo;s see how far I get. Please don&rsquo;t take my judgement to serious.</p>
<blockquote>
<p>Disclaimer: Of course, I am using AI to get the actual syntax, but not for the algorithm itself. I usually use ClaudeAI for that, as I&rsquo;ve experienced the best results with the free version there.</p>
</blockquote>
<p>On the first day, I&rsquo;m starting with <code>F#</code> — the sister of <code>C#</code> — which focuses on functional programming.</p>
<h2 id="setting-up-the-development-environment">Setting Up the Development Environment</h2>
<p>First, I need to set up my development environment. Since I don’t want to pollute my system with tons of different interpreters, I prefer to work in Docker containers. Above all, this requires 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers&amp;utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">the Dev Containers extension</a>


. To make VS Code ready for F#, I also need 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://marketplace.visualstudio.com/items?itemName=Ionide.Ionide-fsharp&amp;utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">the Ionide extension</a>


.</p>
<p>Here is the Dockerfile for a containerized F# environment:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># Use official .NET SDK image with F# support</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">FROM</span> <span class="n">mcr</span><span class="o">.</span><span class="n">microsoft</span><span class="o">.</span><span class="n">com</span><span class="o">/</span><span class="n">dotnet</span><span class="o">/</span><span class="n">sdk</span><span class="p">:</span><span class="mf">8.0</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># Install additional tools</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">RUN</span> <span class="n">apt</span><span class="o">-</span><span class="n">get</span> <span class="n">update</span> <span class="o">&amp;&amp;</span> \
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">apt</span><span class="o">-</span><span class="n">get</span> <span class="n">install</span> <span class="o">-</span><span class="n">y</span> \
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">git</span> \
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">curl</span> \
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">vim</span> \
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">sudo</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="c1"># Create vscode user with sudo permissions</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="n">RUN</span> <span class="n">groupadd</span> <span class="o">-</span><span class="n">r</span> <span class="n">vscode</span> \
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="o">&amp;&amp;</span> <span class="n">useradd</span> <span class="o">-</span><span class="n">m</span> <span class="o">-</span><span class="n">s</span> <span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">bash</span> <span class="o">-</span><span class="n">g</span> <span class="n">vscode</span> <span class="o">-</span><span class="n">G</span> <span class="n">sudo</span> <span class="n">vscode</span> \
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="o">&amp;&amp;</span> <span class="n">echo</span> <span class="s2">&#34;vscode ALL=(ALL) NOPASSWD:ALL&#34;</span> <span class="o">&gt;&gt;</span> <span class="o">/</span><span class="n">etc</span><span class="o">/</span><span class="n">sudoers</span><span class="o">.</span><span class="n">d</span><span class="o">/</span><span class="n">vscode</span> \
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="o">&amp;&amp;</span> <span class="n">chmod</span> <span class="mi">0440</span> <span class="o">/</span><span class="n">etc</span><span class="o">/</span><span class="n">sudoers</span><span class="o">.</span><span class="n">d</span><span class="o">/</span><span class="n">vscode</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c1"># Set working directory and ensure proper ownership</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="n">WORKDIR</span> <span class="o">/</span><span class="n">app</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="n">RUN</span> <span class="n">chown</span> <span class="o">-</span><span class="n">R</span> <span class="n">vscode</span><span class="p">:</span><span class="n">vscode</span> <span class="o">/</span><span class="n">app</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">
</span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="c1"># Ensure .NET tools are in PATH</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="n">ENV</span> <span class="n">PATH</span><span class="o">=</span><span class="s2">&#34;/home/vscode/.dotnet/tools:${PATH}&#34;</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">
</span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="c1"># Set the default user to vscode</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="n">USER</span> <span class="n">vscode</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="c1"># Ensure .NET tools are installed for the vscode user</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl"><span class="n">RUN</span> <span class="n">dotnet</span> <span class="k">tool</span> <span class="n">list</span> <span class="o">--</span><span class="n">global</span> <span class="o">||</span> <span class="bp">true</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">
</span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="c1"># Default command</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="n">CMD</span> <span class="p">[</span><span class="s2">&#34;/bin/bash&#34;</span><span class="p">]</span>
</span></span></code></pre></div><p>Developing inside a container requires /.devcontainer/devcontainer.json to look like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;F# Dev Environment&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="s2">&#34;build&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        <span class="s2">&#34;dockerfile&#34;</span><span class="p">:</span> <span class="s2">&#34;../Dockerfile&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        <span class="s2">&#34;context&#34;</span><span class="p">:</span> <span class="s2">&#34;..&#34;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="s2">&#34;remoteUser&#34;</span><span class="p">:</span> <span class="s2">&#34;vscode&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="s2">&#34;workspaceFolder&#34;</span><span class="p">:</span> <span class="s2">&#34;/app&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="s2">&#34;customizations&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">        <span class="s2">&#34;vscode&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">            <span class="s2">&#34;extensions&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">                <span class="s2">&#34;Ionide.ionide-fsharp&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">                <span class="s2">&#34;ms-dotnettools.csharp&#34;</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">            <span class="p">]</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="s2">&#34;mounts&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        <span class="s2">&#34;source=${localWorkspaceFolder},target=/app,type=bind,consistency=cached&#34;</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">    <span class="p">],</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="s2">&#34;containerUser&#34;</span><span class="p">:</span> <span class="s2">&#34;vscode&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">    <span class="s2">&#34;remoteEnv&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">        <span class="s2">&#34;DOTNET_CLI_HOME&#34;</span><span class="p">:</span> <span class="s2">&#34;/app&#34;</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Now, after building and running the container with <code>docker-compose build; docker-compose up -d</code>, I can connect to the container by either pressing <code>CMD+SHIFT+P</code> or using the small blue button on the bottom left, selecting the option &ldquo;<em>Connect to Dev Container.</em>&rdquo;</p>
<h2 id="day-1---about-differences-and-products">Day 1 - About differences and products</h2>
<p>(





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://adventofcode.com/2024/day/1?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">task</a>


 - 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://github.com/nickyreinert/aoc2024/blob/main/solutions/day1.fsx?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">solution</a>


)</p>
<p>The first day consists of two simple tasks, so I won’t go into too much detail:</p>
<ol>
<li>Order two lists incrementally, then get the difference for each list item and sum all differences up.</li>
<li>For each item on the first list, count the occurrences on the second list, compute the product <code>i * frequency(i)</code>, and then get the sum of all products.</li>
</ol>
<h2 id="whats-up-f">Whats up, F#?</h2>
<p><em>8 of 12, would like to stay in touch</em></p>
<p>Well, I certainly havent had the chance to get familar with F#, but at the first glance I like how you can connect output to adjecent commands using the pipe operator:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">let readListFromFile (filePath: string) =
</span></span><span class="line"><span class="ln">2</span><span class="cl">    File.ReadAllLines(filePath) 
</span></span><span class="line"><span class="ln">3</span><span class="cl">    |&gt; Array.map int
</span></span><span class="line"><span class="ln">4</span><span class="cl">    |&gt; Array.toList
</span></span></code></pre></div><p>This makes things quiete easy, resulting in a clearer and more concise coding style. Seems like F# is a good choice for hands one problem solving.</p>
<p>See you next day…</p>]]></content:encoded><atom:updated>2026-06-16T20:56:44+02:00</atom:updated><category>development</category><category>aoc2024</category><media:content url="https://nickyreinert.de/images/posts/placeholder.jpg" type="image/jpeg"><media:title>Nicky Reinert</media:title></media:content><dc:subject>Lesezeit: 5 Minuten</dc:subject><dc:type>solution_writeup</dc:type></item><item><title>sudoku</title><link>https://nickyreinert.de/en/2024/2024-10-18-sudoku/</link><pubDate>Thu, 17 Oct 2024 12:34:56 +0100</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/en/2024/2024-10-18-sudoku/</guid><description>How to build a brute force Sudoku solver Follow my journey on how to build an algorithm that lets you create or solve Sudoku puzzles!
Follow my steps or jump ahead — this is the Jupyter notebook: https://gist.github.com/nickyreinert/966299333429ef685338e5feb5056126 Warmup — introduction Before we …</description><content:encoded><![CDATA[<h3 id="how-to-build-a-brute-force-sudoku-solver">How to build a brute force Sudoku solver</h3>
<p>Follow my journey on how to build an algorithm that lets you create or solve Sudoku puzzles!</p>
<blockquote>
<p>Follow my steps or jump ahead — this is the Jupyter notebook: 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://gist.github.com/nickyreinert/966299333429ef685338e5feb5056126?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">https://gist.github.com/nickyreinert/966299333429ef685338e5feb5056126</a>


</p>
</blockquote>
<h1 id="warmup--introduction"><strong>Warmup — introduction</strong></h1>
<p>Before we start, we need to get familiar with how <strong>Sudoku</strong> works. I suggest we number each cell in the giant grid from <code>1</code> to <code>81</code>. This way, we can programmatically loop <strong>from the top-left to the bottom-right</strong> to find valid solutions:</p>
<img src="https://nickyreinert.de/en/2024/2024-10-18-sudoku/sudoku_positions.png" alt="sudoku positions"><p><strong>Sudoku</strong> has three rules: a number can’t exist twice in the same <strong>column</strong>, <strong>row</strong>, or <strong>section</strong>. So, first, we need a way to identify the column, row, and section for each position.</p>
<p>Every row contains <code>9</code> cells, so the row index is simply:</p>
<pre><code>ceil(position / 9)
</code></pre>
<p>Or if you prefer double slash (<code>//</code>), which works like a “floor” function:</p>
<pre><code>((position — 1) // 9) + 1
</code></pre>
<p>Columns probably also have something to do with <code>9</code> (because <code>9</code> is kind of a big deal in Sudoku). The first nine cells are exactly addressing their corresponding columns, obviously. So, in the upcoming rows (positions <code>10</code> and onward), we just need to reduce it back to <code>1</code> to <code>9</code>. We can use <strong>modulo</strong> for that!</p>
<pre><code>((position — 1) % 9) + 1
</code></pre>
<p>The section index is a little trickier. It’s a combo of the row and column index and some tweaks. There’s probably a clever mathematical approach, or you can just trial-and-error your way through (my usual strategy). Here’s a starting point:</p>
<img src="https://nickyreinert.de/en/2024/2024-10-18-sudoku/sudoku_cols_and_rows.png" alt="sudoku columns and rows"><p>We take the floor of a third of each position now. It seems like we could just sum them to get the fitting section index, with a little adjustment. Here’s the magic:</p>
<img src="https://nickyreinert.de/en/2024/2024-10-18-sudoku/sudouku_from_cols_and_rows_to_sections.png" alt="sudouku from cols and rows to sections"><p>And that’s the formula to get the section index:</p>
<pre><code>(col // 3) + (((row // 3) — 1) * 3)
</code></pre>
<h1 id="first-steps">First steps</h1>
<p>Well, now that we know how to address each row, column, and section, let’s dive into the actual algorithm.</p>
<h2 id="solution-approach">Solution approach</h2>
<p>The basic idea is to <strong>iterate through the positions</strong> up to <code>81</code>. At every step, we choose a number from <code>1</code> to <code>9</code> and check if it already exists in the corresponding row, column, or section (we’ll call these <strong>containers</strong>). If it does, the number is <strong>blocked</strong> for the current position, and we have to check the next number. If we can’t find a valid number for the current position, we need to <strong>roll back</strong> to the previous one and choose a <strong>different number</strong>. We repeat this process until we either reach position <code>81</code> — meaning we’ve found a valid Sudoku — or have to backtrack more.</p>
<h2 id="implementation-in-python">Implementation in Python</h2>
<p>We start by initializing four dictionaries to keep track of the numbers in each <strong>container</strong>. The <code>solutions</code> dictionary holds (surprise!) the valid numbers for the entire grid.</p>
<pre><code>def reset_containers():  
    global rows, cols, sections, solutions  
    rows = {i: [] for i in range(1, 10)}  
    cols = {i: [] for i in range(1, 10)}  
    sections = {i: [] for i in range(1, 10)}  
    solutions = {}  

reset_containers()  

print(sections, '\n', rows, '\n', cols)
</code></pre>
<p>Result:</p>
<pre><code>{1: [], 2: [], 3: [], 4: [], 5: [], 6: [], 7: [], 8: [], 9: []}   
{1: [], 2: [], 3: [], 4: [], 5: [], 6: [], 7: [], 8: [], 9: []}   
{1: [], 2: [], 3: [], 4: [], 5: [], 6: [], 7: [], 8: [], 9: []}
</code></pre>
<p>The <code>visualize</code> function takes the solution dictionary and returns a nice grid, displaying the numbers — you&rsquo;ll find it in the notebook I referenced at the top!</p>
<pre><code>def visualize(solution):  
    # see Jupyter notebook referenced on the top
</code></pre>
<p>We’ve already figured out how to get the actual <strong>row</strong>, <strong>column</strong>, or <strong>section</strong> from a given position. Now, let’s translate into Python and check if we’re on the right track:</p>
<pre><code>print('pos', '\t', 'row', '\t', 'col', '\t', 'section')  
for position in range(1, 82):  
    idx_row = math.ceil(position / 9)  
    idx_col = ((position - 1) % 9) + 1  
    idx_section = math.ceil(idx_col/3) + ((math.ceil(idx_row/3) - 1) * 3)  
    print(position, '\t', idx_row, '\t', idx_col, '\t', idx_section)
</code></pre>
<p>Output:</p>
<pre><code>pos   row   col   section  
1   1   1   1  
2   1   2   1  
3   1   3   1  
4   1   4   2  
5   1   5   2  
6   1   6   2  
7   1   7   3  
8   1   8   3  
9   1   9   3  
10   2   1   1  
…
</code></pre>
<p>Yes, we are! Every position is correctly mapped to the correct <strong>container</strong>!</p>
<h2 id="solution-checking-and-iterating">Solution checking and iterating</h2>
<p>The first version is quite simple: We iterate through the positions and add a number that does not exist in one of the corresponding containers.</p>
<pre><code>reset_containers()  

number = 1  

for position in range(1, 82):  
    idx_row = math.ceil(position / 9)  
    idx_col = ((position - 1) % 9) + 1  
    idx_section = math.ceil(idx_col/3) + ((math.ceil(idx_row/3) - 1) * 3)  
    if (number not in rows[idx_row] and number not in cols[idx_col] and number not in sections[idx_section]):  
        solutions[position] = number  
        rows[idx_row].append(number)  
        cols[idx_col].append(number)  
        sections[idx_section].append(number)  
        number += 1  

visualize(solutions)
</code></pre>
<p>Seems like our basic loop is working fine. Let’s add some fine-tuning and limit it to the numbers from 1 to 9. We can just introduce an inner loop to keep things simple:</p>
<p>Output:</p>
<img src="https://nickyreinert.de/en/2024/2024-10-18-sudoku/suduko_first_attempt.png" alt="suduko first attempt"><p>Seems like our basic loop is working fine. Let’s add some fine-tuning and limit it to the numbers from 1 to 9. We can just introduce an inner loop to keep things simple:</p>
<pre><code>reset_containers()  

number = 1  

for position in range(1, 82):  
    idx_row = math.ceil(position / 9)  
    idx_col = ((position - 1) % 9) + 1  
    idx_section = math.ceil(idx_col/3) + ((math.ceil(idx_row/3) - 1) * 3)  
    for number in range(1, 10):  
        if (number not in rows[idx_row] and number not in cols[idx_col] and number not in sections[idx_section]):  
            solutions[position] = number  
            rows[idx_row].append(number)  
            cols[idx_col].append(number)  
            sections[idx_section].append(number)  
            break  

visualize(solutions)
</code></pre>
<p>Output:</p>
<img src="https://nickyreinert.de/en/2024/2024-10-18-sudoku/sudoku_second_attempt.png" alt="sudoku second attempt"><p>There are a lot of empty spots in the grid, what are we missing? Let’s introduce a conditional that stops when there’s no solution:</p>
<pre><code>reset_containers()  

number = 1  

for position in range(1, 82):  
    idx_row = math.ceil(position / 9)  
    idx_col = ((position - 1) % 9) + 1  
    idx_section = math.ceil(idx_col/3) + ((math.ceil(idx_row/3) - 1) * 3)  
    for number in range(1, 10):  
        if (number not in rows[idx_row] and number not in cols[idx_col] and number not in sections[idx_section]):  
            solutions[position] = number  
            rows[idx_row].append(number)  
            cols[idx_col].append(number)  
            sections[idx_section].append(number)  
            break  

    if not position in solutions:  
        break  

visualize(solutions)
</code></pre>
<p>Output:</p>
<img src="https://nickyreinert.de/en/2024/2024-10-18-sudoku/sudoku_third_attempt.png" alt="sudoku third attempt"><p>Quite good, but just stopping the loop when we hit a dead end probably won’t get us to a solution. We need to keep track of dead ends, and we need the ability to go back, choose another path, and try again. Let’s start with a container that holds “impossible” paths for each of our 81 positions.</p>
<p>We’ll also switch from a limited <code>for-loop</code> to an &ldquo;infinite&rdquo; <code>while-loop</code> because now we’re not just incrementing from 1 to 81 — we’ll be going back and forth. Of course, it’s always a good idea to use a stop condition, like <code>max_iterations</code>, so we don’t accidentally run into an actual endless loop when using <code>while</code>.</p>
<p>Let’s put all of that into a new “big” init function!</p>
<pre><code>def reset_containers():  
    global rows, cols, sections, solutions, dead_ends  
    rows = {i: [] for i in range(1, 10)}  
    cols = {i: [] for i in range(1, 10)}  
    sections = {i: [] for i in range(1, 10)}  
    solutions = {}  
    dead_ends = {i: [] for i in range(1, 82)}
</code></pre>
<p>Now, if the current iteration doesn’t result in a solution, we don’t just stop processing. We go back one step:</p>
<pre><code>position -= 1
</code></pre>
<p>And we archive the solution from the previous position as a “dead end solution”:</p>
<pre><code>dead_ends[position].append(solutions[position])
</code></pre>
<p>Then we have to update our containers, as the solution is no longer valid:</p>
<pre><code>rows[idx_row].remove(solutions[position])  
    cols[idx_col].remove(solutions[position])  
    sections[idx_section].remove(solutions[position])  
    del solutions[position]
</code></pre>
<p>Let’s see how this performs!</p>
<p>reset_containers()</p>
<pre><code>print('iter', '\t', 'pos', '\t', 'row', '\t', 'col', '\t', 'section', '\t\t', 'number')  

iteration = 1  
position = 1  
max_iterations = 10_000  
stop = False  

while True:  
    idx_row = math.ceil(position / 9)  
    idx_col = ((position - 1) % 9) + 1  
    idx_section = math.ceil(idx_col/3) + ((math.ceil(idx_row/3) - 1) * 3)  
    for number in range(1, 10):  
        print(iteration, '\t', position, '\t', idx_row, '\t', idx_col, '\t', idx_section, '\t\t', number)  

        iteration += 1  
        if (iteration &gt; max_iterations): stop = True; break  

        if (number not in rows[idx_row] and \  
            number not in cols[idx_col] and \  
            number not in sections[idx_section] and \  
            number not in dead_ends[position]):  
            solutions[position] = number  
            rows[idx_row].append(number)  
            cols[idx_col].append(number)  
            sections[idx_section].append(number)  
            break  

    if stop: break  

    if not position in solutions:  

        position -= 1  
        if (position &lt;= 0): stop = True; break  

        dead_ends[position].append(solutions[position])  

        print(f'rolling back {solutions[position]}')  

        idx_row = math.ceil(position / 9)  
        idx_col = ((position - 1) % 9) + 1  
        idx_section = math.ceil(idx_col/3) + ((math.ceil(idx_row/3) - 1) * 3)  

        rows[idx_row].remove(solutions[position])  
        cols[idx_col].remove(solutions[position])  
        sections[idx_section].remove(solutions[position])  
        del solutions[position]  
    
    else:  
        position += 1  

    if (position &gt; 81): break  

print(f'Done after {iteration - 1:,.0f} iterations.')  

visualize(solutions)
</code></pre>
<p>Output:</p>
<img src="https://nickyreinert.de/en/2024/2024-10-18-sudoku/sudoku_fail.png" alt="sudoku fail"><p>An empty grid? Why for flux sake? Well, our housekeeping is incomplete — we didn’t properly clean up the dead end container:</p>
<pre><code>dead_ends[position] = []
</code></pre>
<p>Let’s take this chance to adjust our container reset function. It will now reset all containers or just the ones for a given position only:</p>
<pre><code>def reset_containers(position = None):  
    global rows, cols, sections, solutions, dead_ends  

    if position is None:  
        rows = {i: [] for i in range(1, 10)}  
        cols = {i: [] for i in range(1, 10)}  
        sections = {i: [] for i in range(1, 10)}  
        solutions = {}  
        dead_ends = {i: [] for i in range(1, 82)}  
    else:  
        idx_row = math.ceil(position / 9)  
        idx_col = ((position - 1) % 9) + 1  
        idx_section = math.ceil(idx_col/3) + ((math.ceil(idx_row/3) - 1) * 3)  

        dead_ends[position].append(solutions[position])  
        rows[idx_row].remove(solutions[position])  
        cols[idx_col].remove(solutions[position])  
        sections[idx_section].remove(solutions[position])  
        del solutions[position]
</code></pre>
<p>And now the main loop:</p>
<pre><code>def solve_game(max_iterations = 10_000):  

    global rows, cols, sections, solutions, dead_ends  
    
    reset_containers()  

    iteration = 1  
    position = 1  
    stop = False  

    # print('iter', '\t', 'pos', '\t', 'row', '\t', 'col', '\t', 'section', '\t\t', 'number')  

    while True:  
        idx_row = math.ceil(position / 9)  
        idx_col = ((position - 1) % 9) + 1  
        idx_section = math.ceil(idx_col/3) + ((math.ceil(idx_row/3) - 1) * 3)  
        for number in range(1, 10):  
            # print(iteration, '\t', position, '\t', idx_row, '\t', idx_col, '\t', idx_section, '\t\t', number)  

            iteration += 1  
            if (iteration &gt; max_iterations): stop = True; break  

            if (number not in rows[idx_row] and \  
                number not in cols[idx_col] and \  
                number not in sections[idx_section] and \  
                number not in dead_ends[position]):  
                solutions[position] = number  
                rows[idx_row].append(number)  
                cols[idx_col].append(number)  
                sections[idx_section].append(number)  
                break  

        if stop: break  

        if not position in solutions:  

            dead_ends[position] = [] # if there were any dead ends on this position, remove them now, because we roll back  

            position -= 1  
            if (position &lt;= 0): break  

            reset_containers(position) # and clean up containers for the previous position  

        else:  
            position += 1  

        if (position &gt; 81): break  

    print(f'Done after {iteration - 1:,.0f} iterations.')  

solve_game(max_iterations=10_000)  

visualize(solutions)
</code></pre>
<p>Output:</p>
<img src="https://nickyreinert.de/en/2024/2024-10-18-sudoku/sudoku_finally_solved.png" alt="sudoku finally solved"><p>Wohaa! Looks like our little loop is already producing a valid result. But how does it perform when we add some numbers to the solution?</p>
<p>To work with a pre-filled grid, we need to implement another container — let’s call it <code>target</code>. We’ll add a simple check: If the target contains the current position, we’ll treat it as a constant. Copy it over to the other containers and skip to the next position:</p>
<pre><code>if position in target:   
        number = grid[position]  
        rows[idx_row].append(number)  
        cols[idx_col].append(number)  
        sections[idx_section].append(number)  
        position += 1  
        continue
</code></pre>
<p>We also need to adapt the part where we move back one step when no solution can be found. If we move back from a regular position to a pre-filled one, we should instantly jump back to the last regular position. Otherwise, we’d get stuck in a loop. So, let’s declare <code>last_regular_position</code>, which points to the last regular cell before a pre-filled one, and handle the backtracking that way.</p>
<p>This should help avoid unnecessary loops and keep things running smoothly.</p>
<p>Our loop is growing:</p>
<pre><code>def solve_game(debug = False, max_iterations = 10_000, target = {}):  

    global rows, cols, sections, solutions, dead_ends  

    reset_containers()  

    iteration = 1  
    position = 1  
    stop = False  
    start_of_occupied_area = 82  

    if debug: print('iter', '\t', 'pos', '\t', 'row', '\t', 'col', '\t', 'section', '\t\t', 'number')  

    # mandatroy: register all target position with our containers  
    for target_position in target:  
        idx_row = math.ceil(target_position / 9)  
        idx_col = ((target_position - 1) % 9) + 1  
        idx_section = math.ceil(idx_col/3) + ((math.ceil(idx_row/3) - 1) * 3)  
        rows[idx_row].append(target[target_position])  
        cols[idx_col].append(target[target_position])  
        sections[idx_section].append(target[target_position])  
        
    while True:  
        idx_row = math.ceil(position / 9)  
        idx_col = ((position - 1) % 9) + 1  
        idx_section = math.ceil(idx_col/3) + ((math.ceil(idx_row/3) - 1) * 3)  

        if (position in target):   
            number = target[position]  
            solutions[position] = number  
            rows[idx_row].append(number)  
            cols[idx_col].append(number)  
            sections[idx_section].append(number)  

            if position &lt; start_of_occupied_area: start_of_occupied_area = position  

            position +=1  
            continue  

        start_at = 1  
        if len(dead_ends[position]) &gt; 0:  
            start_at = max(dead_ends[position]) + 1  

        for number in range(start_at, 10):  
            if debug: print(iteration, '\t', position, '\t', idx_row, '\t', idx_col, '\t', idx_section, '\t\t', number)  

            iteration += 1  
            if (iteration &gt; max_iterations): stop = True; break  

            if (number not in rows[idx_row] and \  
                number not in cols[idx_col] and \  
                number not in sections[idx_section] and \  
                number not in dead_ends[position]):  
                solutions[position] = number  
                rows[idx_row].append(number)  
                cols[idx_col].append(number)  
                sections[idx_section].append(number)  

                break  

        if stop: break  

        if not position in solutions:  
            
            dead_ends[position] = []  
            
            if (position - 1) in target:  

                position = start_of_occupied_area - 1  

            else:  
                
                position -= 1  

            reset_containers(position)   

        else:  

            position += 1  

        if (position &gt; 81): break  

    print(f'Done after {iteration - 1:,.0f} iterations.')  

solve_game(debug = False, max_iterations = 5_000, target = {16: 2})  

visualize(solutions)
</code></pre>
<p>Output:</p>
<img src="https://nickyreinert.de/en/2024/2024-10-18-sudoku/sudoku_finally_working.png" alt="sudoku finally working"><p>Farn-tastic! But don’t get too hyped, we’re not there yet. What about different solutions, variations? When we reach the last cell at position 81, in row 9 and column 9, we find exactly one solution for each previous cell. But how do we check for other options? To understand our next approach, imagine the solution as a huge 81-digit number, with all possible solutions branching out like a massive tree. The leftmost branch is the smallest number, and we’re going to find “higher” numbers, which are also valid solutions.</p>
<p>Let’s start with a simple idea: What if, as soon as we reach the end of the grid, we:</p>
<ul>
<li>Go back to the first item</li>
<li>Store all numbers from the current solution to ignore them in the next iteration</li>
<li>Switch to a new, empty solutions container</li>
<li>And start over from the first position?</li>
</ul>
<p>Well, I’ll spare us the effort of implementing it and instead ask you to think it over for yourself. You’ve got 5 milliseconds until the next paragraph starts…</p>
<p>Ka-ching! (Do you remember [Lockerz from 2009](Why did I invest my time into that scam instead of Bitcoin? Whatever…)</p>
<p>It wouldn’t work! The first cell accepts 1 to 9 as possible options — our main branches of a tree. At the second level, which is the second cell, again we have 1 to 9 as possible values. Solution 1 already gave us this path:</p>
<pre><code>1 - 2 - …
</code></pre>
<p>But if we ruled out the 1 when iterating through the second solution, we’d also rule out another possible path:</p>
<pre><code>1 - 3 - …
</code></pre>
<p>So, instead of resetting to the first cell, the top of the tree, we just move back to the last cell — or the last node in the path or branch, however you want to call it. Right now, it looks like this:</p>
<pre><code>… - 6 - 4 - 2
</code></pre>
<p>Think of the solution as a big number:</p>
<pre><code>…642
</code></pre>
<p>From the previous run, we know that the following paths (or numbers) aren’t possible:</p>
<pre><code>…611  
…621  
…631  
…641  
…642 - ka-ching!
</code></pre>
<p>Because, for some reason, the loop always took us back until we eventually hit the 4 and the 2. Now, what happens if we search for the next solution by going back to 81 again, but this time adding the 2 to our list of “dead ends”? The loop will run as it has before:</p>
<pre><code>…643 - not possible, step back  
…65 - not possible, step back  
…7 - not possible, step back
</code></pre>
<p>It will keep doing that until — spoiler alert — it reaches position 66 (row 8, col 3):</p>
<pre><code>…642
</code></pre>
<p>And it will try again, testing all possible numbers until it eventually hits:</p>
<pre><code>…648
</code></pre>
<p>Which is the start of a new possible solution!</p>
<p>Sounds about right, doesn’t it?</p>
<p>Let’s implement it! First, a small adjustment to our init methods and our solutions container, which now holds multiple solutions:</p>
<pre><code>solutions[solution_index][position]
</code></pre>
<p>Of course, we also need to adjust the function’s signature and how we reset our solution container. The way we address positions in the solution deck changes, too. It’s a list now, which makes it easier to slice the first <em>n</em> items of it (honestly, I don’t know why we didn’t do that from the beginning — it’s not really harder to handle. Guess that’s what they call “learning by doing” ;) ).</p>
<pre><code>def reset_containers(solution_index, position = None):  
    global rows, cols, sections, solutions, dead_ends, burned_numbers  


    if position is None and solution_index == 1:  
        rows = {i: [] for i in range(1, 10)}  
        cols = {i: [] for i in range(1, 10)}  
        sections = {i: [] for i in range(1, 10)}  
        solutions[solution_index] = {}  
        dead_ends = {i: [] for i in range(1, 82)}  
    else:  
        idx_row = math.ceil(position / 9)  
        idx_col = ((position - 1) % 9) + 1  
        idx_section = math.ceil(idx_col/3) + ((math.ceil(idx_row/3) - 1) * 3)  

        if position in solutions[solution_index]:  
            number = solutions[solution_index][position]  
            del solutions[solution_index][position]  
        elif position in burned_numbers:  
            number = burned_numbers[position]  
            
        dead_ends[position].append(number)  
        rows[idx_row].remove(number)  
        cols[idx_col].remove(number)  
        sections[idx_section].remove(number)
</code></pre>
<p>Our solution method changes a little. Instead of stopping the loop when <code>position &gt; 81</code>, we add the current solution to our &ldquo;dead end&rdquo; container and then go back to <code>position 81</code>.</p>
<pre><code>def solve_game(debug = False, max_iterations = 10_000, target = {}):  

    global rows, cols, sections, solutions, dead_ends, burned_numbers  

    solution_index = 1  
    solutions = {solution_index: {}}  
    reset_containers(solution_index)  

    report_success = False  

    iteration = 1  
    position = 1  
    stop = False  
    burned_numbers = {} # keeps the previous solution  

    start_of_occupied_area = 82  

    if debug: print('solution', '\t', 'iter', '\t', 'pos', '\t', 'row', '\t', 'col', '\t', 'section', '\t\t', 'number')  

    for target_position in target:  
        idx_row = math.ceil(target_position / 9)  
        idx_col = ((target_position - 1) % 9) + 1  
        idx_section = math.ceil(idx_col/3) + ((math.ceil(idx_row/3) - 1) * 3)  
        rows[idx_row].append(target[target_position])  
        cols[idx_col].append(target[target_position])  
        sections[idx_section].append(target[target_position])  
        
    while True:  

        if position &lt;= 81:  

            idx_row = math.ceil(position / 9)  
            idx_col = ((position - 1) % 9) + 1  
            idx_section = math.ceil(idx_col/3) + ((math.ceil(idx_row/3) - 1) * 3)  

            if (position in target):   
                solutions[solution_index][position] = target[position]  

                if position &gt; 1 and position &lt; start_of_occupied_area: start_of_occupied_area = position  

                position +=1;   
                continue  

            # if the current position has a burned number from the previous solution  
            # we can take this + 1 as the next starting point  
            # this should only happens once for each run when move backwards  
            start_at = 1  
            if position in burned_numbers:  
                last_found_number = burned_numbers[position]  
                start_at = last_found_number + 1  
                # note how we clean or contains only, not the actual burned_numbers record  
                # because if this iteration is not succesful, we need a reference!  
                del burned_numbers[position]  

            elif len(dead_ends[position]) &gt; 0:  
                start_at = max(dead_ends[position]) + 1  

            for number in range(start_at, 10):  
                if debug: print(solution_index, '\t\t', iteration, '\t', position, '\t', idx_row, '\t', idx_col, '\t', idx_section, '\t\t', number)  

                iteration += 1  
                if iteration % (max_iterations // 10) == 0: report_success = True  
                if (iteration &gt; max_iterations): stop = True; break  

                if (number not in rows[idx_row] and \  
                    number not in cols[idx_col] and \  
                    number not in sections[idx_section] and \  
                    number not in dead_ends[position]):  

                    solutions[solution_index][position] = number  
                    
                    rows[idx_row].append(number)  
                    cols[idx_col].append(number)  
                    sections[idx_section].append(number)  
                    break  

            if stop: break  
        
            if not position in solutions[solution_index]:  
        
                dead_ends[position] = []  

                # this is also very important: when we are at this point  
                # we couldn't find a solution for the current position  
                # so we step back to the next &quot;free&quot; cell  
                # that is not blocked by a &quot;target&quot;  

                if (position - 1) in target:  

                    position = start_of_occupied_area - 1  

                else:  
                    
                    position -= 1  

                # and then we clear the containers for this position, to have a fresh start  
                reset_containers(solution_index, position)  

            else:  

                position += 1  

        else:             

            idx_row = math.ceil((position - 1) / 9)  
            idx_col = ((position - 2) % 9) + 1  
            idx_section = math.ceil(idx_col/3) + ((math.ceil(idx_row/3) - 1) * 3)  

            # we found a complete solution, now record the numbers as &quot;burned&quot; for the next run  
            # for the next solution  
            
            if report_success == True: print(f'{solution_index} solutions completed after {iteration - 1:,.0f} iterations.'); report_success = False  

            # take over previous solutions as &quot;burned numbers&quot;  
            dead_ends = {i: [] for i in range(1, 82)}  

            # and also some house keeping, as   
            # we crawl back when trying additional solutions  
            # we only fill the part of the tree  
            # that actually changes, that's why we need to take over  
            # everything that did not change:  
            if solution_index &gt;= 2:  
                solutions[solution_index] = {pos: solutions[solution_index][pos] if pos in solutions[solution_index] else solutions[solution_index - 1].get(pos) for pos in solutions[solution_index - 1]}  
                
            burned_numbers = solutions[solution_index].copy()  

            # it took me some time to find that out:   
            # basically we are telling the script to rewind   
            # as we do when we don't find a solution  
            # so we also need to remove the current number from our  
            # containers  
            number = solutions[solution_index][81]  
            rows[idx_row].remove(number)  
            cols[idx_col].remove(number)  
            sections[idx_section].remove(number)  

            position = 81  
            solution_index += 1  
            solutions[solution_index] = {}  

    print(f'Done after {iteration - 1:,.0f} iterationsm found at least {solution_index - 1} solutions')  

solve_game(debug=False, max_iterations=1_000_000, target = {80: 7})  

visualize(solutions[len(solutions) - 1])
</code></pre>
<p>And that’s our achievment:</p>
<img src="https://nickyreinert.de/en/2024/2024-10-18-sudoku/sudoku_multiple_solutions.png" alt="sudoku multiple solutions"><p>Our brute-force Sudoku solving algorithm is ready! Well, I bet there are a couple of steps to optimize — not only code-style-wise, but also to make it run faster. I’d say that’s your homework. There are <strong>6,670,903,752,021,072,936,960</strong> possible paths (that’s 6 sextillion and a bit more, it can be writte as ~6 x 10^21, which is a reference to Blade Runner: Dr. Ana Stelline was born on the 10/6/21). Can you catch them all? How long does it take on your machine to get 1 million solutions?</p>
<img src="https://nickyreinert.de/en/2024/2024-10-18-sudoku/blade_runner_stelline.png" alt="blade runner stelline"><center><i>Blade Runner's `Dr. Ana Stelline` (c) Warner Bros https://www.warnerbros.com/movies/blade-runner-2049</i></center>
<p>Just one more thing… I asked ChatGPT to build a small function that checks if all solutions are valid. Wait, what? ChatGPT? Why haven’t we asked it earlier?</p>
<p>Because developing algorithms is fun?</p>
<p>You got it! Whatever, here we go:</p>
<pre><code>def check_sudoku(solutions):  

    print('All good!')
</code></pre>
<p>Just kidding — you’ll find the actual implementation in the notebook. Before I send you off into your well-deserved time off, let’s quickly go over some visualizations. I “discovered” 295,295 solutions (took me 100 million iterations and roughly 45 seconds), and this is how they look like in an image:</p>
<img src="https://nickyreinert.de/en/2024/2024-10-18-sudoku/sudoku_solutions_visualized.png" alt="sudoku solutions visualized"><p>Finding <strong>300,000 solutions</strong> out of <strong>6 sextillion possibilities</strong> is like picking a single grain of sand from all the sand on Earth.</p>
<p>For the following visualization, I calculated the difference between each solution — remember, they’re all essentially enormous numbers!</p>
<img src="https://nickyreinert.de/en/2024/2024-10-18-sudoku/sudoku_solutions_difference.png" alt="sudoku solutions difference"><p>There are some interesting points to observe. On the symlog scale of outliers, you can clearly see bands of numbers. There’s also one outlier way out of the region, meaning that from solution <strong>149,652</strong> to <strong>149,653</strong>, the loop has to revert to a very high position in our “solution tree.”</p>
<p>Can you find any more salient features? Let me know!</p>
]]></content:encoded><atom:updated>2026-06-16T20:56:44+02:00</atom:updated><category>projekte</category><dc:subject>Lesezeit: 5 Minuten</dc:subject><dc:type>tutorial</dc:type></item><item><title>How does the SHA256 algorithm work…in detail? (part 2/2)</title><link>https://nickyreinert.de/en/2024/2024-10-06-sha256-part-2/</link><pubDate>Sun, 06 Oct 2024 12:34:56 +0100</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/en/2024/2024-10-06-sha256-part-2/</guid><description>SHA-256 (Secure Hash Algorithm) is the name of a “cryptographic hash function”. SHA-256 is part of an , with the same goal: to create a hash that is resistant to collisions, the calculation of which only works in one direction and has a fixed length. In the following article, I describe the steps …</description><content:encoded><![CDATA[<p>SHA-256 (Secure Hash Algorithm) is the name of a “cryptographic hash function”. 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://de.wikipedia.org/wiki/SHA-2?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">SHA-256 is part</a>


 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://de.wikipedia.org/wiki/SHA-2?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">of</a>


 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://de.wikipedia.org/wiki/SHA-2?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">an</a>


 , with the same goal: to create a hash that is resistant to collisions, the calculation of which only works in one direction and has a fixed length. In the following article, I describe the steps the algorithm takes to create a hash.</p>
<p><em>In the first part we take care of the preparations,</em> 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://medium.com/wie-funktioniert-der-sha256-algorithmus-im-detail-teil-2-2-6afcd85f43b8?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer"><em>in the second part</em></a>


 <em>it goes to the actual algorithm. The source code</em> 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://gist.github.com/nickyreinert/00d631fe9a90108924b1df6e911c8cd5?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer"><em>is on Github</em></a>


<em>.</em></p>
<h2 id="what-will-you-learn">What will you learn?</h2>
<p>In addition to creating a SHA-256, you will be familiar with the handling of binary numbers and binary arithmetic operations such as <strong>XOR</strong>, <strong>AND</strong>, etc. However, I assume that there is a basic understanding of binary numbers, the focus is on the algorithm. <strong>10</strong> should stand in your head either for the <strong>ten</strong> or one <strong>two</strong>. (Or also <strong>twelve</strong>, if you like the <strong>duo decimal system</strong>.)</p>
<h2 id="foreword">Foreword</h2>
<p>If you lower this to a maximum amateur description, the following happens with a crytological hash function: An output text of <strong>any</strong> length is processed in such a way that a result text (<em>the</em> <em>hash</em>) with the <strong>same</strong> length <strong>is always</strong> created. It is almost impossible to calculate the output text from the hash. In addition, you can almost certainly assume that each output text creates <strong>a different hash</strong>. If I change only one character, it affects the original text drastically. Such an algorithm is therefore predestined to be attached to texts, i.e. messages. This is why we also speak of a checksum.</p>
<p>And that is the foundation of a technology that is making more and more talks in the recent past: the <strong>blockchain</strong>, the basis for cryptocurrencies such as <strong>Bitcoin</strong>. With the blockchain, and even that only amateurly broken down, the entries of the “cash book” are securely against manipulation, because the change of a historical value (e.g. booking process) would inevitably result in a drastic change in the checksums generated from it. To take the blockchain apologist out of the wind out of the sails, I quote Fefe, meaning: 





  
  

  
  
    
    
    
  

  
  
  

  
    
      
    
  

  
  <a href="https://ptrace.fefe.de/Blockchain/?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous#46" target="_blank" rel="noopener noreferrer">It is also easier</a>


. I use Bitcoin here only as a buzzword, for marketing reasons. :]</p>
<p>But to motivate you to read on, an important note:</p>
<p>The algorithm is used to calculate the next records of the blockchain. Strictly speaking, a certain hash is setting, which is to be calculated (the notorious <em>mining</em>). The reward for the correct calculation is Bitcoins. The problem: This calculation is <strong>very,</strong> <strong>very complex</strong>, because as already written above: it only works in one direction. The <em>miners</em> therefore have to make unspeakable many calculations in order to calculate a target value. And the miner who carries out the calculation the fastest is also rewarded for it. So if you’re looking forward to optimizing the algorithm, you can get out big in the mining business. That sounds like a challenge, doesn’t it? ;)</p>
<p><img src="https://nickyreinert.de/en/2024/2024-10-06-sha256-part-2/image.png" alt="alt text">
Source: 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://peakd.com/german/-marcus0alameda/dagobert-gold-bitcoin-perfect?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">https://peakd.com/german/-marcus0alameda/dagobert-gold-bitcoin-perfect</a>


 assembly</p>
<blockquote>
<p><strong><em>Disclaimer</em></strong><em>: I have recreated the whole algorithm in Python. Python is certainly not the best option to calculate SHA-256 from performance view and dealing with binary or hexadecimal values is somewhat uncomfortable. Thanks to Jupyter, Python is most likely to describe a complex algorithm step-by-step.</em></p>
</blockquote>
<h2 id="introduction">Introduction</h2>
<p>Before we get involved with the loops, we need to take care of a few functions that we will use later to mix <strong>through binary</strong> <strong>numbers</strong> a little.</p>
<blockquote>
<p><em>In the following, I renounce the prefixes of the number system, such as 0b binary, to keep the text clear. I assume that the following is known: 0 -&gt; False and 1</em></p>
<p><em>Note 2: In the context of SHA-256, one word (dual word) corresponds to exactly 32 bits. In general, 1 Word corresponds to 2 bytes of 16 bits.</em></p>
</blockquote>
<h2 id="the-explicit-or-xor">The Explicit Or (XOR)</h2>
<p>The explicit mean or (<strong>either-or</strong>) is an elementary logical, bitwise operator. The outcome of the operation is only true if exactly a state is true (in comparison, the result of the “simple” <strong>OR</strong> is true, by the way, if at least one operand is true or both).</p>
<p>So two values are processed as follows:</p>
<img src="https://nickyreinert.de/en/2024/2024-10-06-sha256-part-2/image-1.png" alt="alt text"><p>XOR: only if exactly one value is true (1), the corresponding position is true in the result (1)</p>
<p>The implementation in Python is done with the <strong>circumflex</strong> :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">110 to 100
</span></span><span class="line"><span class="ln">2</span><span class="cl">010
</span></span></code></pre></div><h2 id="the-logical-and-and">The logical and (AND)</h2>
<p>The AND operator is also quite common and comparatively simple. Analogous to XOR, the result is true when exactly both (or all) operands are true.</p>
<img src="https://nickyreinert.de/en/2024/2024-10-06-sha256-part-2/image-2.png" alt="alt text"><p>AND: Only if both values of a single area are true, the passage is true in the result</p>
<p>The implementation in Python is done with the <strong>commercial and</strong> :</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">110 &amp; 100
</span></span><span class="line"><span class="ln">2</span><span class="cl">100
</span></span></code></pre></div><h2 id="the-negation-nope">The Negation (Nope?)</h2>
<p>Now it becomes strange: There is also an operator for this: The bitwise operator <strong>negation revolves</strong> values around. 0 becomes 1, from 1 will be 0.</p>
<img src="https://nickyreinert.de/en/2024/2024-10-06-sha256-part-2/image-3.png" alt="alt text"><p>The negation reverses values bitwise. No more but no longer not less.</p>
<p>The implementation in Python is done with the <strong>tilde Ã</strong> my favourite character!</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">110
</span></span><span class="line"><span class="ln">2</span><span class="cl">001
</span></span></code></pre></div><h2 id="the-shift-operation">The Shift Operation</h2>
<p>The shift function is an elementary binary computing operation in which the individual places of a binary value are <strong>moved to the left or right</strong>. The vacancies on the other side will be filled with 0.</p>
<img src="https://nickyreinert.de/en/2024/2024-10-06-sha256-part-2/image-4.png" alt="alt text"><p>Shift to the left around a place, from 6 to 12</p>
<p>And now there is hopefully a positive kink in the learning curve: If you look closely, you will not notice something and let me assure you, it’s not a coincidence: 12 is the product of 6 and 2. This indicates an interesting side effect: A shift comes to a multiplication or. Division with 2 equal. A shift around several digits corresponds to a multiplication with a potency to the base 2 exists. Sounds complicated, therefore an example:</p>
<p>Instead of 139 * 2, you can shift the binary representation of 139, so 10001011, to 17 digits to the left. The result: 1000101100000000000000000. Count after, right of 1 one there are now 17 zeros.</p>
<p>In Python, the binary shift with the <strong>double arrow</strong> is implemented:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">110 &gt;&gt; 1 
</span></span><span class="line"><span class="ln">2</span><span class="cl">011110 to 2
</span></span><span class="line"><span class="ln">3</span><span class="cl">€ 000
</span></span></code></pre></div><h2 id="the-rotate-function">The Rotate function</h2>
<p><strong>Rotate</strong> means that one shifts the values of a (binary) number in one direction. And this is best explained by an example. The following number series should be rotated by one counter to the left. The number on the left side falls out and we turned on the right. The other numbers move one position to the left.</p>
<img src="https://nickyreinert.de/en/2024/2024-10-06-sha256-part-2/image-5.png" alt="alt text"><p>Rotate of a binary value by one place to the left, from 6will 5</p>
<p>This works in both directions and with any number of places. The corresponding function ([Kudos an action](looks like this):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">def rotate(value, rotations, width Ã 32):    
</span></span><span class="line"><span class="ln">2</span><span class="cl">if int(rotations) ! ! abs(int(rotations):        
</span></span><span class="line"><span class="ln">3</span><span class="cl">rotations Ã width + int(rotations)    
</span></span><span class="line"><span class="ln">4</span><span class="cl">return (int(value) - (width - (rotations%width)) | (int(value) &gt;&gt; (rotations % width))) &amp; ((1 Ã width) - 1)
</span></span></code></pre></div><h2 id="the-sigma-functions">The Sigma Functions</h2>
<p>A total of four so-called <strong>Sigma functions</strong> are used. <strong>0</strong> and <strong>1</strong> (the small sigma) respectively. <strong>(Σ1</strong>the large sigma, many known as the sum character). All functions are called with a binary value and return this binary value in a modified form.</p>
<p>(<strong>sigma0</strong>) is as follows:</p>
<ul>
<li>the initial value is <strong>rotated</strong> by 7 <strong>digits</strong> to</li>
<li>the initial value is <strong>rotated</strong> by <strong>18 digits</strong> to</li>
<li>the initial value is <strong>shifted</strong> by <strong>3 digits</strong> to <strong>the right</strong></li>
</ul>
<p>This results in three different values, which are <strong>linked to</strong> each other. The function for this in Python:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">def sigma0(word):    
</span></span><span class="line"><span class="ln">2</span><span class="cl">part1 Ã bin(rotate(int(word, 2), 7, 32))    
</span></span><span class="line"><span class="ln">3</span><span class="cl">part2 Ã bin(rotate(int(word, 2), 18, 32))    
</span></span><span class="line"><span class="ln">4</span><span class="cl">part3 - bin(int(word, 2) &gt;&gt; 3)    
</span></span><span class="line"><span class="ln">5</span><span class="cl">return bin(int(part1, 2) Ã int(part2, 2) Ã int(part3, 2))[2:].zfill(32)
</span></span></code></pre></div><blockquote>
<p><strong><em>Important mote</em></strong> <em>: I work with bin() and in(s, 2), to make the output and input legible and above all comprehensible. I also ensure that the binary representation does not need</em> <strong><em>0b</em></strong><em>. This benefits the purpose of learning, since the binary operations on decimal values are more difficult to understand. With zfill(32) (</em><strong><em>zero fill</em></strong><em>) the binary value to the left is expanded by so many zeros to always comprise</em> <strong><em>32 digits</em></strong><em>. In some cases, this facilitates the overview, on the other hand, this also meets a length specification later. The upper function can also be simplified as follows:</em></p>
</blockquote>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">def rotate(value, rotations, width = 32):
</span></span><span class="line"><span class="ln">2</span><span class="cl">    if int(rotations) != abs(int(rotations)):
</span></span><span class="line"><span class="ln">3</span><span class="cl">        rotations = width + int(rotations)
</span></span><span class="line"><span class="ln">4</span><span class="cl">    return (int(value) &lt;&lt; (width - (rotations%width)) | (int(value) &gt;&gt; (rotations % width))) &amp; ((1 &lt;&lt; width) - 1)
</span></span></code></pre></div><p>It looks very similar to Ã1 (<strong>sigma1)</strong>:</p>
<ul>
<li>the initial value is <strong>rotated</strong> by <strong>17 digits</strong> to <strong>the right</strong></li>
<li>the initial value is <strong>rotated</strong> by <strong>19 digits</strong> to <strong>the right</strong></li>
<li>the initial value is <strong>shifted</strong> by <strong>10</strong> <strong>digits</strong> to <strong>the right</strong></li>
</ul>
<p>This results in three different values, which are <strong>linked to</strong> each other. The function for this in Python:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">def sigma0(word):
</span></span><span class="line"><span class="ln">2</span><span class="cl">    part1 = bin(rotate(int(word, 2), 7, 32))
</span></span><span class="line"><span class="ln">3</span><span class="cl">    part2 = bin(rotate(int(word, 2), 18, 32))
</span></span><span class="line"><span class="ln">4</span><span class="cl">    part3 = bin(int(word, 2) &gt;&gt; 3)
</span></span><span class="line"><span class="ln">5</span><span class="cl">    return bin(int(part1, 2) ^ int(part2, 2) ^ int(part3, 2))[2:].zfill(32)
</span></span></code></pre></div><p>Now to Ã0 (<strong>Sigma0</strong>). Here too, no great overshoots, here now without <strong>shift:</strong></p>
<ul>
<li>the initial value is <strong>rotated</strong> by <strong>2 digits</strong> to <strong>the right</strong></li>
<li>the initial value is <strong>rotated</strong> by <strong>13 digits</strong> to <strong>the right</strong></li>
<li>the initial value is <strong>rotated</strong> by <strong>22</strong> <strong>digits</strong> to <strong>the right</strong></li>
</ul>
<p>Here, too, the respective results are final <strong>XOR.</strong> So in Python:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">def upper_sigma0(word):
</span></span><span class="line"><span class="ln">2</span><span class="cl">    part1 = bin(rotate(int(word, 2), 2, 32))
</span></span><span class="line"><span class="ln">3</span><span class="cl">    part2 = bin(rotate(int(word, 2), 13, 32))
</span></span><span class="line"><span class="ln">4</span><span class="cl">    part3 = bin(rotate(int(word, 2), 22, 32))
</span></span><span class="line"><span class="ln">5</span><span class="cl">    return bin(int(part1, 2) ^ int(part2, 2) ^ int(part3, 2))[2:].zfill(32)
</span></span></code></pre></div><p>Let’s come to the last participant in our illustrious Greek round: Ã1 (<strong>Sigma1</strong>Sigma1):</p>
<ul>
<li>the initial value is <strong>rotated</strong> by <strong>6</strong> <strong>digits</strong> to <strong>the right</strong></li>
<li>the initial value is <strong>rotated</strong> by <strong>11 points</strong> to <strong>the right</strong></li>
<li>the initial value is <strong>rotated</strong> by <strong>25</strong> <strong>digits</strong> to <strong>the right</strong></li>
</ul>
<p>And in the end the XOR link again. Python:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">def upper_sigma1(word):
</span></span><span class="line"><span class="ln">2</span><span class="cl">    part1 = bin(rotate(int(word, 2), 6, 32))
</span></span><span class="line"><span class="ln">3</span><span class="cl">    part2 = bin(rotate(int(word, 2), 11, 32))
</span></span><span class="line"><span class="ln">4</span><span class="cl">    part3 = bin(rotate(int(word, 2), 25, 32))
</span></span><span class="line"><span class="ln">5</span><span class="cl">    return bin(int(part1, 2) ^ int(part2, 2) ^ int(part3, 2))[2:].zfill(32)
</span></span></code></pre></div><h2 id="election-and-majority">Election and majority</h2>
<p>Let us stay with the Greeks and switch to politics: The election and the majority, English: <strong>choose</strong> and <strong>majority</strong>.</p>
<p>Choose is a slightly more complex function that processes three binary values, bitwise. The function goes through the respective digits (x) of the first input value and checks:</p>
<ul>
<li>If <strong>x 1</strong> then take <strong>y</strong></li>
<li>If <strong>x 0</strong> then take <strong>z</strong></li>
</ul>
<p>Y and z stand for the respective positions of the second and third input values. How can this be solved programmatically? So:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">def choose(word1, word2, word3):
</span></span><span class="line"><span class="ln">2</span><span class="cl">    bin_word1 = (int(word1, 2))
</span></span><span class="line"><span class="ln">3</span><span class="cl">    bin_word2 = (int(word2, 2))
</span></span><span class="line"><span class="ln">4</span><span class="cl">    bin_word3 = (int(word3, 2))
</span></span><span class="line"><span class="ln">5</span><span class="cl">    return bin((bin_word1 &amp; bin_word2) ^ (~bin_word1 &amp; bin_word3))[2:].zfill(32)
</span></span></code></pre></div><p>First, value 1 and value 2 are logically linked AND-linked. Then the negation of value 1 is linked to a value of 3 AND-. The two subtotals are finally hunted by XOR.</p>
<p>Majority simply checks for each location of the three input values, which is more common, 1 or 0. This looks like this in Python — here I can’t explain the logical operations again, it’s simply linked XOR and AND:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">def majority(word1, word2, word3):
</span></span><span class="line"><span class="ln">2</span><span class="cl">    bin_word1 = (int(word1, 2))
</span></span><span class="line"><span class="ln">3</span><span class="cl">    bin_word2 = (int(word2, 2))
</span></span><span class="line"><span class="ln">4</span><span class="cl">    bin_word3 = (int(word3, 2))
</span></span><span class="line"><span class="ln">5</span><span class="cl">    return bin((bin_word1 &amp; bin_word2) ^ (bin_word1 &amp; bin_word3) ^ (bin_word2 &amp; bin_word3))[2:].zfill(32)
</span></span></code></pre></div><p><strong>Prime numbers?</strong></p>
<p>To cover another popular field of arithmetic, let us talk about prime numbers briefly. Prime numbers are mystical. And thus just right for our earthly plan to optimize mining.</p>
<p>SHA-256 uses prime numbers as the basis for the algorithm. Which does not mean that the result would be transparent.</p>
<p>We start with the first 64 prime numbers and build a set of constants from it. Of course in bit form.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">[2, 3, 5, 7, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 71, 71, 79, 83, 89, 97, 101, 103, 107, 107, 109, 127, 131, 137, 139, 149, 151, 157, 163, 163, 173, 179, 181, 191, 191, 191, 191, 197, 211, 191, 199257, 263, 269, 271, 277, 281, 283, 293, 307, 311]
</span></span></code></pre></div><p>But these are now also properly taken by the lack. I cannot understand why this is necessary. From my point of view, it does not matter what constants one are used, since they are always the same (which is <strong>why constant</strong>, this time from the Latin one). So there is no big secret about that.</p>
<p>The third root is drawn from the 64 prime numbers. Then the natural part is removed (proach everything before the decimal point) and the result is multiplied by 232 (aka 4,294,996,296, which by the way also corresponds to the number of available IPv4 addresses — the 2nd positive buckling in today’s learning curve?). As you have learned above and hopefully not forgotten yet, the multiplication with 2'32 is not really so complex in the bitu universe.</p>
<p>In any case, the result is rounded off to a natural number, i.e. all decimal places removed. If you repeat this for the remaining 63 prime numbers, you get a well-formed list of 64 entries, which look something like this, using the example of the notorious prime number 2:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">010000101000100010111110011000
</span></span></code></pre></div><p>Or as a hex value:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">0x428a2f98
</span></span></code></pre></div><p>And in the decimal number system:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">1.116.352.408
</span></span></code></pre></div><p>The function for this is as follows:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">result_constants</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">for</span> <span class="n">prime_number</span> <span class="ow">in</span> <span class="n">first_64_prime_numbers</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="n">cube_root</span> <span class="o">=</span> <span class="n">prime_number</span> <span class="o">**</span> <span class="p">(</span><span class="mf">1.</span><span class="o">/</span><span class="mf">3.</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="n">frac_part</span> <span class="o">=</span> <span class="n">cube_root</span> <span class="o">-</span> <span class="nb">floor</span><span class="p">(</span><span class="n">cube_root</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="n">product</span> <span class="o">=</span> <span class="n">frac_part</span> <span class="o">*</span> <span class="p">(</span><span class="mi">2</span><span class="o">**</span><span class="mi">32</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="n">floored_product</span> <span class="o">=</span> <span class="nb">floor</span><span class="p">(</span><span class="n">product</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="n">result_constants</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">bin</span><span class="p">(</span><span class="n">floored_product</span><span class="p">)[</span><span class="mi">2</span><span class="p">:]</span><span class="o">.</span><span class="n">zfill</span><span class="p">(</span><span class="mi">32</span><span class="p">))</span>
</span></span></code></pre></div><p>This is what we call <strong>result constant</strong>, because this list is the beginning of our final output. We pick up this list well and because the work with prime numbers is so liberating, we are organizing a similar circus for the first 8 prime numbers. With one difference: This time the square root serves as the basis:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">compression_constants</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">for</span> <span class="n">prime_number</span> <span class="ow">in</span> <span class="n">first_8_prime_numbers</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="n">square_root</span> <span class="o">=</span> <span class="n">prime_number</span> <span class="o">**</span> <span class="p">(</span><span class="mf">1.</span><span class="o">/</span><span class="mf">2.</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="n">frac_part</span> <span class="o">=</span> <span class="n">square_root</span> <span class="o">-</span> <span class="nb">floor</span><span class="p">(</span><span class="n">square_root</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="n">product</span> <span class="o">=</span> <span class="n">frac_part</span> <span class="o">*</span> <span class="p">(</span><span class="mi">2</span><span class="o">**</span><span class="mi">32</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="n">floored_product</span> <span class="o">=</span> <span class="nb">floor</span><span class="p">(</span><span class="n">product</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="n">compression_constants</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">bin</span><span class="p">(</span><span class="n">floored_product</span><span class="p">)[</span><span class="mi">2</span><span class="p">:]</span><span class="o">.</span><span class="n">zfill</span><span class="p">(</span><span class="mi">32</span><span class="p">))</span>
</span></span></code></pre></div><p>By the way, the names have a meaning, which I later add.</p>
<h2 id="epilog">Epilog</h2>
<p>The preparations are thus completed and we can devote ourselves 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://medium.com/wie-funktioniert-der-sha256-algorithmus-im-detail-teil-2-2-6afcd85f43b8?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">to</a>


 the actual algorithm 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://medium.com/wie-funktioniert-der-sha256-algorithmus-im-detail-teil-2-2-6afcd85f43b8?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">in</a>


 the 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://medium.com/wie-funktioniert-der-sha256-algorithmus-im-detail-teil-2-2-6afcd85f43b8?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">second part</a>


.</p>
]]></content:encoded><atom:updated>2026-06-16T20:56:44+02:00</atom:updated><category>blog</category><dc:subject>Lesezeit: 5 Minuten</dc:subject><dc:type>tutorial</dc:type></item><item><title>How does the SHA256 algorithm work…in detail? (part 2/2)</title><link>https://nickyreinert.de/en/2024/2024-10-04-sha256-part-1/</link><pubDate>Fri, 04 Oct 2024 12:34:56 +0100</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/en/2024/2024-10-04-sha256-part-1/</guid><description>Once you have successfully processed the first part , you are well prepared to learn in this part how the individual components or. Now functions together.
Pre-certificate Before starting, I would like to clarify the connections again: We will immediately create a message (message), the length of …</description><content:encoded><![CDATA[<p>Once you have successfully processed 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://medium.com/@nickyreinert/how-does-the-sha256-algorithm-in-detail-part-1-2-45154fab02d2?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">the first part</a>


, you are well prepared to learn in this part how the individual components or. Now functions together.</p>
<h2 id="pre-certificate">Pre-certificate</h2>
<p>Before starting, I would like to clarify the connections again: We will immediately create a message (<strong>message</strong>), the length of which corresponds to a multiple of <strong>512</strong> bits; in the example, exactly <strong>512 bit</strong>. The message is broken down into <strong>message blocks</strong> that are exactly <strong>512 bit</strong> long. Each message block is in turn broken down into a <strong>message schedule</strong>, with <strong>16 words</strong> (Words) of <strong>32 bit</strong> length each. The length of the words must and will always be <strong>32</strong> bits! However, the message schedule is then extended to contain <strong>64 words</strong>. Its length then: <strong>2,048 bit</strong>. And graphically:</p>
<img src="https://nickyreinert.de/en/2024/2024-10-04-sha256-part-1/image.png" alt="alt text"><p>Important connections</p>
<h2 id="decompressions">Decompressions</h2>
<p>So we want to calculate a <strong>SHA-256</strong> -compliant checksum, the hash, from a message. To do this, the message, i.e. the string, must first be prepared. Our message is, clichéd:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">message = &#39;hello_world&#39;
</span></span></code></pre></div><p>First we have to get the position in the character table for each letterd, i.e. convert the letters (or each character) into their numerical representation:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">dec_message = []
</span></span><span class="line"><span class="ln">2</span><span class="cl">for char in message:
</span></span><span class="line"><span class="ln">3</span><span class="cl">    dec_message.append(ord(char))
</span></span></code></pre></div><p>The achiedes is a list of integers:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">[104, 101, 108, 108, 111, 95, 119, 111, 114, 108, 100]
</span></span></code></pre></div><p>And since we had so much fun dealing with binary values in the first part, we convert the list into binary values that we simply link together:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">bin_message = &#39;&#39;
</span></span><span class="line"><span class="ln">2</span><span class="cl">for decimal in dec_message:
</span></span><span class="line"><span class="ln">3</span><span class="cl">    bin_message += &#39;0&#39; + bin(decimal)[2:
</span></span></code></pre></div><p>The result:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">0110100001100101011011000110110001111110110110110110110110100100100100100
</span></span></code></pre></div><p>Here, however, there is a stumbling trap, and I am reluctant to leave it standing for, for which I have not yet found an explanation: in the transformation into the binary equivalent, we are putting ahead with every binary value ahead of everything. So <strong>104</strong> does not become <strong>1101000</strong> but <strong>01101000</strong>, etc.</p>
<p>In addition, we attach 1 to this date, as a separator for what is now coming up.</p>
<p>Next we calculate the length of this binary number:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">len_bin_message = len(bin_message)
</span></span></code></pre></div><p>The length may or must be submitted exactly <strong>64 bits</strong>. So we also convert it into a binary number to hang a few zeros at the front to get exactly 64 digits:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">rest_to_64 = 64 - len(bin(len_bin_message)[2:])
</span></span><span class="line"><span class="ln">2</span><span class="cl">bin_message_len = &#39;0&#39; * rest_to_64 + bin(len_bin_message)[2:]
</span></span></code></pre></div><p>Now we not only have to connect this three binary information, message, separating one and longitude, but also fill it with so many zeros, so that the total length is a multiple of 512.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">payload</span> <span class="o">=</span> <span class="n">bin_message</span> <span class="o">+</span> <span class="s1">&#39;1&#39;</span> <span class="o">+</span> <span class="n">bin_message_len</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">len_payload</span> <span class="o">=</span> <span class="n">len</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">pad_string</span> <span class="o">=</span> <span class="ne">int</span><span class="p">(</span><span class="mi">512</span> <span class="o">-</span> <span class="p">(</span><span class="n">len_payload</span> <span class="o">%</span> <span class="mi">512</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="n">full_message</span> <span class="o">=</span> <span class="n">bin_message</span> <span class="o">+</span> <span class="s1">&#39;1&#39;</span> <span class="o">+</span> <span class="p">(</span><span class="s1">&#39;0&#39;</span> <span class="o">*</span> <span class="n">pad_string</span><span class="p">)</span> <span class="o">+</span> <span class="n">bin_message_len</span>
</span></span></code></pre></div><p>In our example, the three information shows 153 bits. So we have to include 359 zeros. Strictly speaking, there are between message and length specification. The result is always <em><em>n</em> 512 bits</em>* long:</p>
<img src="https://nickyreinert.de/en/2024/2024-10-04-sha256-part-1/image-1.png" alt="alt text"><p>Setup of a pre-coded message</p>
<p>Last but not least, we take these very, very, very, very…very, very large number (it is very large, you should not see it as a number, but as a !) and divide it into so-called message blocks with a length of <strong>512 bits</strong> each:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">message_block_length = 512
</span></span><span class="line"><span class="ln">2</span><span class="cl">message_blocks = [full_message[i:i+message_block_length] for i in range(0, len(full_message), message_block_length)]
</span></span></code></pre></div><p>Grab a coffee, go fresh air to get another shake out the chair. Now it starts.</p>
<img src="https://nickyreinert.de/en/2024/2024-10-04-sha256-part-1/image-3.png" alt="alt text"><h2 id="the-loop">The loop!</h2>
<p>Since it is easier to explain the process without a loop, here is just a loop. You can still find the programmatic loop on 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://gist.github.com/nickyreinert/00d631fe9a90108924b1df6e911c8cd5?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer"><strong>Github</strong></a>


.</p>
<img src="https://nickyreinert.de/en/2024/2024-10-04-sha256-part-1/image-4.png" alt="alt text"><p>Since our message is exactly 512 bit tall and we work without loop, we can go straight to the full: The laboriously glued message is now included in the so-called. Message Schedule disassembled: Speak in <strong>16 words</strong> with 32 bit length each.</p>
<p>In the first run we take four words and make the following modifications:</p>
<ul>
<li><strong>1</strong> is applied to <strong>word 1</strong> at position 14,</li>
<li><strong>word 2</strong> of position 9 remains unchanged,</li>
<li><strong>0</strong> is applied to <strong>word 3</strong> at position 1 and</li>
<li><strong>Word 4</strong> at position 0 remains unaffected</li>
</ul>
<p>The values are added first and now there is <strong>another important point</strong> to consider: We must strictly ensure that the words are no longer than <strong>32 bits</strong>. Because only in this way can we ensure that the final hash is always the same. And at the latest now, when <strong>adding large values</strong>, we can exceed the <strong>32 bits</strong> quite quickly. From a technical point of view, this is also a hurdle. Therefore, here and for all additions we must finally use <strong>Modulo 2</strong> 32. And now the third hopefully positive kink comes in the learning curve: The binary universe also holds a nice simplification for Modulo: <strong>The</strong> <strong>logical And</strong> with 232–1 (or 4,494,967,295, that is a very large number, not as large as the one in the first part, but exactly one smaller counter) leads to the same result.</p>
<img src="https://nickyreinert.de/en/2024/2024-10-04-sha256-part-1/image-5.png" alt="alt text"><p>Calculation of the first step</p>
<p>This would prepare the message schedule and now contains <strong>64 beautiful words</strong> of <strong>32 bits</strong> <strong>each</strong>. So we first paided up the information from the Message Block and extended from <strong>512 bit to 2,048 bit</strong> — but they are still readable:</p>
<img src="https://nickyreinert.de/en/2024/2024-10-04-sha256-part-1/image-6.png" alt="alt text"><p>The extended Message Schedule</p>
<p>But that is now over, we are coming to the next and most important step:</p>
<h2 id="the-compression">The Compression</h2>
<p>In this step, the extended message schedule is not modified directly, but the words are used as the basis for modifying the <strong>compression constants</strong> initially created.</p>
<p>In 64 loops runs we go through the message schedule. A new word is calculated from each word of the schedule (i.e. our original message) together with the <strong>64 result constants</strong> and the <strong>8 compression constants</strong>. The new word is then preceded by the compression constants, at the same time the last entry is deleted. This is how the list always contains exactly 8 entries.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="n">new_compression_constants</span> <span class="o">=</span> <span class="n">compression_constants</span><span class="o">.</span><span class="n">copy</span><span class="p">()</span><span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">word</span> <span class="ow">in</span> <span class="n">enumerate</span><span class="p">(</span><span class="n">message_schedule</span><span class="p">):</span>    <span class="n">term1</span> <span class="o">=</span> <span class="p">(</span><span class="ne">int</span><span class="p">(</span><span class="n">upper_sigma1</span><span class="p">(</span><span class="n">new_compression_constants</span><span class="p">[</span><span class="mi">4</span><span class="p">]),</span> <span class="mi">2</span><span class="p">)</span> <span class="o">+</span> \
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">                <span class="ne">int</span><span class="p">(</span><span class="n">choose</span><span class="p">(</span><span class="n">new_compression_constants</span><span class="p">[</span><span class="mi">4</span><span class="p">],</span> <span class="n">new_compression_constants</span><span class="p">[</span><span class="mi">5</span><span class="p">],</span> <span class="n">new_compression_constants</span><span class="p">[</span><span class="mi">6</span><span class="p">]),</span> <span class="mi">2</span><span class="p">)</span> <span class="o">+</span> \
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">                <span class="ne">int</span><span class="p">(</span><span class="n">new_compression_constants</span><span class="p">[</span><span class="mi">7</span><span class="p">],</span> <span class="mi">2</span><span class="p">)</span> <span class="o">+</span> \
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">                <span class="ne">int</span><span class="p">(</span><span class="n">result_constants</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="mi">2</span><span class="p">)</span> <span class="o">+</span> \
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">                <span class="ne">int</span><span class="p">(</span><span class="n">word</span><span class="p">,</span> <span class="mi">2</span><span class="p">))</span> \
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">                <span class="o">&amp;</span> <span class="ne">int</span><span class="p">(</span><span class="s1">&#39;11111111111111111111111111111111&#39;</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>    <span class="n">term2</span> <span class="o">=</span> <span class="p">(</span><span class="ne">int</span><span class="p">(</span><span class="n">upper_sigma0</span><span class="p">(</span><span class="n">new_compression_constants</span><span class="p">[</span><span class="mi">0</span><span class="p">]),</span> <span class="mi">2</span><span class="p">)</span> <span class="o">+</span> \
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">                <span class="ne">int</span><span class="p">(</span><span class="n">majority</span><span class="p">(</span><span class="n">new_compression_constants</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">new_compression_constants</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">new_compression_constants</span><span class="p">[</span><span class="mi">2</span><span class="p">]),</span> <span class="mi">2</span><span class="p">))</span> <span class="o">&amp;</span> <span class="ne">int</span><span class="p">(</span><span class="s1">&#39;11111111111111111111111111111111&#39;</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>    <span class="n">new_compression_constants</span><span class="o">.</span><span class="n">insert</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">new_compression_constants</span><span class="o">.</span><span class="n">pop</span><span class="p">()</span>    <span class="n">new_compression_constants</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="n">bin</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">                <span class="p">(</span><span class="n">term1</span> <span class="o">+</span> <span class="n">term2</span><span class="p">)</span> <span class="o">&amp;</span> <span class="ne">int</span><span class="p">(</span><span class="s1">&#39;11111111111111111111111111111111&#39;</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">                <span class="p">)[</span><span class="mi">2</span><span class="p">:]</span><span class="o">.</span><span class="n">zfill</span><span class="p">(</span><span class="mi">32</span><span class="p">)</span>    <span class="n">new_compression_constants</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span> <span class="o">=</span> <span class="n">bin</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">                <span class="p">(</span><span class="ne">int</span><span class="p">(</span><span class="n">new_compression_constants</span><span class="p">[</span><span class="mi">4</span><span class="p">],</span> <span class="mi">2</span><span class="p">)</span> <span class="o">+</span> <span class="n">term1</span><span class="p">)</span> <span class="o">&amp;</span> <span class="ne">int</span><span class="p">(</span><span class="s1">&#39;11111111111111111111111111111111&#39;</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">                <span class="p">)[</span><span class="mi">2</span><span class="p">:]</span><span class="o">.</span><span class="n">zfill</span><span class="p">(</span><span class="mi">32</span><span class="p">)</span>
</span></span></code></pre></div><p>First, a few calculations:</p>
<ul>
<li><strong>Term 1</strong> is calculated from the compression constants, one of the result constants and the respective word. We use here at bit level and <strong>Ã1</strong> (upper sigma1) as well as <strong>choose</strong>.</li>
<li><strong>Term 2</strong> is a sum of two other compression constants that are modified with the help of <strong>Ã0</strong> (upper sigma0) and <strong>majority</strong>.</li>
</ul>
<p>And now pay attention times that the original message part of the 1. Terms is marked in red in the picture:</p>
<img src="https://nickyreinert.de/en/2024/2024-10-04-sha256-part-1/image-7.png" alt="alt text"><p>In the first step, the calculation of two terms is performed</p>
<p>The two terms are now added (and, as always, brought to 32 bit course with <strong>Modulo 232</strong>) and placed at the beginning of the compression constants. The first term will also be held with the 4th Position of this now 9 words long list sums up:</p>
<img src="https://nickyreinert.de/en/2024/2024-10-04-sha256-part-1/image-8.png" alt="alt text"><p>In the second step, the list of compression constants is updated</p>
<p>Next, the last entry of the list is deleted, it now includes <strong>8 completely new compression constants</strong> (if you have paid carefully, you will not have missed that they are not even that constant).</p>
<p>A part of the original message is now a portion <strong>of</strong> the original message as a <strong>summand of term 1</strong> and the <strong>positions 0 and 4.</strong> These will slide part of the same calculations and keep down in the next runs. This distributes the original, previously half-way readable message, over the entire list.</p>
<p>This list is therefore the basis for the second run, which will “rotate” it again to create a completely new list for the third run. And so on until all 64 words of the message schedule are processed. The result should look something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">11100010000000010111101011011001
</span></span><span class="line"><span class="ln">2</span><span class="cl">0100011011010010100000110100100 
</span></span><span class="line"><span class="ln">3</span><span class="cl">11100011111110010111100001001011 
</span></span><span class="line"><span class="ln">4</span><span class="cl">0111000110001000100010000011110 
</span></span><span class="line"><span class="ln">5</span><span class="cl">01001000000111100100000000011000100010001000
</span></span><span class="line"><span class="ln">6</span><span class="cl">0001111111000110101100110101110101 
</span></span><span class="line"><span class="ln">7</span><span class="cl">11101001110010001101110111010100 
</span></span><span class="line"><span class="ln">8</span><span class="cl">0000100110110111100011011011
</span></span></code></pre></div><p>Or in Hexadecimal:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">0xe2017ad9 0x46d28214 0xe3f9784b 0x7186041e 0x481e4018 0x1fc6b235 0xe9c8ddff4 0x96f0efb
</span></span></code></pre></div><p>Finally, this list will go through exactly this list of 8. Compression constants and add each position with the corresponding word of the source list:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="p">[]</span><span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">8</span><span class="p">):</span>    <span class="n">result</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">        <span class="n">bin</span><span class="p">(</span><span class="ne">int</span><span class="p">(</span><span class="n">compression_constants</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="mi">2</span><span class="p">)</span> <span class="o">+</span> 
</span></span><span class="line"><span class="ln">3</span><span class="cl">        <span class="ne">int</span><span class="p">(</span><span class="n">new_compression_constants</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="mi">2</span><span class="p">)</span>  <span class="o">&amp;</span>    
</span></span><span class="line"><span class="ln">4</span><span class="cl">        <span class="ne">int</span><span class="p">(</span><span class="s1">&#39;11111111111111111111111111111111&#39;</span><span class="p">,</span> <span class="mi">2</span><span class="p">))[</span><span class="mi">2</span><span class="p">:]</span><span class="o">.</span><span class="n">zfill</span><span class="p">(</span><span class="mi">32</span><span class="p">))</span>
</span></span></code></pre></div><p>And in plain text:</p>
<img src="https://nickyreinert.de/en/2024/2024-10-04-sha256-part-1/image-9.png" alt="alt text"><p>Last step: Adding the lists</p>
<p>The new list is now the <strong>source list</strong> for the next message schedule. However, since we do not implement the loop here, it was the first. The following is an example summary of the steps:</p>
<img src="https://nickyreinert.de/en/2024/2024-10-04-sha256-part-1/image-10.png" alt="alt text"><p>Schematic representation of the SHA-256 algorithm</p>
<h2 id="hashes-health-what">Hashes. Health. What?</h2>
<p>We did it. You did it. Congratulations.</p>
<img src="https://nickyreinert.de/en/2024/2024-10-04-sha256-part-1/image-11.png" alt="Source: memecreator.org"><p>Now we can either output the hash as a hex value or display it again in binary notation, depending on the purpose of the application.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">for word in result:   
</span></span><span class="line"><span class="ln">2</span><span class="cl">  print(hex(int(word, 2))[2:].zfill(8) + &#39;&#39;, end = &#39;&#39;)
</span></span></code></pre></div><p>The result is impressive in any case.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">35072c1ae546350e0bfa7ab11d49dc6f129e72cdec7eb671225bbd197c8f1
</span></span></code></pre></div><p>Or</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">1101010000011100101101101100010101000100011101101100011111111110100111110110100111010100111011011110110110110111100100111001110110110110110111111101111111110111111111111110001001000100101101110110101110101110101101110101101110101101101110001110110111110101101101101101101101110110110110110110110101
</span></span></code></pre></div><h2 id="epilog">Epilog</h2>
<p>You can find a complete implementation, including a loop to process even large messages





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://gist.github.com/nickyreinert/00d631fe9a90108924b1df6e911c8cd5?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">, in this guest.</a>


</p>
<p>Of course, Python is not suitable for optimizing the SHA-256 process, but it is around to understand the process and learn how to deal with elementary binary computing operation.</p>
<p>If you want to understand the process in a more interactive 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://www.youtube.com/watch?v=f9EbD6iY9zI&amp;utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">way</a>


, I would like to recommend 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://www.youtube.com/watch?v=f9EbD6iY9zI&amp;utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">this video</a>


. There, the algorithm is reproduced in Ruby and the individual calculation steps are explained in more detail.</p>
<p>And why is SHA-256 so important for cryptocurrencies mining now? In short: The hash validates the validity of the <em>cash book</em>. Mining is about calculating exactly a given target hash from a given message and a freely selectable addition. The algorithm therefore has to be mad attenuated. Since this is very complex, it takes time and will be rewarded accordingly.</p>
]]></content:encoded><atom:updated>2026-05-07T15:36:59+02:00</atom:updated><category>blog</category><dc:subject>Lesezeit: 5 Minuten</dc:subject><dc:type>tutorial</dc:type></item><item><title>The Bitcoin Puzzle</title><link>https://nickyreinert.de/en/2024/2024-10-01-bitcoin-puzzle/</link><pubDate>Tue, 01 Oct 2024 12:34:56 +0100</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/en/2024/2024-10-01-bitcoin-puzzle/</guid><description>In this post, I’ll explain how I fell into a deep rabbit hole. I’ll cover how the puzzle works and how the algorithm can be optimized in Python using techniques like parallelization and pre-compiling with Cython. Finally, I’ll reveal how you can increase computation speed by nearly 100x by …</description><content:encoded><![CDATA[<p>In this post, I’ll explain how I fell into a deep rabbit hole. I’ll cover how the puzzle works and how the algorithm can be optimized in Python using techniques like parallelization and pre-compiling with Cython. Finally, I’ll reveal how you can increase computation speed by nearly 100x by implementing Metal to fully harness the power of your Apple Silicon. Let’s go!</p>
<h2 id="the-puzzle">The Puzzle</h2>
<p>It all started with 256 transactions of roughly 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://www.blockchain.com/de/explorer/transactions/btc/08389f34c98c606322740c0be6a7125d9860bb8d5cb182c02f98461e5fa6cd15.?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">33 BTC in January 2015</a>


. The first transaction to wallet #1 contained 0.001 BTC, wallet #2 received 0.002 BTC, then 0.003 BTC to wallet #3 and so on — the final wallet #256 received an amount of 0.256 BTC. Back in those days, the 33 BTC were worth about $5,000.</p>
<p>In the following years, additional transactions were made to adjust each wallet’s balance. In 2017, the balances from wallets 161 to 256 were moved to the lower ranges. And in 2023, someone — allegedly the creator — released another 900 BTC to all wallets from #66 upwards, following a similar principle. Wallet #66 is now worth 66 BTC instead of 0.66 BTC, and so on. At this point, the value of the transferred BTC summed up to over $27 million.</p>
<p>The goal of this exercise is to guess the corresponding private key from the known public address of each wallet. You might think this is impossible because it’s based on a hashing algorithm and you can’t just reverse it or brute-force the whole range of possible combinations.</p>
<p>You are right. Basically…</p>
<p>That’s why the creator limited the complexity of each private key. The first private key has an upper range limit of 1. What does that mean? As the private key is basically just a (huge) number, in the case of the first wallet, you just have to test all numbers from 0 to the upper limit of 1. This means calculating the public key (or signing key) and the compressed wallet address, and comparing it with the known wallet address. If there’s a match, you have access to the wallet and can claim your reward.</p>
<p>These are the first 10 items that were solved quickly after the puzzle was released (





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://privatekeys.pw/puzzles/bitcoin-puzzle-tx?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">full list</a>


)</p>
<pre><code>Index 0:   Hash: 00[…]00   Address: C 1BgGZ9tcN4[…]Z26SAMH  
Index 1:   Hash: 00[…]03   Address: C 1CUNEBjYrC[…]4wpP326Lb  
Index 2:   Hash: 00[…]07   Address: C 19ZewH8Kk1P[…]EiCjTRaZMZQA  
Index 3:   Hash: 00[…]08   Address: C 1EhqbyUMvvs7[…]D6YKfPqb7e  
Index 4:   Hash: 00[…]15   Address: C 1E6NuFjCi2[…]q84zJeBW3k  
Index 5:   Hash: 00[…]31   Address: C 1PitScNLyp2H[…]fveTnfmpPbfp8  
Index 6:   Hash: 00[…]4c   Address: C 1McVt1vMtCC7[…]yCcLXzueeC  
Index 7:   Hash: 00[…]e0   Address: C 1M92tSqNmQLYw[…]d1ysMBxK  
Index 8:   Hash: 00[…]d3   Address: C 1CQFwcjw1dwht[…]L7ivBonGPV  
Index 9:   Hash: 00[…]202   Address: C 1LeBZP5QCwwgXR[…]PUokyLHqe
</code></pre>
<p>So what’s the challenge here? Well, the complexity increases with every wallet by the power of 2. While the first wallet was limited to one private key (2⁰ to 2¹) , the second wallet goes from 2¹ to 2² — 1 (2 possible private keys). Wallet #3’s private key features a range from 4 to 7, and so on. The ranges increase exponentially. The final wallet #160 covers a range from 2¹⁵⁹ to 2¹⁶⁰ — 1, resulting in 7,307,508,186,700,000,000,000,000,000,000,000,000 possible private keys — 73 octillions. Have fun with that. (FYI: The universe is estimated to have about 10⁸⁰ atoms.)</p>
<p>At the lower levels, this is quite a simple task that takes just seconds to solve. And that’s why the first 66 private keys have already been found, as well as some outliers in the upper ranges.</p>
<h2 id="trivia">Trivia</h2>
<p>First of all, it is not known who actually created this task, puzzle, or riddle — whatever you want to call it.</p>
<p>Second, there was a lot of volume going through this wallet: tens of thousands of transactions with millions of BTC. 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://www.blockchain.com/de/explorer/addresses/btc/173ujrhEVGqaZvPHXLqwXiSmPVMo225cqT?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">The last transaction took</a>


 place in 2017, ignoring the winner claiming their rewards.</p>
<p>Third, this has created a rabbit hole for some people, like me. In the chase for the big prize, they are not only throwing hardware into the ring but also knowledge and inventive minds. One of the 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://bitcointalk.org/index.php?topic=5218972.0&amp;utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">“official threads”</a>


 has dozens of pages where people share their findings; they built algorithms to improve the process further and further.</p>
<p>And fourth, this challenge reveals a drawback of the Bitcoin transaction system. A couple of days ago, someone found the private key for wallet #66. He or she tried to withdraw the prize from the wallet. An attacker who was watching the chain for transactions was able to “hijack” this transaction and steal the funds.</p>
<h2 id="bitcoins-double-spending-attack">Bitcoins double spending attack</h2>
<p>To understand how this attack works, you need to understand how the puzzle and Bitcoin transactions work. Each transaction requires three important components to identify the recipient: A private key, a verifying (signing) key and a wallet address.</p>
<p>By performing elliptic curve multiplication (SECP256k1), you can derive the verifying key from the private key Next, you apply several hashing operations to obtain the public wallet address:</p>
<ul>
<li>SHA-256</li>
<li>RIPEMD-160</li>
<li>2 x SHA-256</li>
<li>and finally a (simple) Binary-58 encoding</li>
</ul>
<img src="https://nickyreinert.de/en/2024/2024-10-01-bitcoin-puzzle/required_computation_steps.png" alt="required computation steps"><center><i>required computation steps from private key to wallet address</i></center>
<p>The ultimate goal is to find the private key for a known wallet address. As you can see, obtaining the verifying key requires only one operation, while calculating the wallet address is the more complex part. Solving this puzzle requires you to follow all the steps.</p>
<p>If you find a valid private key for a given wallet address, you can initiate a transaction signed with the now-known verifying key. You need to submit both the wallet address and the verifying key to get confirmation from the blockchain.</p>
<p>Typically, confirmation takes around 10 minutes — that’s how Bitcoin works. An attacker can monitor the blockchain for transaction requests that contain one of the 160 known wallet addresses from the puzzle. Once a request appears, the attacker obtains the verifying key from it, and the clock starts ticking. You’ve already done the hard work of brute-forcing the wallet address from the verifying key. The attacker only needs to go through all possible private keys — which is a limited range in this puzzle — and perform the relatively straightforward SECP256k1 operation to find the matching key (although it’s not as simple as it sounds).</p>
<p>If the attacker succeeds within the 10 minutes, they can submit their own transaction request with a much higher transaction fee. Due to the nature of Bitcoin, miners will prioritize the higher fee and confirm the attacker’s request.</p>
<p>Keep in mind that this method does not work universally for all transactions on the blockchain, as private keys are typically much more complex. Nonetheless, it highlights a significant drawback in the process.</p>
<p>Enough talk — let’s dive into the race and participate.</p>
<h2 id="the-algorithm">The algorithm</h2>
<p>First, we will create a public (or signing) key from the private key using the elliptic curve algorithm SECP256k1.</p>
<img src="https://nickyreinert.de/en/2024/2024-10-01-bitcoin-puzzle/eliptic_curve.png" alt="eliptic curve"><center><i>An eliptic curve (Source: https://en.bitcoin.it/wiki/File:Secp256k1.png)</i></center>
<p>Superficial Note on the Algorithm: SEC stands for Standards for Efficient Cryptography, P represents the prime number 2²⁵⁶ - 2³² - 977 on which this curve is based, 256 denotes the length of the private key in Bit and k1 refers to the Koblitz curve, a specific type of elliptic curve. The actual curve is defined by the equation:</p>
<pre><code>y²=x³ + 7
</code></pre>
<p>Of course there’s a Python Library for that:</p>
<pre><code>private_key_hex = f’{1:064x}’
private_key_bytes = binascii.unhexlify(private_key_hex)
public_key = ecdsa.SigningKey.from_string(private_key_bytes, curve=ecdsa.SECP256k1)
</code></pre>
<p>As mentioned earlier, we need to compress the public key. The details are not important right now; just trust me, this step brings us closer to our goal.</p>
<pre><code>verifying_key = public_key.verifying_key
x = vk.to_string()[:32]
y = vk.to_string()[32:]

y_int = int(binascii.hexlify(y), 16)

if y_int % 2 == 0:
    compressed_public_key = b'\x02' + x
else:
    compressed_public_key = b'\x03' + x

compressed_public_key binascii.hexlify(compressed_public_key).decode()
</code></pre>
<p>Finally, we perform SHA-256 followed by RIPEMD-160 to calculate the wallet address. If you’re interested in how SHA-256 works, I’ve written two extensive posts about it!</p>
<pre><code>compressed_public_key_bytes = binascii.unhexlify(compressed_public_key_hex)

sha256_hash = hashlib.sha256(compressed_public_key_bytes).digest()

ripemd160 = hashlib.new('ripemd160')

ripemd160.update(sha256_hash)  

ripemd160_hash = ripemd160.digest()

versioned_payload = b'\x00' + ripemd160_hash

bitcoin_address = base58check_encode(versioned_payload)
</code></pre>
<p>I’ve compiled everything into a Jupyter notebook, which you can find on GitHub. Let’s see how it performs for the 20th wallet. This will test 524,288 possible numbers, ranging from 2¹⁹ to 2²⁰-1:</p>
<pre><code>target_address = '15JhYXn6Mx3oF4Y7PcTAv2wVVAuCFFQNiP'  

measure_performance(19, 20, target_address)
</code></pre>
<p>On my Apple M3 with 36 GB RAM it took about 30 seconds:</p>
<pre><code>Private Key (Decimal): 863317 
Private Key (Hex): 00000000000000000000000000000000000000000000000000000000000d2c55 
Compressed Public Key: 033c4a45cbd643ff97d77f41ea37e843648d50fd894b864b0d52febc62f6454f7c 
Bitcoin Address (Base58Check): 1HsMJxNiV7TLxmoF6uJNkydxPFDog4NQum 
Total time taken: 24.530259 seconds 
Average time per iteration: 0.047 ms
Initial CPU Usage: 29.7% 
Final CPU Usage: 28.5% 
Initial Memory Usage: 162.12 MB 
Final Memory Usage: 162.25 MB
</code></pre>
<p>This looks like a great starting point for some fine-tuning</p>
<h2 id="parallel-threads">Parallel Threads</h2>
<p>(rel. 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://github.com/nickyreinert/Bitcoin-Puzzle/blob/main/bitcoinPuzzle-v1.py?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">bitcoinPuzzle-v1.py</a>


)</p>
<p>First, let’s move from Jupyter to a Python file and try to implement parallel threads. To effectively use parallel computing, we need to determine how many units we can actually utilize. We can leverage some built-in Python functions to find this out (also see section Measure available system resources in this Notebook):</p>
<pre><code>multiprocessing.cpu_count()
</code></pre>
<p>This returns 12, so that’s the number of units we’ll use. Implementing concurrent processes in Python is quite straightforward. You can use either multiprocessing.Pool or concurrent.futures.ProcessPoolExecutor, which serves as a wrapper class.</p>
<pre><code>  with concurrent.futures.ProcessPoolExecutor(max_workers=12) as executor:

  # Part 1 - starting parallel threads for generate_key()
  
  future_to_index = {
  
  executor.submit(generate_key, start_at, target_address): start_at
  
  for start_at in range(min_val, max_val + 1)}
  
    last_reported_percent = 0
  
  # Part 2 - watch each running thread for result
  
  for future in concurrent.futures.as_completed(future_to_index):
  
    if found: break
  
    result = future.result()
  
    if result: found = result; break
      current_percent = (i + 1) * 100 // n
  
  print('Done')
</code></pre>
<p>The idea is simple: at some point, we start a few threads and monitor the output of each process.</p>
<p>Try running it, and you’ll notice that it takes quite a long time. In our case, this approach isn’t very efficient. The generate_key function is relatively fast, but it gets called twelve times, resulting in significant overhead from thread management. If generate_key were a heavier function, we could maintain this structure. However, in this scenario, we need to divide the search range into batches.</p>
<h2 id="batching">Batching</h2>
<p>(rel. 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://github.com/nickyreinert/Bitcoin-Puzzle/blob/main/bitcoinPuzzle-v2.py?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">bitcoinPuzzle-v2.py</a>


)</p>
<p>To clarify things a bit, we can create a wrapper function called batch_key_generation.</p>
<pre><code>def batch_key_generation(start, end, target_address):

  found_key = None
  
  for i in range(start, end):

    res = generate_key(i, target_address)
    
    if res:
    
      found_key = res
      
      break
    
    return found_key
</code></pre>
<p>The optimal batch size is determined by the number of threads. We could use a bigger value, but then the last thread would have less data to work with. And if the batch size is to small, we’d had to launch more threads than availbl processing units. Which agains creates to much overhead.</p>
<pre><code>batch_size = n // num_workers # Assuming you want 12 workers  

# […]

future_to_index = {

  executor.submit(

    batch_key_generation, 

    start, 

    min(start + batch_size, max_val + 1), 

    target_address): start for start in range(min_val, max_val + 1, batch_size)

}
</code></pre>
<p>Now, the execution time has decreased dramatically to just 5 seconds!</p>
<pre><code>Processed 524289 out of 524288 keys (100%)…
Processed 1048569 out of 524288 keys (199%)…
Total time taken: 5.517847 seconds
Average time per iteration: 0.011 ms
Initial CPU Usage: 40.0%
Final CPU Usage: 96.8%
Initial Memory Usage: 15.20 MB
Final Memory Usage: 16.86 MB
</code></pre>
<p>Great! What’s next?</p>
<h2 id="stop-at-succes">Stop-At-Succes</h2>
<p>(rel. 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://github.com/nickyreinert/Bitcoin-Puzzle/blob/main/bitcoinPuzzle-v3.py?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">bitcoinPuzzle-v3.py</a>


)</p>
<p>Did you notice the slight delay between the success confirmation and the actual termination of the script? This occurs because the pool manager waits for all other threads to finish. We need a global flag to monitor the success state. Here’s how to do it:</p>
<p>First, we wrap our thread loop in a Manager, which acts as a global watchdog for shared state. We also provide a lock and our global state, found_flag, to our calculation function:</p>
<p>with Manager() as manager:</p>
<pre><code>found_flag = manager.Value('i', False)

lock = manager.Lock()

concurrent.futures.ProcessPoolExecutor(max_workers=num_workers) as executor:

future_to_batch = {

  executor.submit(

    batch_key_generation,
    start, 
    min(start + batch_size, max_val + 1), 
    target_address, 
    found_flag, 
    lock): start for start in range(min_val, max_val + 1, batch_size) }  
</code></pre>
<p>[…]</p>
<p>Of course, we should check this global variable in our wrapper function batch_key_generation so that it can stop iterating when needed.</p>
<pre><code>def batch_key_generation(start, end, target_address, found_flag, lock): 
  found_key = None 

  for i in range(start, end):

    if found_flag.value: # check if it's been set from anywhere else

      return None # and then exit early, if required

    res = generate_key(i, target_address) 

    if res: 

      with lock: 

        if not found_flag.value: # double-check

          found found_flag.value = True # and tell all other process, that we found a key

          found_key = res 

          break 

return found_key  
</code></pre>
<p>There’s an important drawback: accessing the lock is a “time-consuming” operation. You will notice a slower processing time when using the lock, which can increase the total execution time from 5 seconds to 11 seconds. You need to decide whether you want a quick result on the command line while the threads are still running or if you’re willing to add this extra time to ensure that all threads finish by the end of the script.</p>
<h2 id="cython--pre-compile-our-code">Cython — pre compile our code</h2>
<p>(rel. 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://github.com/nickyreinert/Bitcoin-Puzzle/blob/main/bitcoinPuzzle-v4.pyx?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">bitcoinPuzzle-v4.pyx</a>


 and 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://github.com/nickyreinert/Bitcoin-Puzzle/blob/main/bitcoinPuzzle-v4-setup.py?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">bitcoinPuzzle-v4-setup.py</a>


)</p>
<p>There’s another trick to speed up processing: precompiling the Python code. Don’t worry, it sounds more complicated than it actually is. We just need to rename the extension of our Python script to .pyx and create a setup.py file like this:</p>
<pre><code>from setuptools import setup
from Cython.Build import cythonize
setup(ext_modules = cythonize(&quot;bitcoinPuzzle-v4.pyx&quot;))
</code></pre>
<p>Now we compile the file into a binary library (you may ignore the warnings):</p>
<pre><code>python bitcoinPuzzle-v4_setup.py build_ext --inplace
</code></pre>
<p>After that, we can either run our script from the command line or use it in any other script:</p>
<pre><code>python -c &quot;import bitcoinPuzzle_v4; bitcoinPuzzle-v4.measure_performance(19, 20, '1HsMJxNiV7TLxmoF6uJNkydxPFDog4NQum', 12)&quot;
</code></pre>
<p>It may not seem significantly faster, likely due to the overhead. However, if you try larger ranges, you will definitely notice a difference. Also, remember that we implemented the global flag to exit the batch processing prematurely.</p>
<h2 id="random-batches">Random batches</h2>
<p>(rel. bitcoinPuzzle_v5.py)</p>
<p>Well, this approach is more of a hack than a true improvement. The idea is simple: as the range increases, processing all possible variations takes more time. However, since we are searching for a needle in a vast field of haystacks, why should we start iterating from the bottom to the top like everyone else? Instead, we could pick random locations, hoping to achieve a quicker win than our “opponents”!</p>
<p>The implementation is straightforward. First, we create a list containing all starting points based on the chunk size, and then we shuffle it.</p>
<pre><code>start_points = list(range(min_val, max_val + 1, chunk_size)) 

random.shuffle(start_points)
</code></pre>
<p>As this is a hack that cannot be measured in time, I’m right jumping to the final improvement:</p>
<h2 id="what-about-pytorch">What about Pytorch?</h2>
<p>(rel: 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://github.com/nickyreinert/Bitcoin-Puzzle/blob/main/bitcoinPuzzle-v5.ipynb?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">bitcoinPuzzle-v5.ipynb</a>


)</p>
<p>First, we need to identify the most expensive operations in this puzzle and then try to improve it further. Let’s determine how long it takes to run 10,000 private keys through the following processes:</p>
<pre><code>SECP256k1): 2.37 seconds
3 x SHA-256): 0.02 seconds
RIPEMD160): 0.01 seconds
BASE58: 0.09 seconds
</code></pre>
<p>The elliptic curve operation is clearly the bottleneck. Currently, we are only utilizing our CPU without taking advantage of the full power of more sophisticated processing units like a GPU or MPS. PyTorch could assist with that, but it doesn’t natively support SECP256k1. Therefore, we need to find another solution, and this is called:</p>
<h2 id="metal">Metal!</h2>
<p>(rel: 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://github.com/nickyreinert/Metal-To-Python-API/blob/main/computation.metal?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">computation.metal</a>


)</p>
<p>Metal is a high-performance graphics and computing API created by Apple that allows developers to harness the full power of the GPU for tasks beyond just rendering graphics, such as machine learning and data processing. It provides low-level access to the GPU, enabling more efficient parallel processing and better performance for applications on iOS and macOS devices.</p>
<p>Spoiler Alert: I wasn’t able to fully implement the working SECP256k1 algorithm in Metal, primarily because the elliptic curve algorithm requires 256-bit integers, which are not natively supported in Metal. This limitation necessitates significant adjustments. However, the performance gains are impressive, and I’m actively working on it!</p>
<p>For demonstration purposes, I chose a mathematical operation that can be implemented in PyTorch, pure Python, and Metal: the cosine!</p>
<p>In Metal we simply create a so called kernel function:</p>
<pre><code>kernel void benchmark(device const float *inputData [[buffer(0)]],
                      device float *outputData [[buffer(1)]],
                      uint id [[thread_position_in_grid]]) {
    
    // Get the input data for the current thread
    float inputValue = inputData[id];
    
    // Compute the cosine of the input value
    float result = cos(inputValue);
    
    // Store the result of the computation in the output buffer
    outputData[id] = result; // Store the computed cosine
    
}
</code></pre>
<p>We compile it using Xcode’s command line tools:</p>
<pre><code>xcrun -sdk macosx metal -fcikernel -c computation.metal -o computation.air
xcrun -sdk macosx metallib computation.air -o computation.metallib
</code></pre>
<p>This command will compile the source code into a Metal library. The most important flag here is -fcikernel, which is necessary to enable the kernel code to run on the GPU.</p>
<p>Here’s the complicated part: we need a wrapper in Swift to bridge the Metal source to Python later on.</p>
<p>First we initiate our metal device, the GPU:</p>
<pre><code>guard let device = MTLCreateSystemDefaultDevice() else {
    print(&quot;Error: Unable to create Metal device.&quot;)
    return 0
}
</code></pre>
<p>Now we reference the benchmark function from the external library we just compiled:</p>
<pre><code>let metallib =  &quot;\(#file.replacingOccurrences(of: &quot;/wrapper.swift&quot;, with: &quot;&quot;))/…/computation.metallib&quot;

guard let library = try? device.makeLibrary(filepath: metallib) else {
    print(&quot;Error: Unable to create Metal library from \(metallib).&quot;)
    return 0
}

guard let kernelFunction = library.makeFunction(name: &quot;benchmark&quot;) else {
    print(&quot;Error: Unable to load benchmark function.&quot;)
    return 0
}
</code></pre>
<p>After that we initiate our command queue as well es input and output buffers:</p>
<pre><code>// Create a command queue
guard let commandQueue = device.makeCommandQueue() else {
    print(&quot;Error: Unable to create command queue.&quot;)
    return 0
}

// Create a command buffer and encoder
guard let commandBuffer = commandQueue.makeCommandBuffer(),
      let encoder = commandBuffer.makeComputeCommandEncoder() else {
    print(&quot;Error: Unable to create command buffer or encoder.&quot;)
    return 0
}

// Create the compute pipeline state
let computePipelineState = try device.makeComputePipelineState(function: kernelFunction)

// Set the compute pipeline state
encoder.setComputePipelineState(computePipelineState)

// Create input buffer for GPU
let inputByteLength = numItems * MemoryLayout&lt;Float&gt;.size
let inputBuffer = device.makeBuffer(bytes: inputData, length: inputByteLength, options: [])
encoder.setBuffer(inputBuffer, offset: 0, index: 0)

// Create output buffer for GPU
let outVectorBuffer = device.makeBuffer(length: inputByteLength, options: .storageModeShared)
encoder.setBuffer(outVectorBuffer, offset: 0, index: 1)&lt;
</code></pre>
<p>And as we also want to utilize parallel computing, we need to define how many parallel threads we want to run:</p>
<pre><code>let threadsPerGroup = MTLSize(width: 64, height: 1, depth: 1)
let numThreadgroups = MTLSize(width: (numItems + threadsPerGroup.width - 1) / threadsPerGroup.width, height: 1, depth: 1)
encoder.dispatchThreadgroups(numThreadgroups, threadsPerThreadgroup: threadsPerGroup)
# tell the encoder, that we are &quot;done&quot; with our setup
encoder.endEncoding()
</code></pre>
<p>And this is how we run the process:</p>
<pre><code>commandBuffer.commit()
commandBuffer.waitUntilCompleted()

// Copy the GPU results to the output buffer passed from Python
if let outputPointer = outVectorBuffer?.contents().assumingMemoryBound(to: Float.self) {
    outputData.update(from: outputPointer, count: numItems)
} else {
    print(&quot;Error: Unable to get contents from output buffer.&quot;)
    return 1
}
</code></pre>
<p>(Note: I also included a Swift wrapper and some additional methods in the corresponding GitHub repository to allow for direct execution from the command line.)</p>
<p>Now we need to build our swift library (I am not going into the details about the Swift package configuration, check out the repo for an example):</p>
<pre><code>swift build
</code></pre>
<p>This will give us a couple of compiled files. The most important one is (usually) hidden in .build/debug: The dynamic library file libWrapper.dylib. Before we can pull it into your Python code, we have to sign it:</p>
<pre><code>// create a new private key
openssl genrsa -out developer.key 2048 
// create a signing certificate
openssl req -new -x509 -key developer.key -out developer.crt -days 365 -subj &quot;/CN=developer/O=NickyReinert/C=DE&quot;
// find the cert's ID
security find-identity -p codesigning
// sign the library
codesign -s &quot;YOUR_CERT_ID&quot; --deep --force .build/debug/libWrapper.dylib
</code></pre>
<p>In Python we load the library simply like that:</p>
<pre><code>swift_function = ctypes.CDLL(&quot;.build/debug/libWrapper.dylib&quot;)
</code></pre>
<p>Now we can already reference our benchmarkin function. As we are working with pointers to the output buffer, it’s a little more efforts to get the results:</p>
<pre><code>swift_function.benchmark.argtypes = [
    ctypes.POINTER(ctypes.c_float),
    ctypes.POINTER(ctypes.c_float),
    ctypes.c_int
]

# create some float values
numItems = 1_000_000_000
input_array = np.linspace(0, numItems - 1, 10, dtype=&quot;float32&quot;)  

# convert array to pointer
input_ptr = input_array.ctypes.data_as(ctypes.POINTER(ctypes.c_float))

# create output pointer
output_length = len(input_array)
output_mutable_ptr = (ctypes.c_float * output_length)()  # Create an output buffer

swift_function.benchmark(input_ptr, output_mutable_ptr, output_length)

output_array = np.ctypeslib.as_array(output_mutable_ptr)

combined_results = np.column_stack((input_array, output_array))

print(combined_results)

Finally we run the script as any other Python script:

python computationGPU.py
</code></pre>
<p>Our Metal implementation completed in just 0.06 seconds (be sure to run it several times to “warm up” your system) for 1,000,000,000 items! The pure Python implementation took about 7 seconds, while PyTorch finished in 4.7 seconds. We’ve improved the timing by a factor of 75!</p>
<p>Congratulations! You’ve just learned how to harness the full power of your GPU. Welcome to the bottom of my rabbit hole. Next stop: implement SECP256k1 and take part in the Bitcoin Puzzle Rally!</p>
<p>~</p>
<p>(Final Note: You can also use PyOpenCL to initialize the device and command queue from within Python, which may simplify and enhance the process. I preferred the Swift wrapper because it is easier to debug.)</p>]]></content:encoded><atom:updated>2026-06-16T20:56:44+02:00</atom:updated><category>projekte</category><dc:subject>Lesezeit: 5 Minuten</dc:subject><dc:type>case_study</dc:type></item><item><title>How to Persist User Configuration in a Gradio App with Cookies</title><link>https://nickyreinert.de/en/2024/2024-05-05-gradio-app/</link><pubDate>Sun, 05 May 2024 12:34:56 +0100</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/en/2024/2024-05-05-gradio-app/</guid><description>I am currently building some kind of playground for Huggingface’s text-2-image models. This app offers more than a dozen configuration parameters. The problem: Every time the user reloads the page, all parameters are gone. Here’s my solution to cope with that.
Gradio has a gr.Stateobject that allows …</description><content:encoded><![CDATA[<p>I am currently building some kind of playground for Huggingface’s text-2-image models. This app offers more than a dozen configuration parameters. The problem: Every time the user reloads the page, all parameters are gone. Here’s my solution to cope with that.</p>
<p><strong>Gradio</strong> has a <code>gr.State</code>object that allows you to persist values for the current user in the current tab. But as soon as you open another tab or even reload the page, your user’s setup is gone. While there’s also a <code>gr.Request</code> oject that allows you to access cookies, this only read cookies.</p>
<img src="https://nickyreinert.de/en/2024/2024-05-05-gradio-app/image-1.png" alt="alt text"><p>So right now there’s no easy way to achieve some kind of “persistent config” for your Gradio demo. But there’s a workaround: Gradio allows you to add custom <strong>JavaScript</strong> to your app, which 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://www.gradio.app/guides/custom-CSS-and-JS?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">is “barely” documented</a>


. As the implementation is not easy and it took me a while to find the solution, I’d like to share my findings:</p>
<ol>
<li>The Javascript</li>
</ol>
<hr>
<p>Let’s start with the JavaScript that takes care of writing the cookie. Put it into a variable and place it before your block definition to make it globally accessible:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">js = &#39;&#39;&#39;function js(){window.set_cookie = function(key, value){document.cookie = key+&#39;=&#39;+value+&#39;; Path=/; SameSite=Strict&#39;;return [value]}}&#39;&#39;&#39;
</span></span></code></pre></div><p>There’s one <strong>heads up</strong>! here: You have to wrap the actual function inside another function. You also need to make it globally available by attaching it to window. Let’s have a closer look at the important parts. The function head apparently requires the key and the value for your cookie:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">function(key, value){
</span></span><span class="line"><span class="ln">2</span><span class="cl">    document.cookie = key+&#39;=&#39;+value+&#39;; Path=/; SameSite=Strict&#39;;
</span></span><span class="line"><span class="ln">3</span><span class="cl">    return [value]
</span></span><span class="line"><span class="ln">4</span><span class="cl">}
</span></span></code></pre></div><p>You need those variables to properly set the cookie. The function must return values according to your Gradio element and event handler. In this case it expects one output variable only.</p>
<p>Done that, load your JavaScript into your demo using the <strong>js-argument</strong> this. The <strong>fn-argument</strong> refers to a Python function that later loads values from your cookies to assign them to your Gradio elements — wich needs to be addressed in <code>outputs</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="ln">1</span><span class="cl">    <span class="n">demo</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">fn</span><span class="o">=</span><span class="n">get_config</span><span class="p">,</span> <span class="n">js</span><span class="o">=</span><span class="n">js</span><span class="p">,</span> <span class="n">outputs</span><span class="o">=</span><span class="p">[</span><span class="n">element1</span><span class="p">])</span>
</span></span></code></pre></div><ol start="2">
<li>The event handler</li>
</ol>
<hr>
<p>Now you are ready to reference the cookie mechanism in your event handler:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">element1.change(fn=element1_change inputs=[element1], outputs=[], js=&#34;(value) =&gt; set_cookie(&#39;element1&#39;, value)&#34;)
</span></span></code></pre></div><p>Again, let’s go through it step by step:</p>
<p>The <strong>fn-argument</strong> simply refers to a Python function that somehow handles the value of <code>element1</code>. You don’t necessarily need it, depending on your setup. The <strong>inputs-argument</strong> is important, as it defines what values are moved through our <em>pipeline</em>. In this case just the value from <code>element1</code>. The <strong>js-argument</strong> now refers to your <strong>JavaScript function</strong>. <code>(value)</code> takes the value from the <strong>inputs-argument</strong> and gives it a name. I am passing this variable to my function and I also need to pass the <code>element1</code> as a string which will be used as the key for the cookie, to identify it later —see <code>set_cookie()</code> requires a key and a value argument?</p>
<ol start="3">
<li>Getting back the value</li>
</ol>
<hr>
<p>We now successfully wrote the value of an element to a cookie. Now we need to read this value when the user reloads the page or open it in another tab:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">def get_config(request: Request):
</span></span><span class="line"><span class="ln">2</span><span class="cl">  config = {&#34;element1&#34;: &#34;value1&#34;}
</span></span><span class="line"><span class="ln">3</span><span class="cl">  for key in initial_config.keys():
</span></span><span class="line"><span class="ln">4</span><span class="cl">      if key in request.cookies:
</span></span><span class="line"><span class="ln">5</span><span class="cl">          config[key] = request.cookies[key]
</span></span><span class="line"><span class="ln">6</span><span class="cl">  return config[&#34;element&#34;]
</span></span></code></pre></div><p>Again — step by step: In the function’s head we refer to Gradio’s Request object which allows us to read the cookies for this request. We then simply loop through all our config parameters, you may define as many as you want, according to your interface. If we find the particular cookie, we can set it’s value.</p>
<p>Finally we return the values. According to the outputs-argument from our <code>demo.load(</code> function (see above), we only return one value for one element.</p>
<ol start="4">
<li>Final Thoughts</li>
</ol>
<hr>
<p>If you want to see the process in action, check out my text-2-image playground at Hugging face either directly via 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://huggingface.co/spaces/n42/pictero?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">https://huggingface.co/spaces/n42/pictero</a>


 or using the easier-to-remember-domain: 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://pictero.com?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">https://pictero.com</a>


 (yes, you can use your own custom domains on Huggingface’s spaces — it’s a simply frame-redir in my DNS configuration).</p>
<p>I am not only using the aboved described workaround to persist users’s configuration: I also implemented a sharing feature reading the configuration from the URL GET-parameter <code>config</code> and put it into my interface.</p>
<p>Have fun and leave your thoughts!</p>
]]></content:encoded><atom:updated>2026-05-07T15:36:59+02:00</atom:updated><category>blog</category><dc:subject>Lesezeit: 5 Minuten</dc:subject><dc:type>tutorial</dc:type></item><item><title>Cryptocurrency Price Prediction with PyTorch: A Machine Learning Tutorial</title><link>https://nickyreinert.de/en/2024/2024-01-01-predict-crypto/</link><pubDate>Mon, 01 Jan 2024 12:34:56 +0100</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/en/2024/2024-01-01-predict-crypto/</guid><description>Flying and predicting stock prices: two of humanity’s greatest dreams. While growing wings seems impossible, let’s focus on predicting the prices of stocks — or in this case, a ‘cryptocurrenciy’! It sounds catchier, doesn’t it?
Goal You’ll learn how to leverage PyTorch, a machine learning …</description><content:encoded><![CDATA[<p>Flying and predicting stock prices: two of humanity’s greatest dreams. While growing wings seems impossible, let’s focus on predicting the prices of stocks — or in this case, a ‘cryptocurrenciy’! It sounds catchier, doesn’t it?</p>
<h3 id="goal">Goal</h3>
<p>You’ll learn how to leverage PyTorch, a machine learning library/framework, to construct a learning algorithm. Our focus will be on the cryptocurrency ADA, which operates on the Cardano blockchain.* Unlike most tutorials that solely consider price as the input feature for the model, we’ll incorporate volume and trade amounts. Implementing the sliding window method, we’ll introduce an ‘outlook gap,’ a less commonly utilized technique. Exploring various model architectures and optimizers, I’ll provide a rough explanation of their workings. Additionally, we’ll aim to enhance the model’s performance. Lastly, I’ll touch upon why a simple script and numerical data might not suffice for accurate price prediction: Stock, cryptos or butter. Whatever.</p>
<p><em>*)I’m not aiming to specifically promote cryptocurrencies. It’s simply convenient to access their historical prices. The primary focus is on PyTorch and machine learning.</em></p>
<h3 id="source-code">Source Code</h3>
<p>Jumping straight in without perusing my carefully crafted text? Bold move. But sure, go ahead, dive in. No hard feelings, probably. Check out this Colab:</p>
<p>





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://drive.google.com/file/d/1lUef_DQBOV5Ngs5rLn3pGPWRpu9ho2Qd/view?usp=sharing&amp;utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">https://drive.google.com/file/d/1lUef_DQBOV5Ngs5rLn3pGPWRpu9ho2Qd/view?usp=sharing</a>


</p>
<h2 id="lets-get-some-data">Let’s get some data</h2>
<p>We’ll utilize the stock prices of <strong>ADA</strong>, a cryptocurrency operating on the <strong>Cardano</strong> blockchain. 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://support.kraken.com/hc/en-us/articles/360047543791-Downloadable-historical-market-data-time-and-sales-?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer"><strong>Kraken</strong> provides</a>


 an extensive archive of historical data encompassing dozens of currencies across various time frames. I’ve opted for the <strong>60-minute time resolution</strong>. Loading this data into a <strong>Pandas</strong> <strong>dataframe</strong> isn’t a significant challenge:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">df = pd.read_csv(&#34;data/ADAEUR_60.csv&#34;)
</span></span><span class="line"><span class="ln">2</span><span class="cl">df[&#39;date&#39;] = pd.to_datetime(df[&#39;timestamp&#39;], unit=&#39;s&#39;, errors=&#39;coerce&#39;)
</span></span><span class="line"><span class="ln">3</span><span class="cl">df[&#39;date&#39;] = pd.to_datetime(df[&#39;date&#39;])
</span></span><span class="line"><span class="ln">4</span><span class="cl">df.set_index(&#39;date&#39;, inplace=True)
</span></span><span class="line"><span class="ln">5</span><span class="cl">df
</span></span></code></pre></div><img src="https://nickyreinert.de/en/2024/2024-01-01-predict-crypto/image.png" alt="alt text"><h3 id="visualize-visualize-visualize">Visualize, Visualize, Visualize</h3>
<p>One of the most crucial teachings from</p>
<p>





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://medium.com/u/dbc019e228f5?source=post_page---user_mention--21873964be4b---------------------------------------&amp;utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">Daniel Bourke</a>


</p>
<p>— a fantastic <strong>PyTorch</strong> and machine learning tutor, and likely much more. As a fellow enthusiast of visualizations, let’s kick things off by plotting the <strong>closing price</strong> and <strong>volume</strong> onto a single chart to gain insights into our data.”</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln"> 1</span><span class="cl"># Downsample the data to not crash the plotting mechanism, we don&#39;t need to plot everything in the dataset
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">downsampled_df = df.resample(&#39;1D&#39;).mean()
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"># close price to the left y axis
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">plt.plot(downsampled_df.index, downsampled_df[&#39;close&#39;], label=&#39;Close&#39;, color=&#39;blue&#39;)
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">plt.ylabel(&#39;Close&#39;, color=&#39;blue&#39;)
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">plt.tick_params(axis=&#39;y&#39;, labelcolor=&#39;blue&#39;)
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"># duplicate to get a second y axis on the right and plot the volune
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">ax2 = plt.twinx()
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">ax2.plot(downsampled_df.index, downsampled_df[&#39;volume&#39;], label=&#39;Volume&#39;, color=&#39;red&#39;)
</span></span><span class="line"><span class="ln">10</span><span class="cl">ax2.set_ylabel(&#39;Volume&#39;, color=&#39;red&#39;)
</span></span><span class="line"><span class="ln">11</span><span class="cl">ax2.tick_params(axis=&#39;y&#39;, labelcolor=&#39;red&#39;)
</span></span><span class="line"><span class="ln">12</span><span class="cl"># Title and legend
</span></span><span class="line"><span class="ln">13</span><span class="cl">plt.title(&#39;Close Price vs. Volume&#39;)
</span></span><span class="line"><span class="ln">14</span><span class="cl">plt.show()py
</span></span></code></pre></div><img src="https://nickyreinert.de/en/2024/2024-01-01-predict-crypto/image-1.png" alt="alt text"><p>I might not be a chart analyst, but here’s my take: Even if I had bought 1 million <strong>ADA</strong> in <strong>2020</strong> and sold them in <strong>2021</strong>, I’d likely still be here, writing this article. Writing brings me joy.</p>
<h2 id="preparation">Preparation</h2>
<p>Let’s delve into the realm of writing machine learning code. I’ll begin by setting some <strong>hyperparameters</strong>. You might not grasp most of them yet, but we’ll cover them in detail later. For now, it’s beneficial to acknowledge their presence.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln"> 1</span><span class="cl">hidden_units = 64
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">num_layers = 4
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">learning_rate = 0.001
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">learning_rate_step_size=5
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">learning_rate_gamma=0.9
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">num_epochs = 100
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">batch_size = 32
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">window_size = 14
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">prediction_steps = 7
</span></span><span class="line"><span class="ln">10</span><span class="cl">dropout_rate = 0.2
</span></span><span class="line"><span class="ln">11</span><span class="cl"># stats and metrics
</span></span><span class="line"><span class="ln">12</span><span class="cl">epoch_count = []
</span></span><span class="line"><span class="ln">13</span><span class="cl">train_loss_values = []
</span></span><span class="line"><span class="ln">14</span><span class="cl">test_loss_values = []
</span></span><span class="line"><span class="ln">15</span><span class="cl">train_rmse_values = []
</span></span><span class="line"><span class="ln">16</span><span class="cl">test_rmse_values = []
</span></span><span class="line"><span class="ln">17</span><span class="cl">duration = 0
</span></span><span class="line"><span class="ln">18</span><span class="cl">features = [&#39;close&#39;, &#39;volume&#39;, &#39;trades&#39;]
</span></span><span class="line"><span class="ln">19</span><span class="cl">target = &#39;close&#39;
</span></span></code></pre></div><ul>
<li><strong>Hidden Units</strong>: The quantity of internal memory cells within our model — visualize them as small algorithms aiding the learning process.</li>
<li><strong>Num_layers</strong>: Denotes the number of layers in the model, where each layer possesses its own memory cells.</li>
<li><strong>Learning Rate</strong>: Represents the magnitude by which the model adjusts its weights and biases (parameters) after each step.</li>
<li><strong>Learning Rate Step Size</strong>: Determines the frequency at which the learning rate should be incremented or decremented.</li>
<li><strong>Learning Rate Gamma</strong>: Serves as a multiplier to modulate the learning rate.</li>
<li><strong>Num_epochs</strong>: Signifies the number of iterations our primary loop will undergo during the training process.</li>
<li><strong>Batch Size</strong>: Specifies the quantity of data processed at a given instance.</li>
<li><strong>Window Size</strong>: Defines the number of past data points considered for our prediction.</li>
<li><strong>Prediction Steps</strong>: Indicates the number of data points to skip before reaching the data point we aim to predict.</li>
<li><strong>Dropout Rate</strong>: Determines the proportion of nodes in the model set to zero.</li>
</ul>
<p>Lastly, we’ll designate the columns intended for our model input: <strong>close</strong>, <strong>volume</strong>, <strong>trades</strong>, and naturally, the column containing the price we aim to predict: <strong>close</strong>.</p>
<h3 id="data-normalisation">Data Normalisation</h3>
<p>Another essential step involves splitting the data into <strong>training and test</strong> <strong>datasets</strong>. Initially, we normalize the data, a crucial process that not only <strong>accelerates computations</strong> but also <strong>enhances</strong> the overall <strong>quality</strong> of the model.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl"># right now start with a small sample, as soon as we have enough computing power, we can skip this step
</span></span><span class="line"><span class="ln">2</span><span class="cl">df_sampled = df[features].head(1000).copy()
</span></span><span class="line"><span class="ln">3</span><span class="cl">#scaler = MinMaxScaler() # MinMax would work, too, but in fact a stock price has not really &#34;min/max values&#34;, except the 0 ;)
</span></span><span class="line"><span class="ln">4</span><span class="cl">scaler = StandardScaler()
</span></span><span class="line"><span class="ln">5</span><span class="cl"># Extract the selected features and transform them
</span></span><span class="line"><span class="ln">6</span><span class="cl">selected_features = df_sampled[features].values.reshape(-1, len(features))
</span></span><span class="line"><span class="ln">7</span><span class="cl">scaled_features = scaler.fit_transform(selected_features)
</span></span><span class="line"><span class="ln">8</span><span class="cl"># Replace the original features with the scaled features in the DataFrame
</span></span><span class="line"><span class="ln">9</span><span class="cl">df_sampled[features] = scaled_features
</span></span></code></pre></div><h3 id="sliding-window-method">Sliding Window Method</h3>
<p>Considering only the current price to predict the subsequent data point might introduce <strong>bias</strong> and limit the model’s <strong>learning ability</strong> due to the lack of sufficient context. Relying solely on the preceding data point could lead the model to overly adapt to recent changes, potentially hindering its predictive capabilities for future values.</p>
<p>To mitigate this, we implement the <strong>sliding window</strong> <strong>method</strong>, gathering multiple data points together. Additionally, we introduce a <strong>prediction gap</strong>. Rather than predicting the next hour’s price, which requires swift responses and might cause <strong>overfitting</strong>, we shift focus to predicting the price in <strong>ten hours</strong>. This longer-term prediction enables informed decisions regarding selling or buying.</p>
<p>Consequently, we generate two <strong>NumPy</strong> arrays: one containing the <strong>features</strong> (<code>X</code>) and the other (<code>y</code>) holding all the <strong>labels</strong>/<strong>targets</strong>, representing the prices we aim to predict.</p>
<blockquote>
<p>The mix of upper and lower case might be confusing. Typically, variables representing <strong>scalars</strong> and <strong>vectors</strong> use lowercase names, while <strong>matrices</strong> and <strong>tensors</strong> are denoted by uppercase names.</p>
</blockquote>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln"> 1</span><span class="cl">def create_sequences(data, window_size, prediction_steps, features, label):
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    X = []
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    y = []
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    for i in range(len(data) - window_size - prediction_steps + 1):
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        sequence = data.iloc[i:i + window_size][features]
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        target = data.iloc[i + window_size + prediction_steps - 1][label]
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        X.append(sequence)
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        y.append(target)
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    return np.array(X), np.array(y)
</span></span><span class="line"><span class="ln">10</span><span class="cl">X, y = create_sequences(df_sampled, window_size, prediction_steps, features, target)
</span></span></code></pre></div><h3 id="splitting-and-batching">Splitting and Batching</h3>
<p>Now, we’ll divide the data into a <strong>training</strong> and a <strong>test dataset</strong>, which is a common step in machine learning. Next, we’ll address some technical requirements by organizing the data into a helper object. This object will then supply batches of data points for the training process.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># SPLITTING</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">X_train</span><span class="p">,</span> <span class="n">X_test</span><span class="p">,</span> <span class="n">y_train</span><span class="p">,</span> <span class="n">y_test</span> <span class="o">=</span> <span class="n">train_test_split</span><span class="p">(</span><span class="n">X</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">test_size</span><span class="o">=</span><span class="mf">0.2</span><span class="p">,</span> <span class="n">shuffle</span><span class="o">=</span><span class="n">False</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c1"># BATCHING</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="n">X_train_tensor</span> <span class="o">=</span> <span class="n">torch</span><span class="o">.</span><span class="n">Tensor</span><span class="p">(</span><span class="n">X_train</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">y_train_tensor</span> <span class="o">=</span> <span class="n">torch</span><span class="o">.</span><span class="n">Tensor</span><span class="p">(</span><span class="n">y_train</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">X_test_tensor</span> <span class="o">=</span> <span class="n">torch</span><span class="o">.</span><span class="n">Tensor</span><span class="p">(</span><span class="n">X_test</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="n">y_test_tensor</span> <span class="o">=</span> <span class="n">torch</span><span class="o">.</span><span class="n">Tensor</span><span class="p">(</span><span class="n">y_test</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="n">train_dataset</span> <span class="o">=</span> <span class="n">TensorDataset</span><span class="p">(</span><span class="n">X_train_tensor</span><span class="p">,</span> <span class="n">y_train_tensor</span><span class="p">)</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="n">test_dataset</span> <span class="o">=</span> <span class="n">TensorDataset</span><span class="p">(</span><span class="n">X_test_tensor</span><span class="p">,</span> <span class="n">y_test_tensor</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="n">train_dataloader</span> <span class="o">=</span> <span class="n">DataLoader</span><span class="p">(</span><span class="n">train_dataset</span><span class="p">,</span> <span class="n">batch_size</span><span class="o">=</span><span class="n">batch_size</span><span class="p">,</span> <span class="n">shuffle</span><span class="o">=</span><span class="n">False</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="n">test_dataloader</span> <span class="o">=</span> <span class="n">DataLoader</span><span class="p">(</span><span class="n">test_dataset</span><span class="p">,</span> <span class="n">batch_size</span><span class="o">=</span><span class="n">batch_size</span><span class="p">,</span> <span class="n">shuffle</span><span class="o">=</span><span class="n">False</span><span class="p">)</span>
</span></span></code></pre></div><h3 id="validation">Validation</h3>
<p>To ensure that no data was lost during the process, we can easily reverse the procedure. I won’t be posting the code here; you can find it in the notebook. Additionally, I’ve included a small section that generates synthetic data to replicate this entire data wrangling process. We’ll revisit this later, so please remember to keep it in mind.</p>
<img src="https://nickyreinert.de/en/2024/2024-01-01-predict-crypto/image-2.png" alt="alt text"><p>The initial 14 lines encompass our features derived from the ‘close,’ ‘volume,’ and ‘trade’ columns. Remember how we set the <strong>sliding window size</strong> to 14? Notably, <strong>7 steps later at position 21</strong>, according to <strong>prediction step</strong>s, we have a closing price marked as 21— this serves as our target!</p>
<p>Furthermore, I’ve implemented several functions to revert the normalization, splitting, and batching processes. These aid us in verifying that the data preparation was executed accurately. The DataFrame presented here showcases the input we supplied to the ‘DataLoader’.</p>
<img src="https://nickyreinert.de/en/2024/2024-01-01-predict-crypto/image-3.png" alt="alt text"><p>The first three columns represent the features, and the fourth column, labeled as ‘close,’ acts as the target closing price or ‘label’ that our model endeavors to predict. Although it reads as 2.999-something rather than precisely three due to rounding errors, it seems we’re moving in the right direction.</p>
<h2 id="init-models-loss-function-and-optimizer">Init Models, Loss Function and Optimizer</h2>
<h3 id="the-training-loop">The training loop</h3>
<p>To train a model, you’ll need three main components: a <strong>model</strong>, a <strong>loss function</strong>, and an <strong>optimizer:</strong></p>
<ul>
<li>The <strong>model</strong> does the prediction works, in the very first loop (aka <strong>epoch</strong>), usually it works with totally random parameters. Remember the X from above? It takes the X (also called <strong>input</strong> or <strong>features</strong>) and calculates the <strong>y</strong>, also called the <strong>output</strong>, <strong>targets</strong> or <strong>labels</strong>.</li>
<li>The <strong>loss function</strong> calculates the difference between the predicted output and the acutal output, which was also part of the training data set.</li>
<li>The <strong>optimizer</strong> takes the loss and decides, how to adapt the models parameters.</li>
<li>And then everything starts over until we reach a loop limit (<strong>num_epochs</strong>)</li>
</ul>
<img src="https://nickyreinert.de/en/2024/2024-01-01-predict-crypto/image-4.png" alt="alt text"><h3 id="the-model-class">The model class</h3>
<p>We’ll begin experimenting with the <strong>LSTM</strong> (<strong>Long Short-Term Memory</strong>) approach, which is a type of <strong>recurrent neural network</strong> (<strong>RNN</strong>). Unlike a typical <strong>neural network</strong> that processes data through layers to generate an output, a recurrent neural network can incorporate past information. This ability is facilitated by its memory cells. Here’s the model class:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln"> 1</span><span class="cl">class StockPriceLSTM(nn.Module):
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    def __init__(self, input_size, hidden_size, num_layers, output_size=1):
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">        super(StockPriceLSTM, self).__init__()
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        self.hidden_size = hidden_size  # Size of the hidden state in the LSTM
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        self.num_layers = num_layers    # Number of LSTM layers
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True)  # LSTM layer
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        self.fc = nn.Linear(hidden_size, output_size)  # Fully connected layer for output prediction
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    def forward(self, input_data):
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        # Initialize hidden and cell states for LSTM
</span></span><span class="line"><span class="ln">10</span><span class="cl">        initial_hidden = torch.zeros(self.num_layers, input_data.size(0), self.hidden_size).to(input_data.device)
</span></span><span class="line"><span class="ln">11</span><span class="cl">        initial_cell = torch.zeros(self.num_layers, input_data.size(0), self.hidden_size).to(input_data.device)
</span></span><span class="line"><span class="ln">12</span><span class="cl">        
</span></span><span class="line"><span class="ln">13</span><span class="cl">        # Forward propagate through LSTM
</span></span><span class="line"><span class="ln">14</span><span class="cl">        lstm_output, _ = self.lstm(input_data, (initial_hidden, initial_cell))  # Output shape: (batch_size, seq_length, hidden_size)
</span></span><span class="line"><span class="ln">15</span><span class="cl">        
</span></span><span class="line"><span class="ln">16</span><span class="cl">        # Pass the output of the last time step through the fully connected layer
</span></span><span class="line"><span class="ln">17</span><span class="cl">        last_time_step_output = lstm_output[:, -1, :]  # Extract the output from the last time step
</span></span><span class="line"><span class="ln">18</span><span class="cl">        output = self.fc(last_time_step_output)  # Output shape: (batch_size, output_size)
</span></span><span class="line"><span class="ln">19</span><span class="cl">        return output
</span></span></code></pre></div><p>When inheriting from <strong>nn.Module</strong>, two methods must be defined: <strong>init</strong> and forward. The <strong>init</strong> method sets up the model’s structure. In this instance, it configures the LSTM portion and a linear layer for the output. The <strong>forward</strong> method handles the computations. It receives the input data (our features), processes it through our model’s modules iteratively, and returns the output.</p>
<h3 id="the-loss-function">The loss function</h3>
<p>To measure the loss we calculate the <strong>mean square error (MSE)</strong>. Sounds complicated, but basically its just the difference between model output and actuall values, squared and divided by the number of data points:</p>
<img src="https://nickyreinert.de/en/2024/2024-01-01-predict-crypto/image-5.png" alt="alt text"><p>There are several options available for calculating the loss. Selecting the appropriate loss function is a crucial aspect of fine-tuning your modeling process and is dependent on the goal and the nature of your data. For instance, in <strong>image classification</strong>, the <strong>Cross Entropy Loss is commonly</strong> utilized.</p>
<h3 id="the-optimizer">The optimizer</h3>
<p>The optimizer determines how to <strong>adjust the parameters</strong> of the model. There are various optimizers to choose from, depending on the specific use case. In simplified terms, here’s what an optimizer does: It <strong>modifies the parameters</strong> of the model based on the <strong>learning rate</strong> and the <strong>loss</strong>.</p>
<h3 id="run-the-loop">Run the loop</h3>
<p>And this is how we initialize all of them before we start the loop:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">model_1 = PricePredictionLSTM(input_size=len(features), hidden_size=hidden_units, num_layers=num_layers)
</span></span><span class="line"><span class="ln">2</span><span class="cl">loss_fn = nn.MSELoss()
</span></span><span class="line"><span class="ln">3</span><span class="cl">optimizer = torch.optim.AdamW(model_1.parameters(), lr=learning_rate)
</span></span></code></pre></div><p>And when the model is the brain, this is the hear of our work, “The Loop”. The steps are always the same, according to the “





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://www.youtube.com/watch?v=Nutpusq_AFw&amp;utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">unofficial optimization loop song</a>


”:</p>
<ol>
<li><strong>Activate Training Mode:</strong> Set the model to training mode if your model includes components like dropout or batch normalization that behave differently during training and inference.</li>
<li><strong>Calculate Predictions:</strong> Pass input data through the model to obtain predictions.</li>
<li><strong>Calculate Loss:</strong> Compute the loss between the predictions and the actual targets.</li>
<li><strong>Reset Gradients to Zero:</strong> Clear the gradients from the previous iteration using <code>optimizer.zero_grad()</code>.</li>
<li><strong>Backward Propagation (Calculate Gradients):</strong> Perform backpropagation by calling <code>loss.backward()</code> to compute gradients of the loss with respect to the model parameters.</li>
<li><strong>Update Parameters:</strong> Use an optimizer (<code>optimizer.step()</code>) to update the model&rsquo;s parameters based on the computed gradients.</li>
<li>Start over…</li>
</ol>
<p>The following code also contains a couple of side features to <strong>keep track</strong> of the <strong>loss values</strong>, <strong>time measuring</strong> and last but not least: <strong>validating</strong> the model against our test data set!</p>
<p>The crucial aspect here is the use of <code>torch.inference_mode()</code>. When your model isn&rsquo;t in training mode, it should operate within this context to avoid interference with the gradients. Occasionally, you might encounter <code>torch.no_grad()</code>, which essentially serves the same purpose but is an older implementation.</p>
<p>We additionally compute the <strong>accuracy</strong> metric. Similar to the loss, it involves calculating the <strong>Mean Squared Error (MSE</strong>) and then taking the square root. However, it’s not entirely necessary as it may not offer additional insights beyond the loss values.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="n">start_epoch</span> <span class="o">=</span> <span class="mi">0</span> <span class="k">if</span> <span class="n">len</span><span class="p">(</span><span class="n">epoch_count</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span> <span class="k">else</span> <span class="n">epoch_count</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> <span class="c1"># helpful if you start over this particular cell</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">for</span> <span class="n">epoch</span> <span class="ow">in</span> <span class="n">tqdm</span><span class="p">(</span><span class="nb">range</span><span class="p">(</span><span class="n">start_epoch</span><span class="p">,</span> <span class="n">start_epoch</span> <span class="o">+</span> <span class="n">num_epochs</span><span class="p">)):</span> <span class="c1"># tqdm is our progress bar wrapper</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">model_1</span><span class="o">.</span><span class="n">train</span><span class="p">()</span> <span class="c1"># activate training mode</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="c1"># handle loss monitoring</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">total_train_loss</span> <span class="o">=</span> <span class="mf">0.0</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">all_train_targets</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">all_train_outputs</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="c1"># process batches in the training dataloader</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="k">for</span> <span class="n">batch_idx</span><span class="p">,</span> <span class="p">(</span><span class="n">inputs</span><span class="p">,</span> <span class="n">targets</span><span class="p">)</span> <span class="ow">in</span> <span class="n">enumerate</span><span class="p">(</span><span class="n">train_dataloader</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">        
</span></span><span class="line"><span class="ln">13</span><span class="cl">        <span class="n">optimizer</span><span class="o">.</span><span class="n">zero_grad</span><span class="p">()</span> <span class="c1"># reset gradients</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">        <span class="n">outputs</span> <span class="o">=</span> <span class="n">model_1</span><span class="p">(</span><span class="n">inputs</span><span class="p">)</span> <span class="c1"># calculate predictions</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">        <span class="n">loss</span> <span class="o">=</span> <span class="n">loss_fn</span><span class="p">(</span><span class="n">outputs</span><span class="o">.</span><span class="n">squeeze</span><span class="p">(),</span> <span class="n">targets</span><span class="p">)</span> <span class="c1"># calculat the loss</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="n">loss</span><span class="o">.</span><span class="n">backward</span><span class="p">()</span> <span class="c1"># backward propagation</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">        <span class="n">optimizer</span><span class="o">.</span><span class="n">step</span><span class="p">()</span> <span class="c1"># update parameters</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">        
</span></span><span class="line"><span class="ln">19</span><span class="cl">        <span class="n">total_train_loss</span> <span class="o">+=</span> <span class="n">loss</span><span class="o">.</span><span class="n">item</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">        <span class="n">all_train_targets</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">targets</span><span class="o">.</span><span class="n">numpy</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">        <span class="n">all_train_outputs</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">outputs</span><span class="o">.</span><span class="n">detach</span><span class="p">()</span><span class="o">.</span><span class="n">numpy</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">    <span class="c1"># scheduler.step()</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">    <span class="n">model_1</span><span class="o">.</span><span class="n">eval</span><span class="p">()</span> <span class="c1"># activate eval mode</span>
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="c1"># handle loss monitoring</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">    <span class="n">total_test_loss</span> <span class="o">=</span> <span class="mf">0.0</span>
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="n">all_test_targets</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">27</span><span class="cl">    <span class="n">all_test_outputs</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="c1"># process batches in the testing dataloader</span>
</span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="p">(</span><span class="n">inputs</span><span class="p">,</span> <span class="n">targets</span><span class="p">)</span> <span class="ow">in</span> <span class="n">enumerate</span><span class="p">(</span><span class="n">test_dataloader</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">        <span class="n">with</span> <span class="n">torch</span><span class="o">.</span><span class="n">inference_mode</span><span class="p">():</span> <span class="c1"># activate inference mode/no grad</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">            <span class="n">outputs</span> <span class="o">=</span> <span class="n">model_1</span><span class="p">(</span><span class="n">inputs</span><span class="p">)</span> <span class="c1"># calculate predictions</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">            <span class="n">loss</span> <span class="o">=</span> <span class="n">loss_fn</span><span class="p">(</span><span class="n">outputs</span><span class="o">.</span><span class="n">squeeze</span><span class="p">(),</span> <span class="n">targets</span><span class="p">)</span> <span class="c1"># calculate loss</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">            <span class="c1"># monitor loss</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">            <span class="n">total_test_loss</span> <span class="o">+=</span> <span class="n">loss</span><span class="o">.</span><span class="n">item</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">35</span><span class="cl">            <span class="n">all_test_targets</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">targets</span><span class="o">.</span><span class="n">numpy</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">36</span><span class="cl">            <span class="n">all_test_outputs</span><span class="o">.</span><span class="n">extend</span><span class="p">(</span><span class="n">outputs</span><span class="o">.</span><span class="n">detach</span><span class="p">()</span><span class="o">.</span><span class="n">numpy</span><span class="p">())</span>
</span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="c1"># calculate average epoch losses</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="n">average_epoch_train_loss</span> <span class="o">=</span> <span class="n">total_train_loss</span> <span class="o">/</span> <span class="n">len</span><span class="p">(</span><span class="n">train_dataloader</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="n">average_epoch_test_loss</span> <span class="o">=</span> <span class="n">total_test_loss</span> <span class="o">/</span> <span class="n">len</span><span class="p">(</span><span class="n">test_dataloader</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">    
</span></span><span class="line"><span class="ln">41</span><span class="cl">    <span class="c1"># caculate accuracy</span>
</span></span><span class="line"><span class="ln">42</span><span class="cl">    <span class="n">train_rmse</span> <span class="o">=</span> <span class="n">math</span><span class="o">.</span><span class="n">sqrt</span><span class="p">(</span><span class="n">mean_squared_error</span><span class="p">(</span><span class="n">all_train_targets</span><span class="p">,</span> <span class="n">all_train_outputs</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">    <span class="n">test_rmse</span> <span class="o">=</span> <span class="n">math</span><span class="o">.</span><span class="n">sqrt</span><span class="p">(</span><span class="n">mean_squared_error</span><span class="p">(</span><span class="n">all_test_targets</span><span class="p">,</span> <span class="n">all_test_outputs</span><span class="p">))</span>
</span></span><span class="line"><span class="ln">44</span><span class="cl">    <span class="c1"># VISUALIZE</span>
</span></span><span class="line"><span class="ln">45</span><span class="cl">    <span class="n">epoch_count</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">epoch</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">    <span class="n">train_loss_values</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">average_epoch_train_loss</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">    <span class="n">test_loss_values</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">average_epoch_test_loss</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">    <span class="n">train_rmse_values</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">train_rmse</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">    <span class="n">test_rmse_values</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">test_rmse</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">50</span><span class="cl">    <span class="c1"># LOG</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl">    <span class="k">if</span> <span class="n">epoch</span> <span class="o">%</span> <span class="ne">int</span><span class="p">(</span><span class="n">num_epochs</span> <span class="o">/</span> <span class="mi">10</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span> <span class="ow">or</span> <span class="n">epoch</span> <span class="o">==</span> <span class="n">num_epochs</span> <span class="o">-</span> <span class="mi">1</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">52</span><span class="cl">        <span class="n">current_lr</span> <span class="o">=</span> <span class="n">scheduler</span><span class="o">.</span><span class="n">get_last_lr</span><span class="p">()[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">53</span><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="n">f</span><span class="s2">&#34;Epoch [{epoch + 1}/{start_epoch + num_epochs}], &#34;</span>
</span></span><span class="line"><span class="ln">54</span><span class="cl">        <span class="n">f</span><span class="s2">&#34;Train Loss: {average_epoch_train_loss:.4f} | &#34;</span>
</span></span><span class="line"><span class="ln">55</span><span class="cl">        <span class="n">f</span><span class="s2">&#34;Test Loss: {average_epoch_test_loss:.4f} | &#34;</span>
</span></span><span class="line"><span class="ln">56</span><span class="cl">        <span class="n">f</span><span class="s2">&#34;Train RMSE: {train_rmse:.4f} | &#34;</span>
</span></span><span class="line"><span class="ln">57</span><span class="cl">        <span class="n">f</span><span class="s2">&#34;Test RMSE: {test_rmse:.4f} | &#34;</span>
</span></span><span class="line"><span class="ln">58</span><span class="cl">        <span class="n">f</span><span class="s2">&#34;Current LR: {current_lr:.8f} | &#34;</span>
</span></span><span class="line"><span class="ln">59</span><span class="cl">        <span class="n">f</span><span class="s2">&#34;Duration: {time.time() - start:.0f} seconds&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">60</span><span class="cl"><span class="n">duration</span> <span class="o">+=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">start</span>
</span></span></code></pre></div><h3 id="wait-stop-whats-that-the-learning-rate-scheduler">Wait… stop… what’s that? The learning rate scheduler?</h3>
<p>If you’ve thoroughly read through the loop, you might have noticed a commented-out command: <code>scheduler.step()</code>. What does it do?</p>
<p>The <strong>learning rate</strong> typically remains constant and dictates how the algorithm adjusts the model’s parameters in each step. A scheduler assists in dynamically altering this step size. While not always necessary, experimenting with a scheduler can be beneficial, potentially <strong>improving results</strong> within a <strong>shorter duration</strong>.</p>
<p>This is how you initialize it:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">scheduler = lr_scheduler.StepLR(optimizer, step_size=learning_rate_step_size, gamma=learning_rate_gamma)  # Adjust step_size and gamma as needed
</span></span></code></pre></div><h2 id="run-it">Run it!</h2>
<p>Now we are ready to run everything and see the training/optimization process in action. To get a first impression how good everything works, I suggest to start with the dummy dataset that we defined above. I will not post the plotting code here. This is how your plot should look like:</p>
<img src="https://nickyreinert.de/en/2024/2024-01-01-predict-crypto/image-6.png" alt="alt text"><p>The result is not too bad and this within <strong>27 seconds</strong>. But considering the source data, not a surprise.</p>
<p>Lets run our real world data through the model. This will not take much longer and the result after ter 100 epochs looks, well…a little off:</p>
<img src="https://nickyreinert.de/en/2024/2024-01-01-predict-crypto/image-7.png" alt="alt text"><p>Let’s be realistic: You can’t expect substantial results after just 4minutes of training on a small dataset like this in the first attempt.</p>
<p>This process involves extensive hyperparameter tuning and selecting network architecture. Now, let’s explore another approach: GRU, which stands for Gated Recurrent Unit, is another type of recurrent neural network. Contrary to LSTM, GRU features a simplified architecture. Specifically, GRU employs two gates to manage data flow (reset and update), while LSTM utilizes three gates (input, forget, and output). LSTM is ideal for handling complex dependencies, while GRU offers reduced computational complexity. This is our model class:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln"> 1</span><span class="cl">class PricePredictionGRU(nn.Module):
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    def __init__(self, input_size, hidden_size, num_layers, output_size=1):
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">        super(PricePredictionGRU, self).__init__()
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        self.hidden_size = hidden_size
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        self.num_layers = num_layers
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        self.gru = nn.GRU(input_size, hidden_size, num_layers, batch_first=True)
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        self.fc = nn.Linear(hidden_size, output_size)
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    def forward(self, x):
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        # Initialize hidden state
</span></span><span class="line"><span class="ln">10</span><span class="cl">        h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)
</span></span><span class="line"><span class="ln">11</span><span class="cl">        
</span></span><span class="line"><span class="ln">12</span><span class="cl">        # Forward propagate GRU
</span></span><span class="line"><span class="ln">13</span><span class="cl">        out, _ = self.gru(x, h0)  # out: tensor of shape (batch_size, seq_length, hidden_size)
</span></span><span class="line"><span class="ln">14</span><span class="cl">        
</span></span><span class="line"><span class="ln">15</span><span class="cl">        # Pass the output of the last time step through the fully connected layer
</span></span><span class="line"><span class="ln">16</span><span class="cl">        out = self.fc(out[:, -1, :])  # Output shape: (batch_size, output_size)
</span></span><span class="line"><span class="ln">17</span><span class="cl">        return out
</span></span></code></pre></div><p>As you can see, it’s the exact same structure as before. That’s because PyTorch provides “templates” for the models and we just need to feed them correctly.</p>
<p>This is the result after 200 epochs:</p>
<img src="https://nickyreinert.de/en/2024/2024-01-01-predict-crypto/image-8.png" alt="alt text"><p>Despite the fact that it ran almost double the expected time, contradicting the promised reduction in computational requirements, the results appear noticeably improved. However, they still aren’t sufficient for accurate stock price prediction.</p>
<h2 id="next-steps-and-final-thoughts">Next steps and final thoughts</h2>
<p>Firstly, let me provide an example showcasing the common flaw in many ‘price prediction’ discussions: they often focus on predicting the immediate next data point without considering the data points ahead. This approach simplifies the model’s task significantly. For instance, if presented with today’s closing price, predicting tomorrow’s close becomes relatively easy due to the typical range within which prices fluctuate.</p>
<p>The following chart shows the prediction when setting the prediction gap to 1, meaning predicting the very next data point:</p>
<img src="https://nickyreinert.de/en/2024/2024-01-01-predict-crypto/image-9.png" alt="alt text"><p>That is the result after 300 epochs for a sample size of 1.000 after 5 minutes of training. Looks tempting, right? As soon as you try to look further into the future, it gets messy. I increased the outlook windows to <code>prediction_steps = 7</code> :</p>
<img src="https://nickyreinert.de/en/2024/2024-01-01-predict-crypto/image-10.png" alt="alt text"><p>Here I increased the sample size for the validation data to <strong>10.000</strong>:</p>
<img src="https://nickyreinert.de/en/2024/2024-01-01-predict-crypto/image-11.png" alt="alt text"><p>It’s not terrible; at least the curve somewhat aligns with the general trend. Ultimately, the quality of your model depends on the computational power allocated and the chosen hyperparameters.</p>
<p>You’re all set with the basics and the code. Feel free to give it a try on your own. And if you happen to strike it rich, just a reminder: I’ve got a soft spot for a well-made <strong>flat white</strong>!</p>
]]></content:encoded><atom:updated>2026-06-16T20:56:44+02:00</atom:updated><category>blog</category><dc:subject>Lesezeit: 5 Minuten</dc:subject><dc:type>tutorial</dc:type></item><item><title>How to predict prime numbers in JavaScript</title><link>https://nickyreinert.de/en/2023/2023-12-12-prime-numbers-javascript/</link><pubDate>Tue, 12 Dec 2023 12:34:56 +0100</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/en/2023/2023-12-12-prime-numbers-javascript/</guid><description>There is only one thing that I love more than algorithms: Improving algorithms! This article will not explain one of those sophisticated formulas — but it will hopefully show you, how thoughtful thinking and tinkering will help you with optimising your code.
Nota bene: Think Python is faster? Try …</description><content:encoded><![CDATA[<p>There is only one thing that I love more than algorithms: Improving algorithms! This article will not explain one of those sophisticated formulas — but it will hopefully show you, how thoughtful thinking and tinkering will help you with optimising your code.</p>
<blockquote>
<p>Nota bene: 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://medium.com/how-to-find-prime-numbers-fast-8d0f7e8bd80f?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">Think Python is faster? Try it!</a>


</p>
</blockquote>
<p>What’s a prime number? All of you know that: It’s a number with only two divider natural divisors: 1 and the number itself.</p>
<p>And when you ask someone for a prime number algorithm, you get two answers: A mathematican will 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://en.wikipedia.org/wiki/Formula_for_primes?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">throw out a complex formula,</a>


 like <strong>Mill’s formula:</strong></p>
<img src="https://nickyreinert.de/en/2023/2023-12-12-prime-numbers-javascript/image.png" alt="alt text"><p>Wait, what? Ok. The other answer is just a simple loop that checks if there is another number, lower than the number in question, where the operation <strong>modulo</strong> returns 0. The so called “trial division”. In other words: The number in question divided by the other number returns a third number and all numbers are natural, not factorized.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">function isPrime(number) {
</span></span><span class="line"><span class="ln">2</span><span class="cl">    for (let i = 2; i &lt; number; i++) {
</span></span><span class="line"><span class="ln">3</span><span class="cl">        if (number % i === 0) {
</span></span><span class="line"><span class="ln">4</span><span class="cl">            return false;
</span></span><span class="line"><span class="ln">5</span><span class="cl">        }
</span></span><span class="line"><span class="ln">6</span><span class="cl">    }
</span></span><span class="line"><span class="ln">7</span><span class="cl">    return true;
</span></span><span class="line"><span class="ln">8</span><span class="cl">}
</span></span></code></pre></div><p>Let’s run this loop to find primes up to 50.000:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln"> 1</span><span class="cl">let startTime = performance.now();
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">let count = 0;
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">for (let number = 2; number &lt; 50000; number++) {
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    if (isPrime(number)) {
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        count++;
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    }
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">}
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">let endTime = performance.now();
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">let executionTime = endTime - startTime;
</span></span><span class="line"><span class="ln">10</span><span class="cl">console.log(`found ${count} primes`);
</span></span><span class="line"><span class="ln">11</span><span class="cl">console.log(`Execution time: ${executionTime} milliseconds`);
</span></span></code></pre></div><blockquote>
<p>Did you know? You can run JavaScript inside Visual Studio Code. Just hit Ctrl+F5. Don’t know where to click. I’m a shortcut-nerd.</p>
</blockquote>
<p>On my machine this will take 212 milliseconds and find 5.133 primes (your results may differ on a different machine):</p>
<img src="https://nickyreinert.de/en/2023/2023-12-12-prime-numbers-javascript/image-1.png" alt="alt text"><p>Wait a minute! JavaScript is dozens of time faster than… Python?</p>
<p>Jepp. Let’s see if we could add another level of disgrace:</p>
<p>Let’s start with some obvious things: We don’t need to test all numbers in our “test division”. In fact we can skip every divisor lower than “number / 2”. Why is that? Because if the divisor is greater than “number / 2” the result must be lower than 2 and a faction.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln"> 1</span><span class="cl">function isPrime2(number) {
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    let limit = Math.floor(number / 2);
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    for (let i = 2; i &lt;= limit; i++) {
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        if (number % i === 0) {
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">            return false;
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        }
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    }
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    return true;
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">}
</span></span><span class="line"><span class="ln">10</span><span class="cl">let startTime = performance.now();
</span></span><span class="line"><span class="ln">11</span><span class="cl">let count = 0;
</span></span><span class="line"><span class="ln">12</span><span class="cl">for (let number = 2; number &lt; 50000; number++) {
</span></span><span class="line"><span class="ln">13</span><span class="cl">    if (isPrime2(number)) {
</span></span><span class="line"><span class="ln">14</span><span class="cl">        count++;
</span></span><span class="line"><span class="ln">15</span><span class="cl">    }
</span></span><span class="line"><span class="ln">16</span><span class="cl">}
</span></span><span class="line"><span class="ln">17</span><span class="cl">let endTime = performance.now();
</span></span><span class="line"><span class="ln">18</span><span class="cl">let executionTime = endTime - startTime;
</span></span><span class="line"><span class="ln">19</span><span class="cl">console.log(`found ${count} primes`);
</span></span><span class="line"><span class="ln">20</span><span class="cl">console.log(`Execution time: ${executionTime} milliseconds`);
</span></span></code></pre></div><p>Again, similar to the Python solution, this halves the calculation time to, but not to 100 milliseconds</p>
<img src="https://nickyreinert.de/en/2023/2023-12-12-prime-numbers-javascript/image-2.png" alt="alt text"><p>But there’s even more in this limit declaration: We can reduce it to the square root <strong>(sqrt)</strong> of our target number. Because if one divisor is greater than <strong>sqrt(number)</strong> the other divisor must be lower than <strong>sqrt</strong>(divisor). Meaning: They have already been checked. Let’s do it:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln"> 1</span><span class="cl">function isPrime3(number) {
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    let limit = Math.floor(Math.sqrt(number));
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    for (let i = 2; i &lt;= limit; i++) {
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        if (number % i === 0) {
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">            return false;
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        }
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    }
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    return true;
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">}
</span></span><span class="line"><span class="ln">10</span><span class="cl">let startTime = performance.now();
</span></span><span class="line"><span class="ln">11</span><span class="cl">let count = 0;
</span></span><span class="line"><span class="ln">12</span><span class="cl">for (let number = 2; number &lt; 50000; number++) {
</span></span><span class="line"><span class="ln">13</span><span class="cl">    if (isPrime3(number)) {
</span></span><span class="line"><span class="ln">14</span><span class="cl">        count++;
</span></span><span class="line"><span class="ln">15</span><span class="cl">    }
</span></span><span class="line"><span class="ln">16</span><span class="cl">}
</span></span><span class="line"><span class="ln">17</span><span class="cl">let endTime = performance.now();
</span></span><span class="line"><span class="ln">18</span><span class="cl">let executionTime = endTime - startTime;
</span></span><span class="line"><span class="ln">19</span><span class="cl">console.log(`found ${count} primes`);
</span></span><span class="line"><span class="ln">20</span><span class="cl">console.log(`Execution time: ${executionTime} milliseconds`);
</span></span></code></pre></div><p>WHAT THE ACTUAL HECK-IDDI-HECK! 4 Milliseconds? Remember Python and 72 Milliseconds?</p>
<img src="https://nickyreinert.de/en/2023/2023-12-12-prime-numbers-javascript/image-3.png" alt="alt text"><p>What’s next?</p>
<p>Why do we check even numbers at all? It’s “natural law” that an even number cannot be a prime (except the magic number 2).</p>
<p>We can safely skip them in our main loop. But not only there, we can also safely skip them in the “test division”. An even factor always leads to an even product in a multiplication. This leads to the assumption, that an even number always leads to a modulo unequal 0 when thrown against a prime!</p>
<p>In JavaScript it is a little trickier to check for the last digit of a number (aka last char of a string).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln"> 1</span><span class="cl">function isPrime4(number) {
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    let limit = Math.floor(Math.sqrt(number));
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    for (let i = 2; i &lt;= limit; i++) {
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        if ([0, 2, 4, 5, 6, 8].includes(parseInt(i.toString().slice(-1)))) {
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">            continue;
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        }
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        if (number % i === 0) {
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">            return false;
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        }
</span></span><span class="line"><span class="ln">10</span><span class="cl">    }
</span></span><span class="line"><span class="ln">11</span><span class="cl">    return true;
</span></span><span class="line"><span class="ln">12</span><span class="cl">}
</span></span></code></pre></div><p>Let’s add this logic to both loops:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln"> 1</span><span class="cl">let startTime = performance.now();
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">let count = 0;
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">for (let number = 2; number &lt; 50000; number++) {
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    if ([0, 2, 4, 5, 6, 8].includes(parseInt(number.toString().slice(-1)))) {
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        continue;
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    }
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    if (isPrime4(number)) {
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        count++;
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    }
</span></span><span class="line"><span class="ln">10</span><span class="cl">}
</span></span><span class="line"><span class="ln">11</span><span class="cl">let endTime = performance.now();
</span></span><span class="line"><span class="ln">12</span><span class="cl">let executionTime = endTime - startTime;
</span></span><span class="line"><span class="ln">13</span><span class="cl">console.log(`found ${count} primes`);
</span></span><span class="line"><span class="ln">14</span><span class="cl">console.log(`Execution time: ${executionTime} milliseconds`);
</span></span></code></pre></div><p>Remember Python? This “silly” approach took a lot time. Same in JavaScript, it increases running time to 47 Milliseconds:</p>
<img src="https://nickyreinert.de/en/2023/2023-12-12-prime-numbers-javascript/image-4.png" alt="alt text"><p>Again — We should implement the last test a little smarter. Right now we are using some string operations and apparently they are slow as heck.</p>
<p>Python allows us to change the step rate of the <strong>range()</strong> function. This way we could just skip even numbers in the loop itself. Way easier. Heads up: As we will start at 3 now, we increment the counter integer beforehand:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln"> 1</span><span class="cl">function isPrime5(number) {
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    let limit = Math.floor(Math.sqrt(number));
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    for (let i = 3; i &lt;= limit; i += 2) {
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        if (number % i === 0) {
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">            return false;
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        }
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    }
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    return true;
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">}
</span></span><span class="line"><span class="ln">10</span><span class="cl">let startTime = performance.now();
</span></span><span class="line"><span class="ln">11</span><span class="cl">let count = 1;
</span></span><span class="line"><span class="ln">12</span><span class="cl">for (let number = 3; number &lt; 50000; number += 2) {
</span></span><span class="line"><span class="ln">13</span><span class="cl">    if (isPrime5(number)) {
</span></span><span class="line"><span class="ln">14</span><span class="cl">        count++;
</span></span><span class="line"><span class="ln">15</span><span class="cl">    }
</span></span><span class="line"><span class="ln">16</span><span class="cl">}
</span></span><span class="line"><span class="ln">17</span><span class="cl">let endTime = performance.now();
</span></span><span class="line"><span class="ln">18</span><span class="cl">let executionTime = endTime - startTime;
</span></span><span class="line"><span class="ln">19</span><span class="cl">console.log(`found ${count} primes`);
</span></span><span class="line"><span class="ln">20</span><span class="cl">console.log(`Execution time: ${executionTime} milliseconds`);
</span></span></code></pre></div><img src="https://nickyreinert.de/en/2023/2023-12-12-prime-numbers-javascript/image-5.png" alt="alt text"><p>It’s not way faster than our initial approach, but it still beats Python by lengths.</p>
<p>Now, as JavaScript is this fast, let’s increase the numbers a little and see how the improved algorithm performs against Python: How long does it take to find all primes up to… wait for it… it’s gonna be legendary… 100 Million!</p>
<p>This is JavaScript performance:</p>
<img src="https://nickyreinert.de/en/2023/2023-12-12-prime-numbers-javascript/image-6.png" alt="alt text"><p>And this is Python:</p>
<img src="https://nickyreinert.de/en/2023/2023-12-12-prime-numbers-javascript/image-7.png" alt="alt text"><p>I stopped the script after 10 Minutes. 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://medium.com/how-to-find-prime-numbers-fast-8d0f7e8bd80f?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">Check the other post</a>


 to find out how to improve Python even more!</p>
<blockquote>
<p>According to 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://t5k.org/howmany.html?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">https://t5k.org/howmany.html</a>


 we have 5.761.455 prime numbers lower than 100.000.000.</p>
</blockquote>
<h2 id="update--what-about-rust-and-c">Update — What about Rust and C++?</h2>
<p>The story does not end here. I tried the same algorithm with Rust. Implementing a progress bar here led to a desaster, I stopped the search after 5 minutes. Without the progress bar it took <strong>400 seconds</strong>. A lot of room for improvement.</p>
<img src="https://nickyreinert.de/en/2023/2023-12-12-prime-numbers-javascript/image-8.png" alt="alt text"><p>And even C++ surprised me in the beginning. The first run took 70 seconds, that’s almost twice the time JavaScript needed. Why that? After a little research I found out, that <strong>-O3</strong> and <strong>-ffast-math</strong> arguments for the compiler are recommended. Now the algorithm is done after 40 seconds:</p>
<img src="https://nickyreinert.de/en/2023/2023-12-12-prime-numbers-javascript/image-9.png" alt="alt text"><p>So… JavaScript is as fast as C++, when it comes to simple calculations. Disclaimer: I’m not a C++ or Rust developer. Leave a comment, if you can explain this!</p>
<img src="https://nickyreinert.de/en/2023/2023-12-12-prime-numbers-javascript/image-10.png" alt="alt text">]]></content:encoded><atom:updated>2026-04-24T17:43:54+02:00</atom:updated><category>blog</category><dc:subject>Lesezeit: 8 Minuten</dc:subject><dc:type>tutorial</dc:type></item><item><title>How to find prime numbers in Python. Feat. Cython.</title><link>https://nickyreinert.de/en/2023/2023-11-11-prime-numbers-python/</link><pubDate>Sat, 11 Nov 2023 12:34:56 +0100</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/en/2023/2023-11-11-prime-numbers-python/</guid><description>[PYTHON] How to find Prime Numbers. Fast. Feat. Cython. There is only one thing that I love more than algorithms: Improving algorithms! This article will not explain one of those sophisticated prime solving formulas — but it will show you, how thoughtful thinking and tinkering will help you with …</description><content:encoded><![CDATA[<h1 id="python-how-to-find-prime-numbers-fast-feat-cython">[PYTHON] How to find Prime Numbers. Fast. Feat. Cython.</h1>
<p>There is only one thing that I love more than algorithms: Improving algorithms! This article will not explain one of those sophisticated prime solving formulas — but it will show you, how thoughtful thinking and tinkering will help you with optimising your code.</p>
<blockquote>
<p>Nota bene: Do you need the JavaScript version? 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://medium.com/javascript-how-to-find-prime-numbers-fast-cbcf6bd62e3d?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">Check it out here!</a>


</p>
</blockquote>
<p>What’s a prime number? All of you know that: It’s a number with only two divider natural divisors: 1 and the number itself.</p>
<p>And when you ask someone for a prime number algorithm, you get two answers: A mathematican will 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://en.wikipedia.org/wiki/Formula_for_primes?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">throw out a complex formula,</a>


 like <strong>Mill’s formula:</strong></p>
<img src="https://nickyreinert.de/en/2023/2023-11-11-prime-numbers-python/image.png" alt="alt text"><p>Wait, what? Ok. The other answer is just a simple loop that checks if there is another number, lower than the number in question, where the operation <strong>modulo</strong> returns 0. The so called “trial division”. In other words: The number in question divided by the other number returns a third number and all numbers are natural, not factorized.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">def isPrime(number):
</span></span><span class="line"><span class="ln">2</span><span class="cl">    for i in range(2, number):
</span></span><span class="line"><span class="ln">3</span><span class="cl">        if (number % i) == 0:
</span></span><span class="line"><span class="ln">4</span><span class="cl">            return False
</span></span><span class="line"><span class="ln">5</span><span class="cl">    
</span></span><span class="line"><span class="ln">6</span><span class="cl">    return True
</span></span></code></pre></div><p>Let’s run this loop to find primes up to 50.000:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">%%time
</span></span><span class="line"><span class="ln">2</span><span class="cl">count = 0
</span></span><span class="line"><span class="ln">3</span><span class="cl">for number in range(2, 50000):
</span></span><span class="line"><span class="ln">4</span><span class="cl">    if isPrime1(number):
</span></span><span class="line"><span class="ln">5</span><span class="cl">        count += 1
</span></span><span class="line"><span class="ln">6</span><span class="cl">print(f&#39;found {count} primes&#39;)
</span></span></code></pre></div><p>On my machine this will take <strong>6 seconds</strong> and find <strong>5.133 primes</strong> (your results may differ on a different machine):</p>
<img src="https://nickyreinert.de/en/2023/2023-11-11-prime-numbers-python/image-1.png" alt="alt text"><p>So, if you are not that much into number theory, what options do we have to improve this algorithm, with some simple tricks?</p>
<p>Let’s start with some obvious things: We don’t need to test all numbers in our “test division”. In fact we can skip every divisor lower than “number / 2”. Why is that? Because if the divisor is greater than “number / 2” the result must be lower than 2 and a faction.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln"> 1</span><span class="cl">def isPrime(number):
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    limit = int(number / 2)
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    for i in range(2, limit + 1):
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        if (number % i) == 0:
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">            return False
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    return True
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">count = 0
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">for number in range(2, 50000):
</span></span><span class="line"><span class="ln">10</span><span class="cl">    if isPrime(number):
</span></span><span class="line"><span class="ln">11</span><span class="cl">        count += 1
</span></span><span class="line"><span class="ln">12</span><span class="cl">print(f&#39;found {count} primes&#39;)
</span></span></code></pre></div><p>Actually, this halves the caluclation time to 3 seconds!</p>
<img src="https://nickyreinert.de/en/2023/2023-11-11-prime-numbers-python/image-2.png" alt="alt text"><p>But there’s even more in this limit declaration: We can reduce it to the square root <strong>(sqrt)</strong> of our target number. Because if one divisor is greater than <strong>sqrt(number)</strong> the other divisor must be lower than <strong>sqrt</strong>(divisor). Meaning: They have already been checked. Let’s do it:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">%%time
</span></span><span class="line"><span class="ln">2</span><span class="cl">import math
</span></span><span class="line"><span class="ln">3</span><span class="cl">def isPrime(number):
</span></span><span class="line"><span class="ln">4</span><span class="cl">    limit = int(math.sqrt(number))
</span></span><span class="line"><span class="ln">5</span><span class="cl">    for i in range(2, limit + 1):
</span></span><span class="line"><span class="ln">6</span><span class="cl">        if (number % i) == 0:
</span></span><span class="line"><span class="ln">7</span><span class="cl">            return False
</span></span><span class="line"><span class="ln">8</span><span class="cl">    
</span></span><span class="line"><span class="ln">9</span><span class="cl">    return True
</span></span></code></pre></div><p>Wooohoo — this leads to an impressive reduction to some milliseconds:</p>
<img src="https://nickyreinert.de/en/2023/2023-11-11-prime-numbers-python/image-3.png" alt="alt text"><p>What’s next?</p>
<p>Why do we check even numbers at all? It’s “natural law” that an even number cannot be a prime (except the magic number 2).</p>
<p>We can safely skip them in our main loop. But not only there, we can also safely skip them in the “test division”. An even factor always leads to an even product in a multiplication. This leads to the assumption, that an even number always leads to a modulo unequal 0 when thrown against a prime!</p>
<p>In Python there is a simple trick to identify even or odd numbers: We can just check the last digit:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">str(integer)[-1]
</span></span></code></pre></div><p>Let’s add this logic to both loops:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln"> 1</span><span class="cl">%%time
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">def isPrime(number):
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    limit = int(math.sqrt(number))
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    for i in range(2, limit + 1):
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        if str(i)[-1] in [0,2,4,5,6,8]:
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">            continue
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">        if (number % i) == 0:
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">            return False
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    
</span></span><span class="line"><span class="ln">10</span><span class="cl">    return True
</span></span><span class="line"><span class="ln">11</span><span class="cl">for number in range(2, 50000):
</span></span><span class="line"><span class="ln">12</span><span class="cl">    if str(number)[-1] in [0,2,4,5,6,8]:
</span></span><span class="line"><span class="ln">13</span><span class="cl">        continue
</span></span><span class="line"><span class="ln">14</span><span class="cl">    if isPrime(number):
</span></span><span class="line"><span class="ln">15</span><span class="cl">        count += 1
</span></span><span class="line"><span class="ln">16</span><span class="cl">print(f&#39;found {count} primes&#39;)
</span></span></code></pre></div><p>Something strange happend:</p>
<img src="https://nickyreinert.de/en/2023/2023-11-11-prime-numbers-python/image-4.png" alt="alt text"><p>The processing time just increased tremendously. Why that? We should implement the last test a little smarter. Right now we are using some string operations and apparently they are slow as heck.</p>
<p>Python allows us to change the step rate of the <strong>range()</strong> function. This way we could just skip even numbers in the loop itself. Way easier. Heads up: As we will start at 3 now, we increment the counter integer beforehand:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln"> 1</span><span class="cl">%%time
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">def isPrime(number):
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    limit = int(math.sqrt(number))
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    for i in range(3, limit + 1, 2):
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        if (number % i) == 0:
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">            return False
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    return True
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">count = 1
</span></span><span class="line"><span class="ln">10</span><span class="cl">for number in range(3, 50000, 2):
</span></span><span class="line"><span class="ln">11</span><span class="cl">    if isPrime(number):
</span></span><span class="line"><span class="ln">12</span><span class="cl">        count += 1
</span></span><span class="line"><span class="ln">13</span><span class="cl">print(f&#39;found {count} primes&#39;)
</span></span></code></pre></div><img src="https://nickyreinert.de/en/2023/2023-11-11-prime-numbers-python/image-5.png" alt="alt text"><p>Waaaay faster — We are back on track. This solution halves the best result from above.</p>
<p>But wait… have you checked 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://medium.com/javascript-how-to-find-prime-numbers-fast-cbcf6bd62e3d?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">the JavaScript version of this script</a>


? Our script is quick bu JavaSript is above and beyond. It found all primes up to 100 Mio. in around 40 seconds… The Python script took 10 Minutes when I interrupted it — with no result. This is devastating, so let’s get back the crown.</p>
<p>There is a way to <strong>make</strong> <strong>Python</strong> scripts <strong>faster</strong>, not by improving the algorithm but the way of running the script. Python is a <strong>versatile</strong>, <strong>high</strong>-<strong>level</strong> <strong>language</strong>, probably easy to learn and with a huge <strong>community</strong>. But it’s slow because of several facts, one of them is the “<strong>dynamic variable</strong> <strong>typing</strong>”. The quick explanation: A variable can be a string, integer or float. Python doesn’t care. Thats why, for example, a division always returns a float, if you not use the double slash (//):</p>
<img src="https://nickyreinert.de/en/2023/2023-11-11-prime-numbers-python/image-6.png" alt="alt text"><p>A float is much more complex than an integer. An integer is just a couple of activated bits, while a float… ok, I don’t want to fall into that rabbit hole. It’s complex and therefore slow.</p>
<p>The answer is “Cython”, bascially also a programming language, but with more C-like features. That’s why it’s call Cython. The trick is now to change our Python code to make it work with Cython.</p>
<p>First make sure that you have the Cython module installed:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">pip install cython
</span></span></code></pre></div><p>This is not too difficult, we just declare our variables upfront:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">def</span> <span class="nf">isPrime5</span><span class="p">(</span><span class="ne">int</span> <span class="n">number</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="n">cdef</span> <span class="ne">int</span> <span class="n">limit</span> <span class="c1"># our limit is always an integer</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="n">limit</span> <span class="o">=</span> <span class="o">&lt;</span><span class="ne">int</span><span class="o">&gt;</span><span class="n">math</span><span class="o">.</span><span class="n">sqrt</span><span class="p">(</span><span class="n">number</span><span class="p">)</span> <span class="c1"># the result is always an integer</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">cdef</span> <span class="ne">int</span> <span class="n">i</span> <span class="c1"># the loop variable is always an integer</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="n">limit</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">number</span> <span class="o">%</span> <span class="n">i</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">            <span class="k">return</span> <span class="n">False</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="k">return</span> <span class="n">True</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="c1"># additionally, we organize the code a little</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">def</span> <span class="nf">countPrimes</span><span class="p">():</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="c1"># and again some upfront declarations</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="n">cdef</span> <span class="ne">int</span> <span class="n">count</span> <span class="o">=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">    <span class="n">cdef</span> <span class="ne">int</span> <span class="n">number</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="n">cdef</span> <span class="ne">float</span> <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="k">for</span> <span class="n">number</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">100000000</span><span class="p">,</span> <span class="mi">2</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">        <span class="k">if</span> <span class="n">isPrime5</span><span class="p">(</span><span class="n">number</span><span class="p">):</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">            <span class="n">count</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="n">f</span><span class="s1">&#39;found {count} primes after {time.time()- start} seconds&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">    <span class="n">countPrimes</span><span class="p">()</span>
</span></span></code></pre></div><p>Name this file primesC.pyx and safe it.</p>
<p>Now we need an additional file called “setup.py” that tells Cython how our script works:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">from</span> <span class="n">setuptools</span> <span class="n">import</span> <span class="n">setup</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">from</span> <span class="n">Cython</span><span class="o">.</span><span class="n">Build</span> <span class="n">import</span> <span class="n">cythonize</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">setup</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="n">ext_modules</span> <span class="o">=</span> <span class="n">cythonize</span><span class="p">(</span><span class="s2">&#34;primesC.pyx&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">)</span>
</span></span></code></pre></div><p>Now you compile your script using this command:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">python setup.py build_ext --inplace
</span></span></code></pre></div><p>This will create a (binary) file named <strong>primesC.cpython-311-darwin.so</strong> which is bascially just a Python module. You can now quickly reference it on the command line like this (or use it in your Python code):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">python -c &#34;import primesC; primesC.count_primes()&#34;
</span></span></code></pre></div><p>And there you go: Instead of waiting Ten minutes, the script now returns a result after almost 79 seconds:</p>
<img src="https://nickyreinert.de/en/2023/2023-11-11-prime-numbers-python/image-7.png" alt="alt text"><p>Well, this is still way slower than JavaScript (40 seconds), but it also improves the initial script tremendously.</p>
<img src="https://nickyreinert.de/en/2023/2023-11-11-prime-numbers-python/image-8.png" alt="alt text"><p>Mandatory Preview Image</p>
]]></content:encoded><atom:updated>2026-05-07T15:36:59+02:00</atom:updated><category>blog</category><dc:subject>Lesezeit: 5 Minuten</dc:subject><dc:type>tutorial</dc:type></item><item><title>How to develop a balanced card game with Python</title><link>https://nickyreinert.de/en/2023/2023-01-28-card-game-development/</link><pubDate>Sat, 28 Jan 2023 12:34:56 +0100</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/en/2023/2023-01-28-card-game-development/</guid><description>Join me on my journey from drawing for fun, to Excel, over Python to a, probably working and good balanced card game.
But why? Shameless bragging: I own a Remarkable2 and from time to time I’m drawing random stuff on it. Just for the sake of recreation. I’m not a good painter. It’s no serious art, …</description><content:encoded><![CDATA[<p>Join me on my journey from drawing for fun, to Excel, over Python to a, probably working and good balanced card game.</p>
<h2 id="but-why">But why?</h2>
<p>Shameless bragging: I own a Remarkable2 and from time to time I’m drawing random stuff on it. Just for the sake of recreation. I’m not a good painter. It’s no serious art, but it’s fun.</p>
<img src="https://nickyreinert.de/en/2023/2023-01-28-card-game-development/image.png" alt="Nope, I’m not"><p>One day I decided to draw a couple of figures based on geometric shapes and I thought: Why don’t make a card game out of it. And that’s where the story begins where I tried to figure out, how to develop a balanced card game.</p>
<h2 id="the-game-setup">The game setup</h2>
<p>First I equipped every figure, I call them characters, with four different attributes that are somehow related to <em>geometry</em>:</p>
<ul>
<li>amount of edges</li>
<li>age</li>
<li>symmetry bonus</li>
<li>euclidean reciprocal</li>
</ul>
<p>If you look at the characters I drawed, the “amount of edges” and the “symmetry bonus” should make sense. Those values ought to be fixed. The “age” and the “euclidean reciprocal” are meant to be “fictive” values that could vary.</p>
<img src="https://nickyreinert.de/en/2023/2023-01-28-card-game-development/image-1.png" alt="My set of characters"><p>I also created two simple formulas to calculate attacking and defense values:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">Attack Value = (1 + amount of edges) * (1 + symmtry bonus)
</span></span><span class="line"><span class="ln">2</span><span class="cl">Defense Value = age * euclidean reciprocal
</span></span></code></pre></div><p>Those are the game rules:</p>
<ul>
<li>every player gets a stack of cards, laying in front of the player, hidden</li>
<li>the player whose turn it is draws the top card from the own stack</li>
<li>this player has to chose an opponent from the other players</li>
<li>now both players compare the attack and defense values of their characters, the winner gets both cards and puts them under the own stack</li>
<li>now the player to the left of the last player draws a card from the own stack and the game proceeds</li>
<li>if one player has all cards, this player wins the game</li>
</ul>
<p>That was my starting point. My first approach was to find out how each character would perform in a fight against it’s peers.</p>
<h2 id="first-stop-excel">First stop: Excel</h2>
<p>I’m in love with Excel, so I created a sheet with dozens of formulas. It’s not sophisticated, it’s complex and not worth complaining at — as this does not help achieving the goal.</p>
<p>The idea was to iterate through the random values for each charachter to finaly get a “fair chance” for every character to win. The range for the random values is always based on the outcome of the current “fight”. This results in a couple of circle references. That’s the result:</p>
<img src="https://nickyreinert.de/en/2023/2023-01-28-card-game-development/image-2.png" alt="alt text"><p><em>(If you don’t know it yet: Enabling “iterative calculations” in Excel allows you to use self referencing formulas, which sometimes help you to crunch numbers)</em></p>
<p>My success indicator was the <strong>average chance of winning</strong> for all characters. Which is 87% in the screenshot. The problem with my setup was: This value does not come from the values you actually see, but from the previous ones. So I need to adapt my strategy and invest a little more time.</p>
<h2 id="next-stop-python">Next stop: Python</h2>
<p>As I am also in love with Python, I switched to Jupyter. The algorithm was quite easy, I’m not going to post it here. Again: It does not help achieving the gol. Let me just give you some pseudo code:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># n sets of 27 characters, where each set contains different values </span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"># for &#39;age&#39; and &#39;euclidean reciprocal&#39; </span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">character_variations</span> <span class="o">=</span> <span class="p">{</span><span class="o">...</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="k">for</span> <span class="n">charachters</span> <span class="ow">in</span> <span class="n">character_variations</span><span class="p">:</span> 
</span></span><span class="line"><span class="ln">5</span><span class="cl">  <span class="k">for</span> <span class="n">character_1</span> <span class="ow">in</span> <span class="n">characters</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="k">for</span> <span class="n">character_2</span> <span class="ow">in</span> <span class="n">characters</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">      <span class="n">fight</span><span class="p">(</span><span class="n">character_1</span><span class="p">,</span> <span class="n">character_2</span><span class="p">)</span>
</span></span></code></pre></div><p>Running this code returns a table containing the win/lose rate for every character. Again I looked for a value to measure the “balance” of the set of characters. I played around using average values, standard deviation and so on and finally came to one conclusion: I am on the wrong track.</p>
<h2 id="epiphany">Epiphany</h2>
<p>I want to create a card game. In a card game you have <strong>n players</strong> with a <strong>random stack of cards</strong> to play with. I don’t need to calculate how powerful every character is. I don’t want to balance the power between each character. The playing mechanic of my card game is solely based on chance. If you play a game you need to come to an end, eventually.</p>
<p>That’s why I took my simple algorithm to a new level. I’m just showing you how to call it, the full script is on Github (see bottom of page).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="n">variable_attributes</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    <span class="s1">&#39;attribute_2&#39;</span><span class="p">:</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">50</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span> <span class="c1"># min, max</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    <span class="s1">&#39;attribute_4&#39;</span><span class="p">:</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">30</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span> <span class="c1"># min, max</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="n">gameEngine</span> <span class="o">=</span> <span class="n">GameEngine</span><span class="p">(</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    <span class="n">debug</span><span class="o">=</span><span class="n">False</span><span class="p">,</span> 
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    <span class="n">bonus_attack_activator</span><span class="o">=</span><span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">6</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="n">bonus_defense_activator</span><span class="o">=</span><span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">],</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="n">escape_activator</span><span class="o">=</span><span class="p">[</span><span class="mi">3</span><span class="p">],</span> 
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="n">dice_range</span><span class="o">=</span><span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">    <span class="n">variable_attributes</span><span class="o">=</span><span class="n">variable_attributes</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="n">gameEngine</span><span class="o">.</span><span class="n">randomizeCharacterAttributes</span><span class="p">(</span><span class="n">variations</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="n">gameEngine</span><span class="o">.</span><span class="n">simulateRealGames</span><span class="p">(</span><span class="n">games</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">round_limit</span><span class="o">=</span><span class="mi">1000</span><span class="p">,</span> <span class="n">players</span><span class="o">=</span><span class="mi">4</span><span class="p">,</span> <span class="n">characters_per_stack</span><span class="o">=</span><span class="mi">3</span><span class="p">,</span> <span class="n">sibblings_per_character</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="n">gameEngine</span><span class="o">.</span><span class="n">simulateAllvsAll</span><span class="p">(</span><span class="n">games</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="n">gameEngine</span><span class="o">.</span><span class="n">exportGameStatsToCsv</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="n">gameEngine</span><span class="o">.</span><span class="n">showSummary</span><span class="p">()</span>
</span></span></code></pre></div><h2 id="changes-to-the-game-mechanic">Changes to the game mechanic</h2>
<p>First I added a new rule: Characters should have either an attack or defense bonus capability. This means: Before two players fight against each other, they roll a dice to improve their attack or defense values or even escape from the fight. This adds more dynamic to the game and probably some kind of strategic moment.</p>
<p>I hard-coded those rules into the code and will explain them briefly:</p>
<h3 id="parameters">Parameters</h3>
<p>Let’s start with the <strong>attack bonus</strong> that e.g. needs to be activated by throwing a 2 or 6:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">bonus_attack_activator=[1, 6]
</span></span></code></pre></div><p>On success, the attacker may roll the dice two times. The greater value can be used as an <strong>attack multiplicator</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln"> 1</span><span class="cl">    def calculateAttackValue(self, character):
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">        
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">        attack_value = (1 + character[&#39;attributes&#39;][&#39;attribute_1&#39;]) * (1 + character[&#39;attributes&#39;][&#39;attribute_3&#39;])
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        eleveated_attack_value = attack_value
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        # roll the attack dice
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        if &#39;attack&#39; in character[&#39;bonus&#39;]:
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">            if random.choice(self.dice_range) in self.bonus_attack_activator:
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">                dice_1 = random.choice(self.dice_range)
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">                dice_2 = random.choice(self.dice_range)
</span></span><span class="line"><span class="ln">10</span><span class="cl">            
</span></span><span class="line"><span class="ln">11</span><span class="cl">                if dice_1 &gt; dice_2:
</span></span><span class="line"><span class="ln">12</span><span class="cl">                    eleveated_attack_value *= dice_1
</span></span><span class="line"><span class="ln">13</span><span class="cl">                else:
</span></span><span class="line"><span class="ln">14</span><span class="cl">                    eleveated_attack_value *= dice_2
</span></span><span class="line"><span class="ln">15</span><span class="cl">        if self.debug : print(f&#39;\tbase attack: {attack_value}, elevated attack {eleveated_attack_value}&#39;)
</span></span><span class="line"><span class="ln">16</span><span class="cl">        return eleveated_attack_value
</span></span><span class="line"><span class="ln">17</span><span class="cl">     bonus_defense_activator=[1, 2, 3, 4, 5, 6],
</span></span></code></pre></div><p>The same logic applies to the <strong>defense bonus</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">bonus_defense_activator=[1, 2, 3, 4, 5, 6]
</span></span></code></pre></div><p>If you define the full dice range the defending player does not need to activate the defense bonus. This player simply throws the dice two times to get the multiplicator.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln"> 1</span><span class="cl">    def calculateDefenseValue(self, character):
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">        defense_value = character[&#39;attributes&#39;][&#39;attribute_2&#39;] + character[&#39;attributes&#39;][&#39;attribute_4&#39;]
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">        elevated_defense_value = defense_value
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">        # roll the defense dice
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">        if &#39;defense&#39; in character[&#39;bonus&#39;]:
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">            if random.choice(self.dice_range) in self.bonus_defense_activator:
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">                
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">                dice_1 = random.choice(self.dice_range)
</span></span><span class="line"><span class="ln">10</span><span class="cl">                dice_2 = random.choice(self.dice_range)
</span></span><span class="line"><span class="ln">11</span><span class="cl">            
</span></span><span class="line"><span class="ln">12</span><span class="cl">                if dice_1 &gt; dice_2:
</span></span><span class="line"><span class="ln">13</span><span class="cl">                    elevated_defense_value += dice_1
</span></span><span class="line"><span class="ln">14</span><span class="cl">                else:
</span></span><span class="line"><span class="ln">15</span><span class="cl">                    elevated_defense_value += dice_2
</span></span><span class="line"><span class="ln">16</span><span class="cl">        if self.debug : print(f&#39;\tbase defense: {defense_value}, elevated defense {elevated_defense_value}&#39;)
</span></span><span class="line"><span class="ln">17</span><span class="cl">        return elevated_defense_value
</span></span></code></pre></div><p>Finally the <strong>escape bonus</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">escape_activator=[3]
</span></span></code></pre></div><p>And the logic behind it:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln"> 1</span><span class="cl">    def calculateEscapeValue(self, character):
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">        # roll the escape dice
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">        escape_success = False
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        if &#39;escape&#39; in character[&#39;bonus&#39;]:
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">            if random.choice(self.dice_range) in self.bonus_attack_activator:
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">                escape_bonus = random.choice(self.dice_range)
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">                if escape_bonus in self.escape_activator:
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">                    escape_success = True   
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">                else:
</span></span><span class="line"><span class="ln">10</span><span class="cl">                    escape_success = False
</span></span><span class="line"><span class="ln">11</span><span class="cl">        
</span></span><span class="line"><span class="ln">12</span><span class="cl">        if self.debug : print(f&#39;\tescape success: {escape_success}&#39;)
</span></span><span class="line"><span class="ln">13</span><span class="cl">        return escape_success
</span></span></code></pre></div><p>There’s not much to explain here: The player can pull back from a fight, if the dice shows the required number.</p>
<p>As mentioned above, the “age” (attribute_2) and “euclidean reciprocal” (attribute_4) are the <strong>attributes that I want to optimize</strong>. I’m just defining a range for those attributes:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">variable_attributes</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="s1">&#39;attribute_2&#39;</span><span class="p">:</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">50</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span> <span class="c1"># min, max</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="s1">&#39;attribute_4&#39;</span><span class="p">:</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">30</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span> <span class="c1"># min, max</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The algorithm will now choose random values between 1 and 50 respectively 1 and 30 for every character variation it creates.</p>
<p><strong>Completion</strong></p>
<p>Now, with every starting parameter set, I <strong>initalize</strong> the “<strong>game engine</strong>”:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">gameEngine</span> <span class="o">=</span> <span class="n">GameEngine</span><span class="p">(</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="n">debug</span><span class="o">=</span><span class="n">False</span><span class="p">,</span> 
</span></span><span class="line"><span class="ln">3</span><span class="cl">    <span class="n">bonus_attack_activator</span><span class="o">=</span><span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">6</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">    <span class="n">bonus_defense_activator</span><span class="o">=</span><span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">    <span class="n">escape_activator</span><span class="o">=</span><span class="p">[</span><span class="mi">3</span><span class="p">],</span> 
</span></span><span class="line"><span class="ln">6</span><span class="cl">    <span class="n">dice_range</span><span class="o">=</span><span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">],</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">    <span class="n">variable_attributes</span><span class="o">=</span><span class="n">variable_attributes</span><span class="p">)</span>
</span></span></code></pre></div><p>This will also load a “<strong>base set of attributes</strong>” for all 27 characters from a JSON-file. It contains attributes I defined from the scratch, as a starting point. It looks like that:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln"> 1</span><span class="cl">{
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    &#34;character_1&#34;: {
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">        &#34;attributes&#34;: {
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">            &#34;attribute_1&#34;: 0, # remember, this is the amount of edges, fixed
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">            &#34;attribute_2&#34;: 42, # the age, this can be randomized
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">            &#34;attribute_3&#34;: 1, # the symmetry bonus, fixed
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">            &#34;attribute_4&#34;: 2 # the euclidean reciprocal, can be randomized
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        },
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">        &#34;bonus&#34;: [&#34;escape&#34;]
</span></span><span class="line"><span class="ln">10</span><span class="cl">    },
</span></span><span class="line"><span class="ln">11</span><span class="cl">...
</span></span><span class="line"><span class="ln">12</span><span class="cl">}
</span></span></code></pre></div><p>The first step now is to find <strong>n random variations</strong> for the base set of all characters to randomize <strong>attribute_2</strong> and <strong>attribute_4</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">gameEngine</span><span class="o">.</span><span class="n">randomizeCharacterAttributes</span><span class="p">(</span><span class="n">variations</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span>
</span></span></code></pre></div><p>No magic here. The higher the number of variations is, the more iterations the full process will do.</p>
<h2 id="starting-the-simulation">Starting the simulation</h2>
<h3 id="real-games">Real games</h3>
<p>After that I will start the simulation for <em>real games:</em></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">gameEngine.simulateRealGames(
</span></span><span class="line"><span class="ln">2</span><span class="cl">  games=1, 
</span></span><span class="line"><span class="ln">3</span><span class="cl">  round_limit=100, 
</span></span><span class="line"><span class="ln">4</span><span class="cl">  players=4, 
</span></span><span class="line"><span class="ln">5</span><span class="cl">  characters_per_stack=3, 
</span></span><span class="line"><span class="ln">6</span><span class="cl">  sibblings_per_character=1)
</span></span></code></pre></div><p>The <strong>amount of games</strong> I want to simulate should be clear. The round limit is a threshold to prevent a single game from running endlessly. The limit of 100 is pretty high. Imagine you’re playing a card game with your friends and after 100 rounds you’re still playing. Motivating, right? The amount of players should be clear, too.</p>
<p>The amount of characters per stack simply defines with how many cards every player starts initially. I have 27 different characters, so 4 players allows 6 cards which leaves 3 character cards in the pool.</p>
<p>The sibblings per character parameter allows to increase the pool size. If set to 2, you have 27 * 2 cards to play with and therefore more cards per every player’s stack.</p>
<p>Be warned: The amount of iterations comes from the formular <strong>games</strong> times <strong>variations</strong> and 1 <strong>base varation</strong> for 27 <strong>characters</strong> and <strong>maximum</strong> 1.000 rounds:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">27 * 27 * 11 * 100 * 100 = 80.190.000 iterations max
</span></span></code></pre></div><p>That’s quite high. But it should give you a statistical useful result. I kept it simple and only simulated 1 game right now.</p>
<h3 id="all-vs-all">All vs all</h3>
<p>I also migrated the inital approach which allows me to find out how powerful every character is. This simply initiates a fight of every character against every other character.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">gameEngine.simulateAllvsAll(games=100)
</span></span></code></pre></div><p>There’s not much to explain here. This will loop through every variation created above. Be warned again: 100 games for 10 variations and 1 base variation for 27 characters means:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">27 * 27 * 11 * 100 = 801.900 iterations
</span></span></code></pre></div><h2 id="the-statistics">The statistics</h2>
<p>Those two lines, or at least the last one, will finally return the desired results:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-gdscript3" data-lang="gdscript3"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">gameEngine</span><span class="o">.</span><span class="n">exportGameStatsToCsv</span><span class="p">()</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">gameEngine</span><span class="o">.</span><span class="n">showSummary</span><span class="p">()</span>
</span></span></code></pre></div><p>The first method creates three CSV-files. One file called “<strong>stats.csv</strong>” contains all stats from both simulation for every variation, game and round. For the above mentioned run this file has over 7 MBytes. One line for each “fight”. A lot of data to analyze. Luv it.</p>
<p>The second file “<strong>stats_per_character.csv”</strong> contains stats for each charachter. This will help to understand, how the “power” is being distributed amongst all characters.</p>
<p>The third file “<strong>stats_summary.csv</strong>” contains a summery of all simulated “real games.” The method showSummary() will also return this information, which is right now the most important one:</p>
<img src="https://nickyreinert.de/en/2023/2023-01-28-card-game-development/image-3.png" alt="alt text"><p>The first column contains all variations. Keep in mind: variation 0 is the one I defined in advance and 1 to 10 are the ones containing random values for attribute_2 (age) and attribut_4 (euclidean reciprocal) for each character.</p>
<p>As mentioned above, I only simulated 1 game per variation for the real game simulator. The third column shows how long it took until the game ended and a player wins. And finally the last column showing what player wins.</p>
<h3 id="conclusion">Conclusion</h3>
<p>Let’s look at the statistics for varation no. 10, where it only takes 15 rounds to finish the game. Which seems a good limit for a casual card game — but remember that I only “played” 1 game. Remember, when I first tried to find a good balance for the characters, to give them a fair chance? This is how the stats look now:</p>
<img src="https://nickyreinert.de/en/2023/2023-01-28-card-game-development/image-4.png" alt="alt text"><p>As you can see, character_24 is clearly the most powerful character in the set. 49 wins and only 3 loses lead to a win/lose rate of over 16, which outperforms the others with no doubt.</p>
<p>Retrospectively I can say, that it is pretty obvious that a balanced game does not require the same strength for all characters. This could work, but then I must add more strategic options to the game.</p>
<p>Well, I am far away from being a game designer. And this script does not return the perfect definition for my card game, but it helps me finding one and also gives a good understanding of the game mechanic. It allows me to easily fine tune the parameters of the game to find a perfect setup for a…hopefully… satisfying game.</p>
<p>If you are a game designer or have some knowledge in this field, I’d happy to get a comment on how I am performing in this area.</p>
<h2 id="source">Source</h2>
<p>You can find the Jupyter notbook on 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://gist.github.com/nickyreinert/05dc269fc5091d9ecc7b03836970cab0?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">https://gist.github.com/nickyreinert/05dc269fc5091d9ecc7b03836970cab0</a>


</p>
]]></content:encoded><atom:updated>2026-05-07T15:36:59+02:00</atom:updated><category>blog</category><dc:subject>Lesezeit: 8 Minuten</dc:subject><dc:type>tutorial</dc:type></item><item><title>Managing Adobe Launch Tags with Local Overrides in Chrome DevTools</title><link>https://nickyreinert.de/en/2022/2022-08-17-adobe-launch/</link><pubDate>Wed, 17 Aug 2022 12:34:56 +0100</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/en/2022/2022-08-17-adobe-launch/</guid><description>POV: You need to maintain a website that implements Adobe Launch (or probably any tag manager). The tag is a huge mess and contains fragmented rules and no clear naming system. Adding features or fixing bugs can be a real pain in the ass: Finding the right rule, modifying the custom code in a not so …</description><content:encoded><![CDATA[<p>POV: You need to maintain a website that implements Adobe Launch (or probably any tag manager). The tag is a huge mess and contains fragmented rules and no clear naming system. Adding features or fixing bugs can be a real pain in the ass: Finding the right rule, modifying the custom code in a not so convenient code editor, saving the rule, re-building the library, re-loading the page, testing and seeing, that something is still missing, back to step 1. How can you improve that?</p>
<p><strong>Disclaimer</strong>: If you are a web developer, this should be an old hat. If you are a developer and never heard about local overrides, you should continue reading.</p>
<h2 id="have-you-met-local-overrides">Have you met “Local Overrides”?</h2>
<p>Solution: Well, you could choose the sophisticated way: Host the tag manager for yourself, use some kind of version control system to synchronize the source code with your local development environment. If this is not possible the solution is “local overrides in Chrome”. 





  
  

  
  
    
    
    
  

  
  
  

  
    
      
    
  

  
  <a href="https://developer.chrome.com/blog/new-in-devtools-65/?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous#overrides" target="_blank" rel="noopener noreferrer">Chrome introduced this feature with Chrome 65 in 2018</a>


 and it allows you to, surprise, override remote resources with local files.</p>
<h2 id="cut-it-tell-me-how">Cut it, tell me how!</h2>
<ol>
<li>Go to site that contains your tag, open the DevTools of Chrome and go to the “Sources” area and then select the “Overrides” tab:</li>
</ol>
<img src="https://nickyreinert.de/en/2022/2022-08-17-adobe-launch/image.png" alt="alt text"><ol start="2">
<li>
<p>Click the area saying “+ Select folder for overrides” and choose your folder wisely. You probably need to confirm this with your operating system.</p>
</li>
<li>
<p>Now select the “Page” tab to the left, which should show you all the resources this page uses. Look for a subfolder called “assets.adobetm.com” — the host where Adobe’s tags are comming from. Inside that folder you’ll find a resource like this:</p>
</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">launch-EN919758db9a654a17bac7d184b99c4820.min.js
</span></span></code></pre></div><p>This is the tag Adobe uses on it’s own site. Right click this item and select “Save for overrides”:</p>
<img src="https://nickyreinert.de/en/2022/2022-08-17-adobe-launch/image-1.png" alt="alt text"><ol start="4">
<li>
<p>Now switch back to the “Overrides” tab and open the folder you just added. You should see a sub folder with the host name and within that the resource you just “saved for override”.</p>
</li>
<li>
<p>Now you may navigate to the folder in your file system and edit it with your preferred code editor, e.g. Visual Studio Code. But wait, there’s one step missing: You just saved the minimized version, which is kind of difficult to handle.</p>
</li>
<li>
<p>Go back to the resource in the “Page” tab, right click it and select “Open in new tab”. From the address bar remove the “min” from the resource name and load the “un-minified” ressource.</p>
</li>
<li>
<p>Copy the content to the clipboard, paste it into the locally saved, minimized version.</p>
</li>
<li>
<p>You can now easily edit everything you need inside this file. If you reload the page, Chrome will not load this particular resource from Adobe’s server. Use the top breadcrumb navigation to browse between the different sections, like the rules or data elements:</p>
</li>
</ol>
<img src="https://nickyreinert.de/en/2022/2022-08-17-adobe-launch/image-2.png" alt="alt text"><ol start="9">
<li>If you are done with your modifications, copy the respective code section to your actual Adobe launch tag in the web interface and re-build the library.</li>
</ol>
<p><strong>Side effect:</strong> You can now utilize a version control system for this local file. This also enables you to use breakpoints.</p>
<p>If you’re done with your local development and you re-built your Adobe Launch library, you should de-activate the feature — otherwise the browser will ignore the content from your updated tag.</p>
<p>Enjoy.</p>
]]></content:encoded><atom:updated>2026-04-24T17:43:54+02:00</atom:updated><category>blog</category><category>web-development</category><category>adobe-launch</category><dc:subject>Lesezeit: 5 Minuten</dc:subject><dc:type>tutorial</dc:type></item><item><title>Web 3.0 explained, without the hype</title><link>https://nickyreinert.de/en/2022/2022-08-01-web3-withouth-the-hype/</link><pubDate>Mon, 01 Aug 2022 12:34:56 +0100</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/en/2022/2022-08-01-web3-withouth-the-hype/</guid><description>Everyone is talking about Web 3.0 and it’s “driving technologies” Blockchain, Bitcoin, NFT or the Metaverse. Terms that belongs to every company’s roadmap if it’s want to be innovative. But if you try to find information about — let’s take Bitcoin — you’ll quickly find yourself on some …</description><content:encoded><![CDATA[<p>Everyone is talking about <strong>Web 3.0</strong> and it’s “driving technologies” <strong>Blockchain</strong>, <strong>Bitcoin</strong>, <strong>NFT</strong> or the <strong>Metaverse</strong>. Terms that belongs to every company’s roadmap if it’s want to be innovative. But if you try to find information about — let’s take Bitcoin — you’ll quickly find yourself on some cryptocurrency site that is celebrating the technology, declaring a growth of 1.000 % and then trying to sell you something. That’s where I’m jumping in: Let me try to describe those four main technologies as simple as possible, without the hype. And at the end you’ll kindly buy 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://opensea.io/assets/matic/0x2953399124f0cbb46d2cbacd8a89cf0599974963/85924082302081312152251891657020775129690493546484874721949341267311034630145?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">my very arty NFT</a>


.</p>
<blockquote>
<p>Disclaimer: I’m pretty sure there are some details missing, maybe even important facts. Tell me, I will add it. This article is under permanent construction.</p>
</blockquote>
<h2 id="the-metaverse">The Metaverse</h2>
<h3 id="what-is-the-metaverse">What is the Metaverse?</h3>
<p>The term itself comes from the science fiction author <strong>Neal Stephenson</strong>. He “invented” it for his 1992 story “Snow Crash”. The concept of the <strong>Metaverse</strong> is quite similar to the well-known <strong>Cyberspace</strong>, a term first used in 1982 by author <strong>William Gibson</strong>. While “cyber” comes from the greek word “kybernetes”, meaning “steersman”, meta comes from the greek word for “beyond”.</p>
<p>The suffix <em>verse</em> will probably remind you of the word <em>universe</em> which comes from the Latin word for “all things”. And the suffix <em>space,</em> yeah well I guess you get it, is not so far away from <em>the universe</em>.</p>
<img src="https://nickyreinert.de/en/2022/2022-08-01-web3-withouth-the-hype/image.png" alt="alt text"><p>Basically both terms, <strong>Metaverse</strong> and <strong>Cyberspace</strong>, could stand for a virtual reality where you can do everything you want to do — respectively the “owner” allows you to do.</p>
<p>The main idea is to use the <strong>Metaverse</strong> not only to play games, but also to do everyday stuff: to buy shoes, operate machines, visit your lawyer and cut the hair of your virtual avatar. And if you like abbreviations, here are some: You can enjoy the <strong>Metaverse</strong> as a virtual reality (<strong>VR)</strong> but also <strong>augmented</strong> or <strong>mixed reality (AR, MR)</strong>.</p>
<p>The idea of the <strong>Metaverse</strong> holds room for philosophical discussion. It sounds tempting to do everything online. What if you don’t need the real world anymore? Imagine you will be connected to the <strong>Metaverse</strong> from the very beginning of your life. Imagine that every sense will be simulated and your needs are satisfied by stimulation of particular parts of your brain and youl’ll be fed by nutrition infusions. Imagine that you will start using a <strong>Metaverse</strong> inside the <strong>Metaverse</strong>! Are we already living in a <strong>Metaverse</strong>?</p>
<h3 id="is-the-metaverse-new">Is the Metaverse new?</h3>
<p>No. As already mentioned, the term itself is around 30 years old. And also the technical idea is not new: In 2003 a platform called “<strong>Second Life</strong>” was launched. Second Life was not only about gaming, it was also about socializing or establishing a virtual Alter Ego. The current media circus around the <strong>Metaverse</strong> becomes a hype driven by improved technical possibilities.</p>
<p><strong>Facebook</strong>, looking for new areas of operation, pulled this pretty smart marketing move by naming themself “<strong>Meta</strong>”, which could lead to the assumption that they invented the <strong>Metaverse</strong> — in fact they just trying to hook on this trend.</p>
<h3 id="does-the-metaverse-requires-nft-or-blockchain">Does the Metaverse requires NFT or Blockchain?</h3>
<p>No. It may rely on the Internet, that’s all. It relies on someone who is hosting the software and take care of it and it’s users. There is no reason that the <strong>Metaverse</strong> will only work because of and with <strong>Blockchain</strong>, <strong>Bitcoin</strong> and <strong>NFT</strong>.</p>
<p>Sure you could say, that you need some kind of currency. But that would mean that the Metaverse is focussed on consumption. We don’t want that. Right? Even then, why shouldn’t you use your good old PayPal account with 2FA?Because a Bitcoin transaction is safe? In general: Right. But if you use an intermediate — and that’s what you do — you create a weak point.</p>
<h3 id="how-can-i-enter-the-metaverse">How can I enter the Metaverse?</h3>
<p>There is not this one “<strong>Metaverse</strong>”. Right now there are a couple of virtual worlds out there, hosted by different companies. If you for example search the <strong>Oculus</strong> store for “metaverse” you will find a couple of apps claiming to take you into the <strong>Metaverse</strong>. If you search the web for <strong>Metaverse</strong>, you will find a couple of providers offering you access to “their interpretation” of the <strong>Metaverse</strong>, and they are not even connected.</p>
<p>Compared to the <strong>World Wide Web</strong> — which simply describes all conected web sites, the <strong>Metaverse</strong> is a term not covered by one single solution. Maybe sometime in the future someone develops a protocol or a standardized system like <strong>Tim Berner Lee</strong> once did for the World Wide Web. Although there is not “one Metaverse”, I will stick to this term, because “Metaverses” just sounds strange.</p>
<p>If you want to enter one of those <strong>Metaverses</strong> (sounds strange, right?), a virtual head set is the most common way. This will give you the full experience. But you could also just open a <strong>browser</strong> and go to sandbox.game or decentraland — two quite populare but separated virtual worlds — and walk through the virtual landscapes there, purchase land, talk to people. Even a smart <strong>Smart TV</strong> could take you into the <strong>Metaverse</strong>. In the far future it’s maybe an USB interface in the back of your head. Then the <strong>Metaverse</strong> will probably be more than an audio-visual simulation; it will probably feed all other senses.</p>
<h2 id="the-blockchain">The Blockchain</h2>
<h3 id="what-is-the-blockchain">What is the Blockchain?</h3>
<p>A <strong>blockchain</strong> is kind of a database, but please don’t call it that. A block is a container for data and therefore information. As all blocks are connected they eventually form a chain: The <strong>blockchain</strong>. How the blocks are connected is one of the most important selling points: Every block comes with a block header that contains, among other fields, two important fields: One field holds a hash for all the information of the current block’s payload, another field contains the hash for the previous block’s header. This makes the information within the blockchain secure: If you tamper the data of one block, you need to re-create all the hashes of the following blocks.</p>
<img src="https://nickyreinert.de/en/2022/2022-08-01-web3-withouth-the-hype/image-1.png" alt="Now you know"><h3 id="is-there-one-blockchain">Is there <code>one</code> Blockchain?</h3>
<p>No. As more and more, in fact thousands, new application areas emerged, there are all kind of <strong>blockchains</strong> out there in the wild. There are public and private <strong>blockchains</strong>, there are different confirmation processes (describing how a new block is considered to be valid) and there are <strong>blockchains</strong> holding cryptocurrencies, contracts, applications and everything else that could possibly be digitalized.</p>
<h3 id="is-the-blockchain-new">Is the Blockchain new?</h3>
<p>The first mentions of this concept can be found in the early 1980s. In 2008 the main idea was improved by a yet not know individual (or group?), who proposed some additional features, like decentralization and hashing. The fact that this mechanism is at least 14 years old, which is a lot in information technology, should show that a <strong>blockchain</strong> is an old hat.</p>
<h3 id="is-the-blockchain-decentralized">Is the Blockchain decentralized?</h3>
<p>Yes. The concept itself is based on a network of connected hosts. Every host holds a full copy of a <strong>blockchain</strong> and every host can confirm new blocks. A <strong>blockchain</strong> is fully decentralized by design.</p>
<p>But…</p>
<p>The service based on a <strong>blockchain</strong> are not necessarily decentralized. 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://bitcoin.org/en/full-node?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">Setting up the Bitcoin blockchain is not a simple thing</a>


. And that’s the documentation of one of the most popular Blockchain projects, don’t try to get your head around installing the more niche ones.</p>
<p>The concept is totally decentralized, but to make it usable by the public and therefore use it in a commercial way, a lot of companies arised in this field of business. They are offering access to several services hosted on the Blockchain. And if there’s a single company, it’s not centralized anymore. Now you have a single point of failure.</p>
<p>Besides that, from a technical perspective this “decentralisation” is not a big deal. Modern databases are able hold up with this feature. A lot of popular databases support replication across several locations, like MongoDB and its replica set architecture. Not peer-2-peer enough for you? There are even real peer-2-peer databases, like OrbitDB.</p>
<h3 id="is-a-blockchain-secure">Is a Blockchain secure?</h3>
<p>The mechanism itself is pretty secure and safe against tampering or manipulating existing information. That is a big win. Though this does not mean that all applications based on the blockchain are secure. First of all: Security is not inheritable. As just mentioned above, the <strong>blockchain</strong> comes to you via a provider — the vulnerable point.</p>
<p>





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://www.ftc.gov/news-events/data-visualizations/data-spotlight/2022/06/reports-show-scammers-cashing-crypto-craze?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">It happens on a regular base</a>


, that people get scammed or loose money when dealing with <strong>blockchain</strong> based services. Besides that, the current implementation with <strong>SHA256</strong> may loose it’s weight as soon as we are able to use Quantum computers. And that only describe two weak spots, there are way more, starting with “social engineering” and up to the 51% percent attack.</p>
<h3 id="is-a-blockchain-the-better-database">Is a Blockchain the better database?</h3>
<p>While it’s arguable that a <strong>blockchain</strong> is a database at all, I would never consider a <strong>blockchain</strong> the <em>better</em> database. As always it depends on the use case. A Blockchain is an option to store data in safe way, but you cannot — like it’s just not possible — change the data in a <strong>blockchain</strong>. You would need to add a new block. Besides that, a <strong>blockchain</strong> is probably too slow o be a good database.</p>
<h3 id="is-the-blockchain-useless">Is the Blockchain useless?</h3>
<p>A blockchain has three main features: It’s <strong>transparent</strong>, it’s <strong>secure</strong> and it’s running <strong>decentralized</strong>. It’s not useless, it’s a nice piece of technology. But it’s not the answer to everything. It’s best known use case is the transaction ledger. You can also use it to store security relevant source code in it or contracts (see → <strong>NFT</strong>). But that does not mean, that you must implement it into your business processes. It’s just another option if your looking for solutions.</p>
<h2 id="the-bitcoin--the-cryptocurrency">The Bitcoin / the Cryptocurrency</h2>
<h3 id="what-is-the-bitcoin-what-is-a-cryptocurrency">What is the Bitcoin? What is a Cryptocurrency?</h3>
<p>The Bitcoin is the oldest cryptocurrency, “found” in 2008 by the mysterious <strong>Nakamato</strong> — if this is his real name. <strong>Bitcoin</strong> is using a <strong>blockchain</strong> as it’s ledger. And that’s why it’s called a <strong>Cryptocurrency</strong>: A blockchain uses cryptographic algorithms to protect it’s integrity. While <strong>Bitcoin</strong> is the mother of all cryptocurrencies, all her successors are called <strong>Altcoins</strong> — alternative coins. Except maybe <strong>Ethererum</strong> , another quite old and important cryptocurrency.</p>
<img src="https://nickyreinert.de/en/2022/2022-08-01-web3-withouth-the-hype/image-2.png" alt="Mother Bitcoin"><p>The term currency however is a little misleading for two reasons. First: As everyone could set up a cryptocurrency this kind of contradicts the concept of a classical currency, controlled by a governmental entity. Still it allows interesting new concepts within our economic system: Theoretically everyone is able to create a currency to exchange values.</p>
<p>Second cryptocurrencies implement dozens of different concepts and follow different goals; they are not only for payment: Some of the <strong>Altcoins</strong> are used for payment, some for voting in a network, some of them are just for fun, like the notorious <strong>Dogecoin</strong>.</p>
<p>Right now there exist more than <strong>10.000</strong> so called <strong>cryptocurrencies</strong>; compared to <strong>160 official classical currencies</strong>.</p>
<h3 id="are-cryptocurrencies-anonymous">Are cryptocurrencies anonymous?</h3>
<p>Yes. The underlying concept supports privacy and anonymity — but it’s hard to maintain for several reasons.</p>
<p>How to get your hands on <strong>Bitcoin</strong>? You can either try to participate in the high competitive market of mining or you need something in exchange, like a real currency. There are some <strong>Bitcoin</strong> ATMs where you exchange real money into <strong>Bitcoin</strong>. But this is quite a limited service. You could also purchase vouchers or cash cards, like <strong>PaySafe</strong>, to buy <strong>Bitcoin</strong> online.</p>
<p>That are your options. For the typical user this may be to complicated and he or shee will just create an account with an exchange and buy some <strong>Bitcoin</strong> with his credit card. Anonymity: Gone.</p>
<p>And that’s not even all: Most governments try to regulate and control cryptocurrencies to prevent criminal use or financial “gambling”. Anonymity: Gone, now for sure.</p>
<p>In fact there are <strong>Altcoins</strong> focussed on privacy, the so called <strong>PrivacyCoins</strong>, like <strong>Monero</strong>. Still the problem occurs, that you need to get your hand on those currencies.</p>
<p>You could maintain anonymity but it requires some efforts. Paying your <strong>Pornhub</strong> account to hide it from your wife is maybe not worth the effort. Setting up an anonymous payment channel to support an endangered informant? Totally worth it. To support illegal weapon trades? Morally not acceptable, but probably your best option.</p>
<p>Anonymity is in the DNA of Cryptocurrencies but it’s not a guaranteed feature.</p>
<h3 id="are-cryptocurrencies-decentralized">Are cryptocurrencies decentralized?</h3>
<p>The same answer applies here: It depends! The underlying technology, the blockchain, is per definition based on a decentralized network of nodes. But again the reality kicks in: Imagine you want to buy something online and want to pay using Bitcoin. You need to set up a Wallet or even a Full Node. That’s not “plug-and-play”.</p>
<p>That’s why third party payment provider emerged. They enable the common user to access the cryptocurrency space. No matter if it’s a cryptocurrency exchange or a payment provider: It’s a platform. And platforms contradict the idea of a decentralized payment system. Again.</p>
<p>And that’s not all. The confirmation of new blocks via the so called “<strong>Proof Of Work</strong>” requires a lot of computing power. Currently a few big players in this mining business hold the “





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://finance.yahoo.com/news/does-bitcoin-mining-monopoly-problem-181528719.html?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">computing monopoly</a>


” for this process for the <strong>Bitcoin Blockchain</strong>.</p>
<p>Of course there are alternative methods of block confirmation, like the <strong>Proof of Authority</strong>. Meaning: Only a chosen authority is allowed to confirm — do you see the conflict between “decentralization” and “authority”?</p>
<p>What about “<strong>Proof of Stake</strong>”? This allows a conformation by participating parties with a given amount of staked value. This is probably the most “decentralization” we could get.</p>
<p>Decentralization is in the DNA of Cryptocurrencies but it’s not a guaranteed feature.</p>
<h3 id="are-cryptocurrencies-the-future-of-payment">Are cryptocurrencies the future of payment?</h3>
<p>This is actually hard to imagine.</p>
<p>Let’s start with the speed of processing: Bitcoin relies on the “<strong>Proof of Work</strong>” concept. And this literally dictates a 10 minute time-frame before a transaction will be confirmed. That does not sound like a convenient payment replacement when you’re just about to get a coffee at Starbucks, right?</p>
<p>There are approaches to solve this problem. One promising idea is the “<strong>Lightning network</strong>”: a network that connects two payment participants with a payment channel and allows immediate payments. But you need to consider two things: The payment channel has to be initialized on the Blockchain: Meaning: It’s not something that you set up ad hoc. Also the <strong>Lightning</strong> <strong>network</strong> is disconnected from the Blockchain. You loose all the advantages the Blockchain offers. That does not mean that the Lightning Network is insecure. It has some sophisticated implementations to grant secure transactions. But it’s not the Blockchain. You want to secure financial transactions on the Blockchain, but you need to implement an additional layer as an intermediate ledger? You see the irony.</p>
<p>The next thing is the <strong>volatility</strong> of most cryptocurrencies. In 2010 you could buy 1 Bitcoin for 0,08 USD. Ten years later Bitcoin hit the 50.000 USD mark. If you want to sell goods for Bitcoin it requires a completely different price calculation system for the whole supply chain. Money needs to meet two requirements: It has to work as an <strong>exchange medium</strong> and as a <strong>value storage</strong>. Something that a cryptocurrency hardly can cover, at least the “value-part”.</p>
<p><img src="https://nickyreinert.de/en/2022/2022-08-01-web3-withouth-the-hype/image-3.png" alt="alt text">
Source: 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://www.reddit.com/r/bitcoinmemes/comments/wq6abz/because_bitcoin_leads_to_bosslessness/?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">https://www.reddit.com/r/bitcoinmemes/comments/wq6abz/because_bitcoin_leads_to_bosslessness/</a>


</p>
<h2 id="the-nft">The NFT</h2>
<h3 id="what-is-a-nft">What is a NFT?</h3>
<p>NFT is short for <strong>Non-Fungible Token</strong> and is one famous application based on the blockchain technology. The token is like a contract containing a reference to a (digital) asset. And that asset can be everything from a piece of art to a software. You also may reference to a physical asset. In short: A <strong>NFT</strong> can be understand as an <strong>evidence of ownership.</strong> The token respectively the contract is saved on a blockchain and therefore the contract ought to inherit all the blockchains advantages: It cannot be tampered.</p>
<h3 id="is-a-nft-secure">Is a NFT secure?</h3>
<p>This depends on how you define “secure” and what do you want to achieve.</p>
<p>The <strong>proof of ownership is secure,</strong> as it’s written to a blockchain.</p>
<p>But…</p>
<p><strong>…the resource itself is not secure.</strong> The token is referring to a resource that has to be hosted somewhere. If the resource is lost, the token has no value. Remember 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://opensea.io/assets/matic/0x2953399124f0cbb46d2cbacd8a89cf0599974963/85924082302081312152251891657020775129690493546484874721949341267311034630145/?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">my very arty NFT</a>


 I advertised at the top of this article? Well, you don’t need to buy it. You can download it from here:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">[https://lh3.googleusercontent.com/fcjOKIviiAaHM9Qzjj6QM8YFs_ALfBlan4CdJcI24jPl49VlGdjz-dQwMx5gVbHsFpY9tw3Hih43Ts7vkiCND8zaUG3OQkREbfIM4Ck=s0](https://lh3.googleusercontent.com/fcjOKIviiAaHM9Qzjj6QM8YFs_ALfBlan4CdJcI24jPl49VlGdjz-dQwMx5gVbHsFpY9tw3Hih43Ts7vkiCND8zaUG3OQkREbfIM4Ck=s0)
</span></span></code></pre></div><p>(And that’s why I’m giving it away for free, it’s public, anyway)</p>
<p>As you can see from the host — <strong>googleusercontent</strong> —the NFT exchange OpenSea does not even host the file on it’s own. If google takes the file down, it’s gone.</p>
<p>Of course there exists solutions where the resource is stored inside the blockchain and therefore uses the advantage of decentralization — but it’s not a granted feature. Ask for it.</p>
<p><strong>And finally:</strong> The resource is not secured against unwanted duplication. Everyone could download 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://img.seadn.io/files/abb825f65f440dea6d492d6872c36fcd.png?fit=max&amp;utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">the very precious bored ape</a>


.</p>
<p>Let’s sum it up: <strong>NFT have an</strong> <strong>ideational value</strong>.</p>
<img src="https://nickyreinert.de/en/2022/2022-08-01-web3-withouth-the-hype/image-4.png" alt="Adam Sacks (https://twitter.com/adamsacks/status/1450162259733491715?)"><h3 id="is-nft-a-good-investment-like-rare-art">Is NFT a good investment, like rare art?</h3>
<p>The previous paragraph explained the background, let’s emphasize it: If you want to speculate: Buy NFT. But don’t spend your hard earned money on NFT. Remember this trade from 2021? <strong>Sina Estavi</strong> bought the first Tweet by <strong>Jack Dorsey</strong> for <strong>2.9 Mio. USD</strong>. 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://www.theguardian.com/technology/2022/apr/14/twitter-nft-jack-dorsey-sina-estavi?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">In 2022 he tried to sell it</a>


. The highest bid was around 7,000 USD. This is the Tweet in question, you can read it for free and you can even print it out and put it on your wall:</p>
<blockquote>
<p>just setting up my twttr (





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://twitter.com/jack/status/20?lang=de&amp;utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">Jack Dorsay, 2006 on Twitter</a>


)</p>
</blockquote>
<p>





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://cointelegraph.com/news/how-do-you-assess-the-value-of-an-nft?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">Cointelegraph said that there are nine factors determining the value of a NFT</a>


: Rarity, Utility, Tangibility, Interoperability, Social proof, Ownership history, Liquidity premium, Speculation, Continual change in the NFT ecosystem.</p>
<h2 id="conclusion">Conclusion</h2>
<p>





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://www.youtube.com/watch?v=g_KeXPTJeCk&amp;utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">Don’t let the hype delude you</a>


. The <strong>Blockchain</strong> is an interesting technical concept, but it’s not the solution to everything. It most likely solves a problem. A problem that you most likely do not have.</p>
<p><strong>NFT</strong> and <strong>cryptocurrencies</strong> are interesting concepts based on the <strong>blockchains</strong>. Cryptocurrencies undoubtedly will have a future. At least a billion dollar industry emerged around those products, but there’s also a pretty good portion of scam.</p>
<p>And the “Metaverse”? Did you see the screenshot from inside it? Does this look like the future to you?</p>
<img src="https://nickyreinert.de/en/2022/2022-08-01-web3-withouth-the-hype/image-5.png" alt="Mark Zuckerberg inside the Metaverse, colorized, around 1895"><p>The whole story could use some buzz-diet. When the <strong>World Wide Web</strong> entered the stage it was not driven by profit. Look who’s behind the <strong>Metaverse</strong>. Meta’s main source if income is <strong>digital advertising</strong>.</p>
<p>As a good read I suggest you this collection of dozens of critical articles about all those new technologies 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://github.com/life-itself/web3?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">https://github.com/life-itself/web3</a>


.</p>
<p><strong>Stay sharp - but don’t be to pessimistic.</strong> You could miss the next big thing.</p>
]]></content:encoded><atom:updated>2026-04-19T20:27:26+02:00</atom:updated><category>blog</category><dc:subject>Lesezeit: 20 Minuten</dc:subject><dc:type>article</dc:type></item><item><title>How to sell your NFT — step by step, pros and cons</title><link>https://nickyreinert.de/en/2022/2022-02-19-nft-selling/</link><pubDate>Sat, 19 Feb 2022 12:34:56 +0100</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/en/2022/2022-02-19-nft-selling/</guid><description>What is NFT? NFT is short for Non-Fungible Token. In fact it’s a reference to a (digital) asset. And that asset can be everything from a poem to a software. You also may reference to a physical asset. In short: A NFT can be understand as an evidence of ownership.
The token aka the reference is saved …</description><content:encoded><![CDATA[<h2 id="what-is-nft">What is NFT?</h2>
<p>NFT is short for <strong>Non-Fungible Token</strong>. In fact it’s a reference to a (digital) asset. And that asset can be everything from a poem to a software. You also may reference to a physical asset. In short: A NFT can be understand as an <strong>evidence of ownership.</strong></p>
<p>The token aka the reference is saved on a blockchain. Not every blockchain supports non-fungible tokens. One of the most popular blockchains is Ethereum. You can trade this token using a NFT exchange and the trade will also be saved to the blockchain.</p>
<h3 id="advantages">Advantages</h3>
<p>The blockchain is a pretty safe place. Usually there’s no way to manipulate the token or the trade.</p>
<p>Another essential aspect of blockchains is the anonymity. You may just create an account at an exchange, mint your asset (I’ll come to that later) and sell it to some Jane Doe.</p>
<h3 id="disadvantages">Disadvantages</h3>
<p>The token is only a reference to a good, e.g. a file that has to be hosted somewhere. If the server is down, the token is kind of worthless.</p>
<p>Depending on the blockchain’s verification mechanism, any operations such as minting or selling requires computing power and therefore energy. The exchange could charge you with a transaction or minting fee. Though you have to mention, that any conventional market place has operational costs or wants to create revenue.</p>
<p>Also the ownership of the token does not necessarily mean that the asset can be sold again. If it’s an digital image, and if not forbidden in the terms of sales, the seller of the NFT can sell as many copies as he want.</p>
<h3 id="how-to-start-with-nft">How to start with NFT</h3>
<p>First you have to choose an exchange. The most popular one is <strong>OpenSea</strong>. When selecting your exchange, you should consider the transaction fee as well as the cost of <strong>minting.</strong> That’s the initial process of putting the NFT to the blockchain. OpenSea allows you to <strong>mint free of charge.</strong></p>
<p>Second you need to select a wallet to manage your financials. Depending on the exchange you are using, you can connect different wallets. OpenSea for example currently supports the following wallet providers:</p>
<img src="https://nickyreinert.de/en/2022/2022-02-19-nft-selling/image.png" alt="Cryptocurrency wallet providers supported by OpenSea"><p>As stated in the screenshot, 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://metamask.io/?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer"><strong>MetaMask</strong></a>


 is one of the most popular wallets. MetaMask comes as a plugin for the <strong>Firefox</strong> browser as well as an app for <strong>iOS</strong> and <strong>Android</strong>. I will not dive into the important security topic here. But IMHO a browser is not the safest place to manage your wallet. For the sake of convenience we will stick to this simple solution.</p>
<h3 id="install-the-metamask-wallet-as-a-firefox-plugin-and-add-a-polygon-wallet">Install the MetaMask wallet as a Firefox plugin and add a Polygon wallet</h3>
<p>After you downloaded the MetaMask plugin and added it to Firefox the plugin will guide you through the initial setup where you create a password protected wallet. Do not forget to write down the password recovery phrase, a combination of several English words.</p>
<p>The initial setup creates a wallet for the well known Ethereum network. OpenSea supports the Ethereum and the Polygon network. Polygon is also using the Ethereum blockchain, but has no gas fee, the according currency is called <strong>MATIC</strong>. To also add a Polygon wallet to MetaMask just click on “Ethereum Mainnet” in the upper right and select “Add network” to open the “Add network” form.</p>
<img src="https://nickyreinert.de/en/2022/2022-02-19-nft-selling/image-1.png" alt="Click the “Ethereum Mainnet” element and select"><p>Now you need to provide some technical details that are also listed [in the official documentation]((make sure you do not enter credentials from some fishy website, like medium.com):</p>
<p>Networkname: <code>Polygon Mainnet</code></p>
<p>RPC URL: <code>[https://polygon-rpc.com/](https://polygon-rpc.com/)</code></p>
<p>Chain ID: <code>137</code></p>
<p>Currency Symbol: <code>MATIC</code></p>
<p>Block Explorer URL: <code>https://polygonscan.com/</code></p>
<p>Hit the save button and you are done!</p>
<h3 id="create-an-opensea-account">Create an OpenSea account</h3>
<p>Now change over to 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://opensea.io/login?referrer=%2Faccount&amp;utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">OpenSea</a>


 to connect your Polgyon wallet. Before you click on the MetaMask button, make sure that MetaMask is currently running in the Polygon network by just clicking the icon in the upper right — this is how it should look like:</p>
<img src="https://nickyreinert.de/en/2022/2022-02-19-nft-selling/image-2.png" alt="alt text"><p>Now head back to OpenSea and click the MetaMask button. A popup should open showing your Polygon account with a <strong>balance of 0 Matic</strong>. Continue and confirm the connection request. When you’re done you should see the account overview like that:</p>
<img src="https://nickyreinert.de/en/2022/2022-02-19-nft-selling/image-3.png" alt="alt text"><h3 id="optional-add-a-balance-to-your-wallet">Optional: Add a balance to your wallet</h3>
<p>Creating a NFT means putting a piece of information to the blockchain which requires some <strong>computing efforts</strong>, which leads to <strong>energy consumption</strong> thus <strong>costs money</strong>. OpenSea currently does not charge you for the minting process. Just for the case let me quickly go through the top up-process to get some MATIC into your wallet.</p>
<p>There are several ways to top up your Polygon wallet with MATIC but unfortunately there’s no simple “checkout with PayPal” button. I’m using 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://www.binance.com?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">Binance</a>


, one of the better known exchanges, that also supports MATIC. I’m not leading you through the Binance account creation process.</p>
<p>You can buy MATIC via the “Buy Crypto” menu on the top and your favorite payment method — just <strong>make sure you select MATIC</strong> as the desired currency.</p>
<img src="https://nickyreinert.de/en/2022/2022-02-19-nft-selling/image-4.png" alt="alt text"><p>Binance has a bottom limit of <strong>15 EUR</strong> for a trade. This is by fare enough, as you will see later. After you bought some MATIC, the transaction should not take more than a couple of seconds, you need to send the MATIC to your MetaMask wallet. Click on the MetaMask icon in your browser and then on account name — that copies your <strong>wallet address</strong> to the clipboard. <em>Always make sure you selected the Polygon network!</em> It should look like that:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">0x1db3fgEc712347C312B28ADa94bC1afb7Dd170A01
</span></span></code></pre></div><p>In case you are wondering where Binance hides the transfer function: Clicking “Wallet” in the upper menu and then selecting “Fiat and Spot” leads you to [your asset overview](Now look for the MATIC row and click “Withdraw”. The following form wants your <strong>wallet address</strong>, the required <strong>amount</strong> and of course the <strong>currency</strong> (MATIC) and the <strong>network</strong> (Polygon). As you can see you will be charged with a network fee.</p>
<img src="https://nickyreinert.de/en/2022/2022-02-19-nft-selling/image-5.png" alt="alt text"><p><strong>Always double check the receiving address</strong>! There is no such thing as undo and as a wallet owner is usually anonymous, you will not be able to get in touch with the beneficiary in case of a wrong transaction.</p>
<p>The transaction itself should not take more then a couple of seconds. Get back to your MetaMask plugin and celebrate your first successful crypto transaction as it now should show a balance for your Polygon wallet!</p>
<h3 id="mint-your-asset-and-create-a-token">Mint your asset and create a token</h3>
<p>From your account overview click <strong>Create.</strong> The first time you do this, it will open a MetaMask popup asking for your confirmation. This step will not trigger any costs. Done that you should see a form where you can upload your asset and provide some meta information that are self explaining. You just need the asset, a name and I suggest a description. Also make sure that you change the <strong>Polygon network</strong> at the bottom of the form. That’s all, you may leave the other settings unchanged.</p>
<p>I uploaded this image I recently drew:</p>
<img src="https://nickyreinert.de/en/2022/2022-02-19-nft-selling/image-6.png" alt="The thing"><p>After clicking “Create” OpenSea should confirm the process with a little popup.</p>
<h3 id="sell-the-nft">Sell the NFT</h3>
<p>This step is quite simple: Click the sell button and set a price and how long you want to offer your piece of art. The confirmation requires you to sign it again with MetaMask.</p>
<p>And then you’re done. Your NFT is online and up for sale. Copy the sharing link, write something to gain attention and hope that beyond millions of other items yours will draw a little more attention and eventually a buyer.</p>
<p>Good luck!</p>
<p>And there’s the <strong>final</strong> product :)</p>
<p>





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://opensea.io/assets/matic/0x2953399124f0cbb46d2cbacd8a89cf0599974963/85924082302081312152251891657020775129690493546484874721949341267311034630145/?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">https://opensea.io/assets/matic/0x2953399124f0cbb46d2cbacd8a89cf0599974963/85924082302081312152251891657020775129690493546484874721949341267311034630145/</a>


</p>
]]></content:encoded><atom:updated>2026-06-16T20:56:44+02:00</atom:updated><category>blog</category><dc:subject>Lesezeit: 15 Minuten</dc:subject><dc:type>tutorial</dc:type></item><item><title>How to use the new LAMBDA-feature in Excel (e.g. to identify outliers)</title><link>https://nickyreinert.de/en/2021/2021-05-08-excel-lambda/</link><pubDate>Sat, 08 May 2021 12:34:56 +0100</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/en/2021/2021-05-08-excel-lambda/</guid><description>Whait what, Excel? You call yourself a data engineer and you work with Excel? Sure. Excel is still the best tool for quick’n’dirty solutions, don’t let Tableau, Python or Alteryx tell you something different… ;)
What’s LAMBDA? One sentence: LAMBDA allows you to combine common Excel functions into …</description><content:encoded><![CDATA[<p>Whait what, Excel? You call yourself a data engineer and you work with Excel? Sure. Excel is still the best tool for quick’n’dirty solutions, don’t let Tableau, Python or Alteryx tell you something different… ;)</p>
<h2 id="whats-lambda">What’s LAMBDA?</h2>
<p>One sentence: <strong>LAMBDA</strong> allows you to combine common Excel functions into one new custom function (respectively formula) that is available all over your workbook. Back in the days you had to cope with VBA or multiple columns to implement special calculations. LAMBDA still does not cover one disadvantage: <strong>Building custom functions remains a mess</strong> and ends up with long, encapsulated formulas.</p>
<p>Creating a LAMBDA function requires <strong>two steps</strong>. Let’s take an example challenge to demonstrate how that works: Find outliers by calculation <strong>quartiles</strong> and the upper and lower fence.</p>
<img src="https://nickyreinert.de/en/2021/2021-05-08-excel-lambda/image.png" alt="being an outlier"><h2 id="what-are-fences">What are fences?</h2>
<p>Upper and lower fences are borders, defined by 1,5 times the so called inter quartile range, which is just the area between two quartiles: the first and the third. The following formulas are used to find those borders for a given set of values.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">Lower Fence = Q1 - (1,5 * IQR)
</span></span><span class="line"><span class="ln">2</span><span class="cl">Upper Fence = Q3 + (1,5 * IQR)
</span></span></code></pre></div><p>Every value that’s outside this range can be considered as an outlier. That’s the short unprofessional explanation. Imagine you have a set of very important values:</p>
<img src="https://nickyreinert.de/en/2021/2021-05-08-excel-lambda/image-1.png" alt="example set of very important numbers — where are the outliers"><p>A couple of steps are required to find the outliers with Excel. Every step requires one column (<em>the bold parts are the actual Excel formulas, you don’t need to fill your sheet, that’s just for showing the process).</em></p>
<p>First you need to calculate the quartiles (I’m using the including quartile function, on the difference between including and excluding quartile 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://corporatefinanceinstitute.com/resources/excel/functions/quartile-inc-function/?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">check this text out)</a>


.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">1st Quartile = Q1 =&gt; **Column B: =QUARTILE.INKL(A:A;1)**
</span></span><span class="line"><span class="ln">2</span><span class="cl">3rd Quartile = Q3 =&gt; **Column C: =QUARTILE.INKL(A:A;3)**
</span></span></code></pre></div><p>The next column calculates the interquartile range — IQR:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">IQR = Q3 - Q1 =&gt; **Column D: =C2-B2**
</span></span></code></pre></div><p>Another two columns are required to calculate the lower and upper fence:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">Lower Fence = LF =&gt; **Column E: =B2-(1,5*D2)**
</span></span><span class="line"><span class="ln">2</span><span class="cl">Upper Fence = UP =&gt; **Column F: =C2+(1,5*D2)**
</span></span></code></pre></div><p>Finally we need to check the actual value: Is it lower than the lower fence or greater than the upper fence? Is it inside our range?</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">Is outlier? =&gt; **Column G: =IF(OR(A2&lt;E2;A2&gt;F2);&#34;x&#34;;&#34;&#34;)**
</span></span></code></pre></div><p>That’s how it would look like in Excel:</p>
<img src="https://nickyreinert.de/en/2021/2021-05-08-excel-lambda/image-2.png" alt="Outlier identification (in case you’re wondering: There are no outliers below the lower fence"><p>Now you would tend to compress the six additional columns into one, like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">=IF(
</span></span><span class="line"><span class="ln">2</span><span class="cl">    OR(
</span></span><span class="line"><span class="ln">3</span><span class="cl">        A2&lt;QUARTILE.INKL($A$2:$A$2670;1) - (1,5*QUARTILE.INKL($A$2:$A$2670;3) - QUARTILE.INKL($A$2:$A$2670;1));
</span></span><span class="line"><span class="ln">4</span><span class="cl">        A2&gt;QUARTILE.INKL($A$2:$A$2670;3) + (1,5*QUARTILE.INKL($A$2:$A$2670;3) - QUARTILE.INKL($A$2:$A$2670;1))
</span></span><span class="line"><span class="ln">5</span><span class="cl">    );
</span></span><span class="line"><span class="ln">6</span><span class="cl">    &#34;x&#34;;
</span></span><span class="line"><span class="ln">7</span><span class="cl">    &#34;&#34;)
</span></span></code></pre></div><img src="https://nickyreinert.de/en/2021/2021-05-08-excel-lambda/image-3.png" alt="alt text"><p>Isn’t that beautiful? <strong>Welcome to Excel</strong>! Good news is: <strong>LAMBDA</strong> helps you to make this a little <strong>more readable</strong> and also <strong>portable</strong>. The bad news: It still looks a little chaotic.</p>
<h2 id="step-1-build-the-custom-function-inside-a-cell">Step 1: Build the custom function inside a cell</h2>
<p>The <strong>LAMBDA</strong> formula requires <strong>n parameters</strong>. You can add (almost) as many parameters as you want, the <strong>last parameter</strong> always contains the <strong>calculation</strong>, the <strong>ones before</strong> are <strong>parameters</strong> that can be used within the calculation. Example:</p>
<p>This one expects one parameter and would increment it by 1:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">=LAMBDA(x, x + 1)
</span></span></code></pre></div><p>And this <strong>LAMBDA</strong> calculation just returns a 5 — it does not expect any parameters:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">=LAMBDA(5)
</span></span></code></pre></div><p>And another one that adds up to values:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">=LAMBDA(x;y;x+y)
</span></span></code></pre></div><p>If you just put those lines into a cell, Excel returns a <strong>#CALC-error</strong> because you’re not providing any references. The worksheet is only a place to <strong>create and test</strong> your LAMBDA function. You also need to add example values, according to the parameters your LAMBDA function, to make it work within your worksheet:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">=LAMBDA(x;y;x+y)(5;9)
</span></span></code></pre></div><p>This will actualy return a 14. Now let’s put our <strong>upper-lower-fence-drama</strong> into a LAMBDA-function. First we replace every reference with a distinct variable, I chose <strong>value</strong> and <strong>range:</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">=IF(
</span></span><span class="line"><span class="ln">2</span><span class="cl">    OR(
</span></span><span class="line"><span class="ln">3</span><span class="cl">        value &lt; QUARTILE.INKL(range;1) - (1,5 * QUARTILE.INKL(range;3) - QUARTILE.INKL(range;1));
</span></span><span class="line"><span class="ln">4</span><span class="cl">        value &gt; QUARTILE.INKL(range;3) + (1,5 * QUARTILE.INKL(range;3) - QUARTILE.INKL(range;1))
</span></span><span class="line"><span class="ln">5</span><span class="cl">    );
</span></span><span class="line"><span class="ln">6</span><span class="cl">    &#34;x&#34;;
</span></span><span class="line"><span class="ln">7</span><span class="cl">    &#34;&#34;)
</span></span></code></pre></div><p>Now we wrap this up with LAMBDA and some brackets and register our two variables:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">=LAMBDA(value;range;IF(
</span></span><span class="line"><span class="ln">2</span><span class="cl">    OR(
</span></span><span class="line"><span class="ln">3</span><span class="cl">        value &lt; QUARTILE.INKL(range;1) - (1,5 * QUARTILE.INKL(range;3) - QUARTILE.INKL(range;1));
</span></span><span class="line"><span class="ln">4</span><span class="cl">        value &gt; QUARTILE.INKL(range;3) + (1,5 * QUARTILE.INKL(range;3) - QUARTILE.INKL(range;1))
</span></span><span class="line"><span class="ln">5</span><span class="cl">    );
</span></span><span class="line"><span class="ln">6</span><span class="cl">    &#34;x&#34;;
</span></span><span class="line"><span class="ln">7</span><span class="cl">    &#34;&#34;)
</span></span><span class="line"><span class="ln">8</span><span class="cl">)
</span></span></code></pre></div><p>And last but not least, we need to add some testing values — we just referencing the cells, as we did before:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">=LAMBDA(value;range;IF(
</span></span><span class="line"><span class="ln">2</span><span class="cl">    OR(
</span></span><span class="line"><span class="ln">3</span><span class="cl">        value &lt; QUARTILE.INKL(range;1) - (1,5 * QUARTILE.INKL(range;3) - QUARTILE.INKL(range;1));
</span></span><span class="line"><span class="ln">4</span><span class="cl">        value &gt; QUARTILE.INKL(range;3) + (1,5 * QUARTILE.INKL(range;3) - QUARTILE.INKL(range;1))
</span></span><span class="line"><span class="ln">5</span><span class="cl">    );
</span></span><span class="line"><span class="ln">6</span><span class="cl">    &#34;x&#34;;
</span></span><span class="line"><span class="ln">7</span><span class="cl">    &#34;&#34;)
</span></span><span class="line"><span class="ln">8</span><span class="cl">)(A1;A$2:A$2670)
</span></span></code></pre></div><p>Now let’s fill the column with this formula:</p>
<img src="https://nickyreinert.de/en/2021/2021-05-08-excel-lambda/image-4.png" alt="alt text"><p>Unsurprisingly the result is the same.</p>
<h2 id="step-2--actually-create-a-new-function--formula">Step 2 — Actually create a new function / formula</h2>
<p>Of course we don’t want to copy the whole stuff every time we need to identify outliers. So let’s <strong>register this calculation</strong> as a new formula. Press either <strong>CMD+F3</strong> (CTRL+F3 on Windows) or select “<strong>define names</strong>” in the Formula-Area:</p>
<img src="https://nickyreinert.de/en/2021/2021-05-08-excel-lambda/image-5.png" alt="alt text"><p>Add a name for your new function — lets call it <strong>IS_OUTLIER</strong> — and paste your LAMBDA-formular into the second text field — <strong>without the second pair</strong> of brackets containing your test values:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">=LAMBDA(value;range;IF(
</span></span><span class="line"><span class="ln">2</span><span class="cl">    OR(
</span></span><span class="line"><span class="ln">3</span><span class="cl">        value &lt; QUARTILE.INKL(range;1) - (1,5 * QUARTILE.INKL(range;3) - QUARTILE.INKL(range;1));
</span></span><span class="line"><span class="ln">4</span><span class="cl">        value &gt; QUARTILE.INKL(range;3) + (1,5 * QUARTILE.INKL(range;3) - QUARTILE.INKL(range;1))
</span></span><span class="line"><span class="ln">5</span><span class="cl">    );
</span></span><span class="line"><span class="ln">6</span><span class="cl">    &#34;x&#34;;
</span></span><span class="line"><span class="ln">7</span><span class="cl">    &#34;&#34;))
</span></span></code></pre></div><p><strong>Do not forget to press the plus-button</strong> to actually create the new formula. Confirm everything with OK and <strong>get ready to rumble</strong>. This is your new and simple outlier detection formula:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">=IS_OUTLIER(value;range)
</span></span></code></pre></div><img src="https://nickyreinert.de/en/2021/2021-05-08-excel-lambda/image-6.png" alt="alt text"><p>And it works, apparently, like a charm. If you need this feature in another worksheet, just copy the name definition.</p>
<p>That’s all. No epilog, but one word of advice: You have to subscribe to the <strong>Beta-channel</strong> (at least on MacOS), as the LAMBDA function is not yet provided in the official release candiate. Have fun! :)</p>
]]></content:encoded><atom:updated>2026-04-19T20:27:26+02:00</atom:updated><category>blog</category><dc:subject>Lesezeit: 8 Minuten</dc:subject><dc:type>tutorial</dc:type></item><item><title>My Python boilerplate and a little Python-Fu</title><link>https://nickyreinert.de/en/2021/2021-02-24-python-foo/</link><pubDate>Wed, 24 Feb 2021 12:34:56 +0100</pubDate><author>Nicky Reinert</author><dc:creator>Nicky Reinert</dc:creator><guid isPermaLink="true">https://nickyreinert.de/en/2021/2021-02-24-python-foo/</guid><description>Let me show you my Python boilerplate and some little nifty standard features I’m working with. You can download the full template from github . This post will probably be extended in the future.
Summary The boilerplate will help you with the following features:
Use virtual environments manage …</description><content:encoded><![CDATA[<p>Let me show you my Python boilerplate and some little nifty standard features I’m working with. You can download the 





  
  

  
  

  
  
  

  
    
      
    
  

  
  <a href="https://gist.github.com/nickyreinert/45c019f318fe7a10232327cdf8b119f4?utm_source=institut-fuer-digitale-herausforderungen&amp;utm_medium=allyourbasearebelongtous" target="_blank" rel="noopener noreferrer">full template from github</a>


. This post will probably be extended in the future.</p>
<h2 id="summary">Summary</h2>
<p>The boilerplate will help you with the following features:</p>
<ul>
<li>Use virtual environments</li>
<li>manage requirements</li>
<li>notifications via Telegram</li>
<li>manage command line arguments</li>
<li>list comprehension</li>
<li>print progress in loops</li>
<li>measure time</li>
<li>play a sound after a loop is finished</li>
</ul>
<h2 id="virtual-environment">Virtual environment</h2>
<p>I like working with virtual environments. It helps me to work with individual clean development environments. How do you do that? Switch to your main development folder, create a subfolder for your new project and initiate it:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">cd /development/
</span></span><span class="line"><span class="ln">2</span><span class="cl">mkdir project1
</span></span><span class="line"><span class="ln">3</span><span class="cl">python3 -m venv project1
</span></span><span class="line"><span class="ln">4</span><span class="cl">cd project1
</span></span><span class="line"><span class="ln">5</span><span class="cl">source bin/activate
</span></span></code></pre></div><p>There you go. If you install packages, they are located in this folder only. Wait, packages? Right, let’s get to the next step:</p>
<img src="https://nickyreinert.de/en/2021/2021-02-24-python-foo/image.png" alt="This post comes without any images, that’s why I’m showing you this beautiful image that I took in Athens, a couple of years ago — a wonderful city!"><h2 id="manage-your-libraries">Manage your libraries</h2>
<p>As your Python scripts grows, by the amount of lines but also by the amount of features, the amount of used libraries will grow, too. But how to run the script on a different machine, in a differente environment? How to easily share you script? The answer is pipreqs:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">pip install pipreqs
</span></span></code></pre></div><p>After you installed this little tool, run it against your script (or a folder of scripts):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">pipreqs yourscript.py # one script only
</span></span><span class="line"><span class="ln">2</span><span class="cl">pipreqs . # all scripts in the folder
</span></span></code></pre></div><p>Pipreqs will scan your script for required libraries and list them in a file called requirements.txt (suprise!). You need to share this text file with your Python script. Everyone who wants to run the script needs to prepare the environment using this line of code:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">pip install -r requirements.txt
</span></span></code></pre></div><p>Et voilà. All requirements met.</p>
<h2 id="telegram-logging">Telegram logging</h2>
<blockquote>
<p>Please be adviced: Using a centralized messaging service like telegram, where your content is hosted on foreing computers, may be a risky plan. I’m fully aware of that, you should be too. That’s why I’m only sharing non-senstive information with telegram. It’s just my perfect notification service.</p>
</blockquote>
<p><em>How does telegram help?</em> You may ask. Well, imagine you have a script running hours or even days! Actually, you don’t really know. Wouldn’t it be nice to get a message, when the script is done, or about it’s progress.</p>
<p>First let’s create your own bot for Telegram. Open the app and look for “BotFather”. He will allow you to create a new bot. Name doesn’t matter, what does matter is the secret(!) token. Take it and start a chat with your bot. Then browse to this url (replace the token part with your secret(!) token):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">https://api.telegram.org/bot&lt;bot token&gt;/getUpdates
</span></span></code></pre></div><p>The page will return a JSON object with a couple of information, amongst it is your chat-id (it’s secret, too, everything is secret!). Grab it and put it with your secret (!!!) token into <strong>your telegram_notifier.py:</strong></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln"> 1</span><span class="cl">import requestsbot_token = &#39;secret token&#39;
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">bot_chat_id = &#39;secret chat id&#39;def send_text_to_telegram(bot_message):
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">   bot_token = bot_token
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">   
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">   send_text = &#39;[https://api.telegram.org/bot&#39;](https://api.telegram.org/bot&#39;) + bot_token + &#39;/sendMessage?chat_id=&#39; + bot_chat_id + &#39;&amp;parse_mode=Markdown&amp;text=&#39; + bot_message   response = requests.get(send_text)
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"> 
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">   return response.json()def send_image_to_telegram(image):   bot_token = bot_token
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"> 
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">   data = {&#39;chat_id&#39;: bot_chat_id}
</span></span><span class="line"><span class="ln">10</span><span class="cl">   
</span></span><span class="line"><span class="ln">11</span><span class="cl">   url = &#39;[https://api.telegram.org/bot&#39;](https://api.telegram.org/bot&#39;) + bot_token + &#39;/sendPhoto?chat_id=&#39; + bot_chat_id + &#39;&amp;parse_mode=Markdown&#39;
</span></span><span class="line"><span class="ln">12</span><span class="cl"> 
</span></span><span class="line"><span class="ln">13</span><span class="cl">   with open(image, &#34;rb&#34;) as image_file:
</span></span><span class="line"><span class="ln">14</span><span class="cl"> 
</span></span><span class="line"><span class="ln">15</span><span class="cl">      response = requests.post(url, data=data, files={&#34;photo&#34;: image_file})
</span></span><span class="line"><span class="ln">16</span><span class="cl">  
</span></span><span class="line"><span class="ln">17</span><span class="cl">   return response.json()
</span></span></code></pre></div><p><em>Please note, that there’s no error handling with this script.</em></p>
<p>Congratulations, you now have two functions to send text and images to your telegram bot. This will help you to get a notification from your Python scripts. Import the two functions with this line in every of your actual scripts:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">from telegram_notifier import *
</span></span></code></pre></div><h2 id="in-media-res">In media res</h2>
<h3 id="command-line-parameters">Command line parameters</h3>
<p>Enough prepping, let’s talk about the script itself. The first lines look like that:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">import time # for logging purposes
</span></span><span class="line"><span class="ln">2</span><span class="cl">import argparse # for command line argumentsparser=argparse.ArgumentParser()parser.add_argument(&#39;--limit&#39;, help=&#39;Source files in CSV format&#39;, required=True, type=int)
</span></span><span class="line"><span class="ln">3</span><span class="cl">parser.add_argument(&#39;--loop_length&#39;, help=&#39;Source files in CSV format&#39;, required=False, type=int)
</span></span><span class="line"><span class="ln">4</span><span class="cl">parser.add_argument(&#39;--message&#39;, help=&#39;Give me a message&#39;, required=False, default=&#34;Hello World&#34;, type=str)args = parser.parse_args()
</span></span></code></pre></div><p>You’ll find it helpful to pass arguments to your script from the command line. That’s what the <strong>argparse</strong> library is for. This library supports loads of other parameters and features, I’m just showing you some basic ones to read two parameters <strong>limit</strong> and <strong>loop_length</strong>. You can read those parameters using <strong>args.limit</strong> or <strong>args.loop_length.</strong> So easy. Example?</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">loop_length = 1000000 if args.loop_length == None else args.loop_length
</span></span><span class="line"><span class="ln">2</span><span class="cl">limit = args.limit
</span></span></code></pre></div><p>That’s the shortend version of a if-condition. If the loop-length is set, take it. Otherwise use the default value of 1.000.000. The limit is always set to the one passed by the user — because it’s a mandatory parameter and therefore always present. Next, please.</p>
<h3 id="logging">Logging</h3>
<p>Why is logging so important to me? Why sould it be important to you? You will get to that point, <strong>when a customer asks you</strong>:</p>
<blockquote>
<p><em>How long will it take to process this dataset with 2 Mio. rows? How much will it costs?</em></p>
<p><em>~a customer, somewhen</em></p>
</blockquote>
<p>Or if you want to compare two different methods. And so on. I’m a huge fan of (extensive) logging. It’s very helpful. Trust me. So, how do we start?</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">feedback_frequency = 10                                               count = 1  
</span></span><span class="line"><span class="ln">2</span><span class="cl">start = time.time()
</span></span></code></pre></div><p>Frequency? Why? Imagine you have a script that crunches 1 Mio. rows. And depending on the surrounding parameters, it’s either pretty fast or pretty slow. When do you create a log message? With the feedback_frequency you define, how many times you want to see a log message. Set it to 10 to get a feedback every 1 Mio. / 10 rows. Increase it to get even more feedback.</p>
<h3 id="colors">Colors</h3>
<p>If you are sending a lot of information to the console, you may want to use colored output. First let’s define a custom class for that. As you can see, this also allows you to set the font style to bold or underline it.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln"> 1</span><span class="cl">class bcolors:
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">    HEADER = &#39;\033[95m&#39;
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">    OKBLUE = &#39;\033[94m&#39;
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    OKCYAN = &#39;\033[96m&#39;
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">    OKGREEN = &#39;\033[92m&#39;
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">    WARNING = &#39;\033[93m&#39;
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">    FAIL = &#39;\033[91m&#39;
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    ENDC = &#39;\033[0m&#39;
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    BOLD = &#39;\033[1m&#39;
</span></span><span class="line"><span class="ln">10</span><span class="cl">    UNDERLINE = &#39;\033[4m&#39;
</span></span></code></pre></div><p>Now you implement the class into your output like that:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">print(f&#39;{bcolors.FAIL}{bcolors.BOLD}Bold red text{bcolors.ENDC}&#39;)
</span></span></code></pre></div><h3 id="a-simple-loop--comprehension">A simple loop — comprehension</h3>
<p>The list or dict comprehension feature is a feature you either love or don’t understand. I’m not even sure if I really understand the feature. Nevertheless, here’s an example that hopefully explains it.</p>
<p>List comprehension works for dictonaries, too. It allows you to loop through data without writing a fully fledged loop. And its supposed to be faster than a common loop.</p>
<p>The following example goes through a range of numbers from 1 to loop_length=1.000.000 (<em>second last line</em>).</p>
<p><em>The last line</em> in this comprehension says: Only process the current row, if index is greater than 10.</p>
<p><em>The first line</em> just takes the index value. Work with it. Add it, round it, calculate it. That’s the value, that goes to our list “<strong>a_list</strong>”.</p>
<p><em>The second line</em> is the second way to work with conditions: Depending on the acutal value of index, it defines an outcome. If index is less than 50, pass it on. Else pass on the value defined in <strong>default_value</strong>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">default_value = &#39;x&#39;
</span></span><span class="line"><span class="ln">2</span><span class="cl">a_list = [    index
</span></span><span class="line"><span class="ln">3</span><span class="cl">    if index &lt; 50 else default_value
</span></span><span class="line"><span class="ln">4</span><span class="cl">    for index in range(1, loop_length)
</span></span><span class="line"><span class="ln">5</span><span class="cl">    if index &gt; 10
</span></span><span class="line"><span class="ln">6</span><span class="cl">    ]
</span></span></code></pre></div><p>I know it’s tricky. You will get to it, as soon as you need it. ;P</p>
<h3 id="logging-actually">Logging, actually</h3>
<p>The next lines will send one message to telegram and to the standard output.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">duration = time.time() - start            message = &#39;\r\nDone with list comprehension after %s seconds &#39; % (      &#39;{:.2f}&#39;.format(duration) # two decimal places
</span></span><span class="line"><span class="ln">2</span><span class="cl">)
</span></span><span class="line"><span class="ln">3</span><span class="cl">                                               send_text_to_telegram(message)                                               print(message, flush=True)
</span></span></code></pre></div><p>You should understand this one from the code, except the flush-Parameter? This one forces the Python-script to send the message to the output, without waiting for the script to end. That’s your real-time-update-guarantee!</p>
<h3 id="the-fully-fledged-loop">The fully-fledged Loop</h3>
<p>This is a full loop, that allows you to add as many conditions and calculations as you want.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln"> 1</span><span class="cl">for i in range(1, loop_length):   if i &gt; limit - 1 and limit &gt; 0: break
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">   
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">   count += 1   if i % (limit / feedback_frequency) == 0 or i % (round(loop_length / feedback_frequency, 0)) == 0:
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">        
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">      progress = i / limit if limit &gt; 0 else i / loop_length      message = (
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">            &#39;%s%%...&#39; % 
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">            (&#39;{:.1f}&#39;.format(100 * progress))
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">        )      print(
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">            message,            
</span></span><span class="line"><span class="ln">10</span><span class="cl">            end = &#39;&#39;, # no line break
</span></span><span class="line"><span class="ln">11</span><span class="cl">            flush=True # don&#39;t buffer output
</span></span><span class="line"><span class="ln">12</span><span class="cl">      )
</span></span></code></pre></div><p>The first lines are easy. Break the loop, if the limit is hit. The most exciting thing is the logging-feature. As mentioned above, it will only logg a message if it meets the frequency requirements. Also check out the <strong>end-Parameter</strong> of print. It removes the line break from the print-command.</p>
<h3 id="final-message">Final message</h3>
<p>Finally, after our script logic is finished, we send a message. This time to telegram, too.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="ln">1</span><span class="cl">duration = time.time() - startmessage = &#39;\r\nDone with processing %s iterations after %s seconds &#39; % (
</span></span><span class="line"><span class="ln">2</span><span class="cl">    &#39;{:0,.0f}&#39;.format(count), # formatting integers with thousand separator
</span></span><span class="line"><span class="ln">3</span><span class="cl">    &#39;{:.2f}&#39;.format(duration) # formatting decimals with two decimals
</span></span><span class="line"><span class="ln">4</span><span class="cl">    )send_text_to_telegram(message)print(message)
</span></span></code></pre></div><p>There’s nothing special happening here. The only interesting thing is, that Telegram also accepts images. This is good if you’re creating some sophisticated statistcial analysis and want to see the output. Again, a word of advise: Remember that Telegram is storing your messages on foreign servers. Keep that in mind.</p>
<h2 id="last-words">Last words</h2>
<p>I’m always learing, and I hope your learned something, too. I’m going to extend this post in the future. If you have further tricks for me, drop me a dove.</p>
]]></content:encoded><atom:updated>2026-04-19T20:27:26+02:00</atom:updated><category>blog</category><dc:subject>Lesezeit: 5 Minuten</dc:subject><dc:type>tutorial</dc:type></item></channel></rss>