Skip to content

Commit

Permalink
Credit xnor
Browse files Browse the repository at this point in the history
  • Loading branch information
purplesyringa committed Nov 17, 2024
1 parent e0c7a7d commit 4ac1c56
Show file tree
Hide file tree
Showing 2 changed files with 4 additions and 4 deletions.
4 changes: 2 additions & 2 deletions blog/any-python-program-fits-in-24-characters/index.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!doctypehtml><html prefix="og: http://ogp.me/ns#"lang=en_US><meta charset=utf-8><meta content=width=device-width,initial-scale=1 name=viewport><title>Any Python program fits in 24 characters* | purplesyringa's blog</title><link href=../../favicon.ico?v=2 rel=icon><link href=../../all.css rel=stylesheet><link href=../../blog.css rel=stylesheet><link href=../../vendor/Temml-Local.css rel=stylesheet><link crossorigin href=https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&family=Roboto+Mono:ital,wght@0,100..700;1,100..700&family=Roboto:ital,wght@0,400;0,700;1,400;1,700&family=Slabo+27px&display=swap rel=stylesheet><link href=../../fonts/webfont.css rel=stylesheet><link media="screen and (prefers-color-scheme: dark"href=../../vendor/atom-one-dark.min.css rel=stylesheet><link media="screen and (prefers-color-scheme: light"href=../../vendor/a11y-light.min.css rel=stylesheet><link title="Blog posts"href=../../blog/feed.rss rel=alternate type=application/rss+xml><meta content="Any Python program fits in 24 characters*"property=og:title><meta content=article property=og:type><meta content=https://purplesyringa.moe/blog/any-python-program-fits-in-24-characters/og.png property=og:image><meta content=https://purplesyringa.moe/blog/any-python-program-fits-in-24-characters/ property=og:url><meta content="* If you don’t take whitespace into account.
My friend challenged me to find the shortest solution to a certain Leetcode-style problem in Python. They were generous enough to let me use whitespace for free, so that the code stays readable. So that’s exactly what we’ll abuse to encode any Python program in 24 bytes, ignoring whitespace."property=og:description><meta content=en_US property=og:locale><meta content="purplesyringa's blog"property=og:site_name><meta content=summary_large_image name=twitter:card><meta content=https://purplesyringa.moe/blog/any-python-program-fits-in-24-characters/og.png name=twitter:image><script data-website-id=0da1961d-43f2-45cc-a8e2-75679eefbb69 defer src=https://zond.tei.su/script.js></script><body><header><div class=viewport-container><div class=media><a href=https://github.com/purplesyringa><img alt=GitHub src=../../images/github-mark-white.svg></a></div><h1><a href=/>purplesyringa</a></h1><nav><a href=../..>about</a><a class=current href=../../blog/>blog</a><a href=../../sink/>kitchen sink</a></nav></div></header><section><div class=viewport-container><h2>Any Python program fits in 24 characters*</h2><time>November 17, 2024</time><p><em>* If you don’t take whitespace into account.</em><p>My friend challenged me to find the shortest solution to a certain Leetcode-style problem in Python. They were generous enough to let me use whitespace for free, so that the code stays readable. So that’s exactly what we’ll abuse to encode <em>any</em> Python program in <eq><math><mn>24</mn></math></eq> bytes, ignoring whitespace.<blockquote><p>This post originally stated that <eq><math><mn>30</mn></math></eq> characters are always enough. Since then, <a href=https://github.com/commandblockguy>commandz</a> and another person from the codegolf Discord server have devised a better solution, reaching <eq><math><mn>24</mn></math></eq> bytes. After a few minor modifications, it satisfies the requirements of this problem, so I publish it here too.</blockquote><p class=next-group><span aria-level=3 class=side-header role=heading><span>Bits</span></span>We can encode arbitrary data in a string by only using whitespace. For example, we could encode <code>0</code> bits as spaces and <code>1</code> bits as tabs. Now you just have to decode this.<p>As you start implementing the decoder, it immediately becomes clear that this approach requires about 50 characters at minimum. You can use <code>c % 2 for c in b"..."</code> to extract individual bits, then you need to merge bits by using <code>str</code> and concatenating then with <code>"".join(...)</code>, then you to parse the bits with <code>int.to_bytes(...)</code>, and finally call <code>exec</code>. We need to find another solution.<p class=next-group><span aria-level=3 class=side-header role=heading><span>Characters</span></span>What if we didn’t go from characters to bits and then back? What if instead, we mapped each whitespace character to its own non-whitespace character and then evaluated that?<pre><code class=language-python><span class=hljs-built_in>exec</span>(
My friend challenged me to find the shortest solution to a certain Leetcode-style problem in Python. They were generous enough to let me use whitespace for free, so that the code stays readable. So that’s exactly what we’ll abuse to encode any Python program in 24 bytes, ignoring whitespace."property=og:description><meta content=en_US property=og:locale><meta content="purplesyringa's blog"property=og:site_name><meta content=summary_large_image name=twitter:card><meta content=https://purplesyringa.moe/blog/any-python-program-fits-in-24-characters/og.png name=twitter:image><script data-website-id=0da1961d-43f2-45cc-a8e2-75679eefbb69 defer src=https://zond.tei.su/script.js></script><body><header><div class=viewport-container><div class=media><a href=https://github.com/purplesyringa><img alt=GitHub src=../../images/github-mark-white.svg></a></div><h1><a href=/>purplesyringa</a></h1><nav><a href=../..>about</a><a class=current href=../../blog/>blog</a><a href=../../sink/>kitchen sink</a></nav></div></header><section><div class=viewport-container><h2>Any Python program fits in 24 characters*</h2><time>November 17, 2024</time><p><em>* If you don’t take whitespace into account.</em><p>My friend challenged me to find the shortest solution to a certain Leetcode-style problem in Python. They were generous enough to let me use whitespace for free, so that the code stays readable. So that’s exactly what we’ll abuse to encode <em>any</em> Python program in <eq><math><mn>24</mn></math></eq> bytes, ignoring whitespace.<blockquote><p>This post originally stated that <eq><math><mn>30</mn></math></eq> characters are always enough. Since then, <a href=https://github.com/commandblockguy>commandz</a> and xnor from the <a href=https://code.golf>Code Golf</a> Discord server have devised a better solution, reaching <eq><math><mn>24</mn></math></eq> bytes. After a few minor modifications, it satisfies the requirements of this problem, so I publish it here too.</blockquote><p class=next-group><span aria-level=3 class=side-header role=heading><span>Bits</span></span>We can encode arbitrary data in a string by only using whitespace. For example, we could encode <code>0</code> bits as spaces and <code>1</code> bits as tabs. Now you just have to decode this.<p>As you start implementing the decoder, it immediately becomes clear that this approach requires about 50 characters at minimum. You can use <code>c % 2 for c in b"..."</code> to extract individual bits, then you need to merge bits by using <code>str</code> and concatenating then with <code>"".join(...)</code>, then you to parse the bits with <code>int.to_bytes(...)</code>, and finally call <code>exec</code>. We need to find another solution.<p class=next-group><span aria-level=3 class=side-header role=heading><span>Characters</span></span>What if we didn’t go from characters to bits and then back? What if instead, we mapped each whitespace character to its own non-whitespace character and then evaluated that?<pre><code class=language-python><span class=hljs-built_in>exec</span>(
<span class=hljs-string>"[whitespace...]"</span>
.replace(<span class=hljs-string>" "</span>, <span class=hljs-string>"A"</span>)
.replace(<span class=hljs-string>"\t"</span>, <span class=hljs-string>"B"</span>)
Expand All @@ -17,7 +17,7 @@
)
)
</code></pre><p>The characters <code>ABCDEFGHIJ</code> are located at indices <eq><math><mrow><mn>9</mn><mo separator=true>,</mo></mrow><mrow><mn>11</mn><mo separator=true>,</mo></mrow><mrow><mn>12</mn><mo separator=true>,</mo></mrow><mrow><mn>28</mn><mo separator=true>,</mo></mrow><mrow><mn>29</mn><mo separator=true>,</mo></mrow><mrow><mn>30</mn><mo separator=true>,</mo></mrow><mrow><mn>31</mn><mo separator=true>,</mo></mrow><mrow><mn>32</mn><mo separator=true>,</mo></mrow><mrow><mn>133</mn><mo separator=true>,</mo></mrow><mrow><mn>160</mn></mrow></math></eq> – all whitespace code points below <eq><math><mn>256</mn></math></eq> except CR and LF, which are invalid in a string. While this code is long, most of it is just whitespace, which we ignore. After removing whitespace, it’s only <eq><math><mn>32</mn></math></eq> characters:<pre><code class=language-python><span class=hljs-built_in>exec</span>(<span class=hljs-string>""</span>.translate(<span class=hljs-string>"ABCDEFGHIJ"</span>))
</code></pre><p>We can now encode any Python program that uses at most <eq><math><mn>10</mn></math></eq> different characters. We could now use <a href=https://github.com/kuangkzh/PyFuck>PyFuck</a>, which transforms any Python script to an equivalent script that uses only <eq><math><mn>8</mn></math></eq> characters: <code>exc('%0)</code>. This reduces the code size to <eq><math><mn>30</mn></math></eq> charaters (plus whitespace). A bit of postprocessing is necessary to get it working well, as PyFuck often has exponential output, but that’s a minor issue.<p class=next-group><span aria-level=3 class=side-header role=heading><span>A better way</span></span>But it turns out there’s another way to translate whitespace to non-whitespace.<blockquote><p>This solution was found by a reader of my blog – thanks!</blockquote><p>When <code>repr</code> is applied to Unicode strings, it replaces the Unicode codepoints with their <code>\uXXXX</code> representations. For example, <code>U+2001 Em Quad</code> is encoded as <code>'\u2001'</code>. All in all, Unicode whitespace gives us unlimited supply of <code>\</code>, <code>x</code>, and the whole hexadecimal alphabet (plus two instances of <code>'</code>).<p>Say we wanted to extract the least significant digits of characters from <code>U+2000</code> to <code>U+2007</code>. Here’s how to do this:<pre><code class=language-python><span class=hljs-comment># Imagine these \uXXXX escapes are literal whitespace characters</span>
</code></pre><p>We can now encode any Python program that uses at most <eq><math><mn>10</mn></math></eq> different characters. We could now use <a href=https://github.com/kuangkzh/PyFuck>PyFuck</a>, which transforms any Python script to an equivalent script that uses only <eq><math><mn>8</mn></math></eq> characters: <code>exc('%0)</code>. This reduces the code size to <eq><math><mn>30</mn></math></eq> charaters (plus whitespace). A bit of postprocessing is necessary to get it working well, as PyFuck often has exponential output, but that’s a minor issue.<p class=next-group><span aria-level=3 class=side-header role=heading><span>A better way</span></span>But it turns out there’s another way to translate whitespace to non-whitespace.<blockquote><p>This solution was found by readers of my blog – thanks!</blockquote><p>When <code>repr</code> is applied to Unicode strings, it replaces the Unicode codepoints with their <code>\uXXXX</code> representations. For example, <code>U+2001 Em Quad</code> is encoded as <code>'\u2001'</code>. All in all, Unicode whitespace gives us unlimited supply of <code>\</code>, <code>x</code>, and the whole hexadecimal alphabet (plus two instances of <code>'</code>).<p>Say we wanted to extract the least significant digits of characters from <code>U+2000</code> to <code>U+2007</code>. Here’s how to do this:<pre><code class=language-python><span class=hljs-comment># Imagine these \uXXXX escapes are literal whitespace characters</span>
<span class=hljs-meta>>>> </span><span class=hljs-built_in>repr</span>(<span class=hljs-string>"\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007"</span>)[<span class=hljs-number>6</span>::<span class=hljs-number>6</span>]
<span class=hljs-string>'01234567'</span>
</code></pre><p>To get <code>\</code>, <code>x</code>, and the rest of the hexadecimal alphabet, we need characters like <code>U+000B</code> and <code>U+001F</code>. We also need to align the strings exactly, so that one of the columns contains all the alphabet:<pre><code class=language-python> v
Expand Down
4 changes: 2 additions & 2 deletions blog/any-python-program-fits-in-24-characters/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ intro: |

My friend challenged me to find the shortest solution to a certain Leetcode-style problem in Python. They were generous enough to let me use whitespace for free, so that the code stays readable. So that's exactly what we'll abuse to encode *any* Python program in $24$ bytes, ignoring whitespace.

> This post originally stated that $30$ characters are always enough. Since then, [commandz](https://github.com/commandblockguy) and another person from the codegolf Discord server have devised a better solution, reaching $24$ bytes. After a few minor modifications, it satisfies the requirements of this problem, so I publish it here too.
> This post originally stated that $30$ characters are always enough. Since then, [commandz](https://github.com/commandblockguy) and xnor from the [Code Golf](https://code.golf) Discord server have devised a better solution, reaching $24$ bytes. After a few minor modifications, it satisfies the requirements of this problem, so I publish it here too.

### Bits
Expand Down Expand Up @@ -79,7 +79,7 @@ We can now encode any Python program that uses at most $10$ different characters
But it turns out there's another way to translate whitespace to non-whitespace.
> This solution was found by a reader of my blog -- thanks!
> This solution was found by readers of my blog -- thanks!
When `repr` is applied to Unicode strings, it replaces the Unicode codepoints with their `\uXXXX` representations. For example, `U+2001 Em Quad` is encoded as `'\u2001'`. All in all, Unicode whitespace gives us unlimited supply of `\`, `x`, and the whole hexadecimal alphabet (plus two instances of `'`).
Expand Down

0 comments on commit 4ac1c56

Please sign in to comment.