Compare commits

...
3 Commits
Author SHA1 Message Date
github-actions[bot] 5bed66e853 docs: auto-update README.md via pytudes.py 2026-09-16 19:30:26 +00:00
Peter Norvig 40b87eae78 Add new entries to pytudes.py 2026-09-16 12:30:14 -07:00
Peter Norvig f814edcd3a Add files via upload 2026-09-16 12:27:06 -07:00
3 changed files with 648 additions and 1 deletions

No files matched your search

+2
View File
@@ -26,6 +26,7 @@ For each notebook you can hover on the title to see a description, or click the
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/lispy.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/lispy.ipynb) | 2026 | <a href="ipynb/lispy.ipynb" title="Tutorial on interpreters.">(How to Write a (Lisp) Interpreter (in Python))</a> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/RationalPi.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/RationalPi.ipynb) | 2026 | <a href="ipynb/RationalPi.ipynb" title="Using continued fractions to find fractions (like 22/7) that are close to pi.">Approximating Pi with a Fraction</a> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/clvr.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/clvr.ipynb) | 2026 | <a href="ipynb/clvr.ipynb" title="Guess famous phrases from letter shapes. Beginner level.">Did you solve it? R y clvr ngh t rd ths sntnc?</a> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/palsum.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/palsum.ipynb) | 2026 | <a href="ipynb/palsum.ipynb" title="Finding all palindromic integers with no zero digit (human vs LLM solutions)">Integer Palindromes</a> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Euler3.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Euler3.ipynb) | 2026 | <a href="ipynb/Euler3.ipynb" title="For beginner programmers: How to find the largest prime factor of a number. Project Euler #3.">Largest prime factor</a> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Euler-Fable.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Euler-Fable.ipynb) | 2026 | <a href="ipynb/Euler-Fable.ipynb" title="Solutions to Project Euler math/programming problems by Claude Fable LLM">Project Euler #1–100 by Fable LLM</a> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Euler-Kimi.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Euler-Kimi.ipynb) | 2026 | <a href="ipynb/Euler-Kimi.ipynb" title="Solutions to Project Euler math/programming problems by Kimi LLM">Project Euler #1–100 by Kimi LLM</a> |
@@ -42,6 +43,7 @@ For each notebook you can hover on the title to see a description, or click the
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Advent-2025-AI.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Advent-2025-AI.ipynb) | 2025 | <a href="ipynb/Advent-2025-AI.ipynb" title="Using large language coding models to solve AoC puzzles">Advent of Code 2025: AI LLM Edition</a> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Goldberg.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Goldberg.ipynb) | 2019 | <a href="ipynb/Goldberg.ipynb" title="A re-implementation in Python 3 of Yoav Goldberg's unreasonably effective character-level n-gram language model.">Effectiveness of Language Models</a> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/How%20to%20Do%20Things%20with%20Words.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/How%20to%20Do%20Things%20with%20Words.ipynb) | 2018 | <a href="ipynb/How%20to%20Do%20Things%20with%20Words.ipynb" title="Spelling Correction, Secret Codes, Word Segmentation, and more">How to Do Things with Words: NLP in Python</a> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/palsum.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/palsum.ipynb) | 2026 | <a href="ipynb/palsum.ipynb" title="Finding all palindromic integers with no zero digit (human vs LLM solutions)">Integer Palindromes</a> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/Triplets.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/Triplets.ipynb) | 2024 | <a href="ipynb/Triplets.ipynb" title="LLMs do better at producing a program to solve this puzzle than they do at solving it directly">The Languages of English, Math, and Programming</a> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/CherylMind.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/CherylMind.ipynb) | 2024 | <a href="ipynb/CherylMind.ipynb" title="Do LLMs have enough theory of mind to solve the Cheryl's Birthday puzzle?">LLMs, Theory of Mind, and Cheryl's Birthday</a> |
| [co](https://colab.research.google.com/github/norvig/pytudes/blob/main/ipynb/OneLetterOff.ipynb) [nb](https://nbviewer.jupyter.org/github/norvig/pytudes/blob/main/ipynb/OneLetterOff.ipynb) | 2023 | <a href="ipynb/OneLetterOff.ipynb" title="Word game; use of large language model word embeddings to generate clues.">One Letter Off</a> |
+644
View File
@@ -0,0 +1,644 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "e57b9914-8169-4acb-ab11-03891732ecf1",
"metadata": {},
"source": [
"<div align=\"right\" style=\"text-align: right\"><i>Peter Norvig<br>Sept 2026</i></div>\n",
"\n",
"# Integer Palindromes\n",
"\n",
"Consider this slightly-modified version of a problem from the [2026 AIME](https://artofproblemsolving.com/wiki/index.php/2026_AIME_I_Problems/Problem_2?srsltid=AfmBOor4WAOSBdOAsLKKfku51DG21_h8NUzWEenLIieAMaUFWilw8QeB) (American Invitational Mathematics Examination): \n",
"\n",
"***Write a function to return the positive integer palindromes written in base 10, with no zero digits, and whose digits add up to N.***\n",
"\n",
"\n",
"Here are my thoughts about the problem:\n",
"- An integer can only become a [palindromic number](https://en.wikipedia.org/wiki/Palindromic_number) when expressed as a sequence of characters.\n",
" - Thus, my function's signature will be `palindromes_with_digit_sum(N: int) -> list[str]`\n",
" - For example, `palindromes_with_digit_sum(4) == ['1111', '121', '22', '4']`.\n",
" - The empty string is [considered](https://en.wikipedia.org/wiki/Empty_string) a palindrome, so `palindromes_with_digit_sum(0) == ['']`.\n",
"- Valid palindromes come from two cases:\n",
" - Base case: a palindrome can be empty (when *N* = 0) or can be a single digit (when 1 ≤ *N* ≤ 9).\n",
" - Multi-digit case: a palindrome can have first and last digit *d*, and a middle part that is a palindrome.\n",
" - Middle parts can be found by recursively asking for palindromes with digit sum equal to *N* - 2*d*.\n",
" - Since the recursive step always subtracts 2*d* from *N*, the process will always terminate in at most *N*/2 steps.\n",
" - (If zero digits were allowed, it would not terminate.)\n",
"\n",
"# My Solution\n",
"\n",
"Those thoughts easily translate into a function that is concise and reasonably efficient:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "c096f544-36dd-4d54-9b25-04add9e32bc8",
"metadata": {},
"outputs": [],
"source": [
"from functools import cache\n",
"\n",
"@cache\n",
"def palindromes_with_digit_sum(N: int) -> list[str]:\n",
" \"\"\"Palindromic digit-strings, with no zero digits, whose digits sum to `N`.\"\"\"\n",
" base_case = [''] if N == 0 else [str(N)] if 1 <= N <= 9 else []\n",
" multi_case = [f'{d}{middle}{d}'\n",
" for d in range(1, 10) if d * 2 <= N\n",
" for middle in palindromes_with_digit_sum(N - 2 * d)]\n",
" return multi_case + base_case"
]
},
{
"cell_type": "markdown",
"id": "0114e46a-a615-4687-adf9-15d6eb177f82",
"metadata": {},
"source": [
"Here's the program in action:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "8adb65e4-cb37-465c-95bd-8307a1bfe62c",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['1111111', '11311', '12121', '151', '21112', '232', '313', '7']"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"palindromes_with_digit_sum(7)"
]
},
{
"cell_type": "markdown",
"id": "78009bb9-88ba-4d91-9029-09c16288da46",
"metadata": {},
"source": [
" That looks right.\n",
" \n",
"# Analysis\n",
"\n",
"Here are some facts I noticed:\n",
" - If *N* is odd, each palindrome must have an odd length, with an odd middle digit.\n",
" - If *N* is even, each palindrome can have an even length, or an odd length with an even middle digit.\n",
" - The number of solutions for *N* = 2*m* is the same as for *N* = 2*m* + 1. Here's why:\n",
" - For each even-length 2*m* solution, you can insert 1 as the middle digit.\n",
" - For each odd-length 2*m* solution, you can increase the (even) middle digit by 1.\n",
" - The number of solutions for *N* + 2 is either double or slightly less than double the number of solutions for *N*.\n",
" - The doubling breaks down when we run out of digits. For example, with *N* = 7, one solution is \"7\", and for *N* + 2 we can change that solution to either \"171\" or \"9\", doubling the count (and similarly for every other palindrome with sum 7). But for *N* = 8, we we can't add 2 to the digit \"8\", so we get one less than double the number of solutions.\n",
"\n",
"Exploring the counts of number of valid palindromes:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "7215d254-48ac-449c-8712-dea49189abb9",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1 1 2 2 4 4 8 8 16 16 31 31 62 62 124 124 248 248 496 496 991 991 1980 1980 3956 3956 7904 7904 15792 15792\n"
]
}
],
"source": [
"def count(N) -> int: return len(palindromes_with_digit_sum(N))\n",
"\n",
"print(*(count(N) for N in range(30)))"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "886cca47-a2d7-4d8b-8e87-90d1c48f56e1",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{0: 2.0,\n",
" 1: 2.0,\n",
" 2: 2.0,\n",
" 3: 2.0,\n",
" 4: 2.0,\n",
" 5: 2.0,\n",
" 6: 2.0,\n",
" 7: 2.0,\n",
" 8: 1.9375,\n",
" 9: 1.9375,\n",
" 10: 2.0,\n",
" 11: 2.0,\n",
" 12: 2.0,\n",
" 13: 2.0,\n",
" 14: 2.0,\n",
" 15: 2.0,\n",
" 16: 2.0,\n",
" 17: 2.0,\n",
" 18: 1.997983870967742,\n",
" 19: 1.997983870967742,\n",
" 20: 1.997981836528759,\n",
" 21: 1.997981836528759,\n",
" 22: 1.997979797979798,\n",
" 23: 1.997979797979798,\n",
" 24: 1.9979777553083924,\n",
" 25: 1.9979777553083924,\n",
" 26: 1.9979757085020242,\n",
" 27: 1.9979757085020242,\n",
" 28: 1.9980369807497467,\n",
" 29: 1.9980369807497467}"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Does the number of solutions double from N to N+2?\n",
"{N: count(N + 2) / count(N) for N in range(30)}"
]
},
{
"cell_type": "markdown",
"id": "d0199f94-d8d6-4728-8dc6-11a3ee191abc",
"metadata": {},
"source": [
"# Tests\n",
"\n",
"I'll define `test` to test any function that purports to meet the specification. The output of the function is canonicalized because an implementation might reasonably choose to use ints instead of strings, and might return (or yield) the palindromes in any order (not necessarily the lexicographic order that my program produces). If the function has a cache, it is cleared at the start."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "0034dd91-13ab-498d-9e9c-9f7debfd00e7",
"metadata": {},
"outputs": [],
"source": [
"import time\n",
"\n",
"def test(palindrome_function) -> bool:\n",
" \"\"\"Tests for the palindrome digit sum problem.\"\"\"\n",
"\n",
" if hasattr(palindrome_function, 'cache_clear'):\n",
" palindrome_function.cache_clear()\n",
" \n",
" def f(N: int) -> list[str]: \n",
" \"\"\"Canonicalize the output of palindrome_function(N).\"\"\"\n",
" return sorted(map(str, palindrome_function(N)))\n",
"\n",
" # Tests for small values of N\n",
" assert f(0) == [''] or f(0) == [] # Spec is unclear; either answer is acceptable\n",
" assert f(1) == ['1']\n",
" assert f(2) == ['11', '2']\n",
" assert f(3) == ['111', '3']\n",
" assert f(4) == ['1111', '121', '22', '4']\n",
" assert f(5) == ['11111', '131', '212', '5']\n",
" assert f(6) == ['111111', '11211', '1221', '141', '2112', '222', '33', '6']\n",
" assert f(7) == ['1111111', '11311', '12121', '151', '21112', '232', '313', '7']\n",
"\n",
" # Try the function on some larger N and check the count of palindromes.\n",
" for N, count in {38: 502_864, 40: 1_004_737, 42: 2_007_494}.items():\n",
" assert len(f(N)) == count == len(f(N + 1))"
]
},
{
"cell_type": "markdown",
"id": "f4db4656-6146-4bae-9235-eae95943dd5a",
"metadata": {},
"source": [
"It takes about a second to run the test suite on cases up to *N* = 44 (for which there are 4,011,032 valid palindromes)."
]
},
{
"cell_type": "markdown",
"id": "9798c834-da89-4ff6-99e7-ae393dde9e93",
"metadata": {},
"source": [
"# LLM Solutions\n",
"\n",
"I asked four Large Language Model coding agents to solve this problem: Gemini 3.1 Pro, GPT 5.6 Sol, Claude Sonnet 5, and Kimi K3.\n",
"\n",
"On the positive side, they all wrote correct code that passed the tests, and they all got the idea that you only have to build up the first half of the digit string, and that you shouldn't generate half-strings with a sum greater than *N*/2.\n",
"\n",
"On the negative side, they all ran 10 to 20 times slower on the test suite compared to my solution, largely because none of them used `@cache`. Only Gemini produced what I considered a clear, simple solution (basically the same as mine, but using `for` and `if` statements instead of conditional expressions and list comprehensions). All the others had overly complicated code (with a line count 6 times mine). Only GPT provided type hints.\n",
"\n",
"For each coding agent, the table below lists the test pass/fail grade, the lines of code (including blank lines and comments), the wall time in seconds for the test suite, and my subjective code-quality judgment. So for this round, the human came out on top!\n",
"\n",
"|Author|Test|LOC|Time|Subjective|\n",
"|-----|---:|---:|---:|---|\n",
"|Human|pass|10|0.7|Good|\n",
"|Gemini|pass|19|7.4|Good|\n",
"|ChatGPT|pass|60|10.1|Too complicated|\n",
"|Claude|pass|62|16.1|Too complicated|\n",
"|Kimi|pass|62|14.9|Too complicated|\n",
"\n",
"\n",
"\n",
"# Gemini 3.1 Pro's Solution\n"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "f2e63e96-7cd9-4459-a626-f09f22bd8e84",
"metadata": {},
"outputs": [],
"source": [
"def gemini_generate_palindromes(target_sum):\n",
" \"\"\"\n",
" Yields all positive integer palindromes (as strings) with no zero digits\n",
" whose digits add up to the target_sum.\n",
" \"\"\"\n",
" # Base case 1: The remaining sum is exactly 0 (even-length palindrome center)\n",
" if target_sum == 0:\n",
" yield \"\"\n",
" \n",
" # Base case 2: The remaining sum is a single non-zero digit (odd-length center)\n",
" if 1 <= target_sum <= 9:\n",
" yield str(target_sum)\n",
" \n",
" # Recursive step: Wrap matching pairs of digits (1 through 9) around the outside\n",
" for d in range(1, 10):\n",
" # We need enough remaining sum for two 'd' digits\n",
" if target_sum >= 2 * d:\n",
" for inner in gemini_generate_palindromes(target_sum - 2 * d):\n",
" yield f\"{d}{inner}{d}\""
]
},
{
"cell_type": "markdown",
"id": "b2794730-0eba-41c2-9cc8-38bf0250ce26",
"metadata": {},
"source": [
"# GPT 5.6 Sol's Solution"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "1fa05867-c009-44a1-b084-6198c3798123",
"metadata": {},
"outputs": [],
"source": [
"from collections.abc import Iterator\n",
"\n",
"\n",
"def _halves_with_sum(weights: tuple[int, ...], target: int) -> Iterator[str]:\n",
" \"\"\"Yield digit strings whose weighted digit sum is ``target``.\"\"\"\n",
"\n",
" suffix_weight = [0] * (len(weights) + 1)\n",
" for index in range(len(weights) - 1, -1, -1):\n",
" suffix_weight[index] = suffix_weight[index + 1] + weights[index]\n",
"\n",
" digits: list[str] = []\n",
"\n",
" def visit(index: int, remaining: int) -> Iterator[str]:\n",
" if index == len(weights):\n",
" if remaining == 0:\n",
" yield \"\".join(digits)\n",
" return\n",
"\n",
" weight = weights[index]\n",
" rest_weight = suffix_weight[index + 1]\n",
" for digit in range(1, 10):\n",
" after = remaining - weight * digit\n",
" # Every remaining digit is in [1, 9].\n",
" if rest_weight <= after <= 9 * rest_weight:\n",
" digits.append(str(digit))\n",
" yield from visit(index + 1, after)\n",
" digits.pop()\n",
"\n",
" yield from visit(0, target)\n",
"\n",
"\n",
"def chatGPT_palindromes_with_digit_sum(n: int) -> Iterator[int]:\n",
" \"\"\"Yield positive zero-free decimal palindromes with digit sum ``n``.\n",
"\n",
" Results are yielded in increasing numeric order. The sequence is finite:\n",
" since every digit is at least one, a result can contain at most ``n``\n",
" digits.\n",
" \"\"\"\n",
"\n",
" if n < 1:\n",
" return\n",
"\n",
" for length in range(1, n + 1):\n",
" paired_digits = length // 2\n",
" weights = (2,) * paired_digits\n",
" if length % 2:\n",
" weights += (1,)\n",
"\n",
" minimum_sum = sum(weights)\n",
" maximum_sum = 9 * minimum_sum\n",
" if not minimum_sum <= n <= maximum_sum:\n",
" continue\n",
"\n",
" for half in _halves_with_sum(weights, n):\n",
" left = half[:paired_digits]\n",
" if length % 2:\n",
" palindrome = half + left[::-1]\n",
" else:\n",
" palindrome = half + half[::-1]\n",
" yield int(palindrome)"
]
},
{
"cell_type": "markdown",
"id": "71ed927d-de25-4f9d-85a7-18c60fdab010",
"metadata": {},
"source": [
"# Claude Sonnet 5's Solution"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "98d21fdc-ac2f-4f46-bab2-e87dfb84db27",
"metadata": {},
"outputs": [],
"source": [
"def gen_digit_sequences(length, target):\n",
" \"\"\"\n",
" Yield every list of `length` digits, each in 1..9, that sums to `target`.\n",
" Uses feasibility pruning (min/max achievable sum) to avoid dead branches.\n",
" \"\"\"\n",
" if length == 0:\n",
" if target == 0:\n",
" yield []\n",
" return\n",
"\n",
" remaining_positions = length - 1\n",
" min_possible = remaining_positions * 1\n",
" max_possible = remaining_positions * 9\n",
"\n",
" for d in range(1, 10):\n",
" rem = target - d\n",
" if min_possible <= rem <= max_possible:\n",
" for tail in gen_digit_sequences(remaining_positions, rem):\n",
" yield [d] + tail\n",
"\n",
"\n",
"def claude_find_palindromes(n):\n",
" \"\"\"\n",
" Return a sorted list of ints: all zero-free palindromes whose digits\n",
" sum to n.\n",
" \"\"\"\n",
" if n < 1:\n",
" return []\n",
"\n",
" palindromes = []\n",
" max_length = n # each digit >= 1, so length can't exceed the target sum\n",
"\n",
" for L in range(1, max_length + 1):\n",
" if L % 2 == 0:\n",
" h = L // 2\n",
" if n % 2 != 0:\n",
" continue # 2*sum(half) is always even\n",
" target = n // 2\n",
" for half in gen_digit_sequences(h, target):\n",
" full_digits = half + half[::-1]\n",
" palindromes.append(full_digits)\n",
" else:\n",
" h = (L + 1) // 2\n",
" if h == 1:\n",
" # single-digit palindrome: the digit itself must equal n\n",
" if 1 <= n <= 9:\n",
" palindromes.append([n])\n",
" continue\n",
" # half = prefix (h-1 digits) + middle digit m\n",
" # n = 2*sum(prefix) + m\n",
" for m in range(1, 10):\n",
" rem = n - m\n",
" if rem < 0 or rem % 2 != 0:\n",
" continue\n",
" prefix_target = rem // 2\n",
" for prefix in gen_digit_sequences(h - 1, prefix_target):\n",
" half = prefix + [m]\n",
" full_digits = half + half[:-1][::-1]\n",
" palindromes.append(full_digits)\n",
"\n",
" numbers = sorted(int(\"\".join(map(str, digits))) for digits in palindromes)\n",
" return numbers"
]
},
{
"cell_type": "markdown",
"id": "549ea6b6-21f3-49c8-b54a-e70710edbfd5",
"metadata": {},
"source": [
"# Kimi K3's Solution"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "0754167e-a8bb-45a7-80a3-8dd15645e7a2",
"metadata": {},
"outputs": [],
"source": [
"def kimi_palindromes_with_digit_sum(digit_sum, *, as_int=False):\n",
" \"\"\"\n",
" Return every positive base-10 palindrome with no zero digits whose\n",
" digit sum equals `digit_sum`.\n",
"\n",
" Returns a numerically sorted list of strings by default.\n",
" Set as_int=True to return integers.\n",
" \"\"\"\n",
" if not isinstance(digit_sum, int) or isinstance(digit_sum, bool):\n",
" raise TypeError(\"digit_sum must be an integer\")\n",
" if digit_sum < 1:\n",
" return []\n",
"\n",
" def compositions(total, parts):\n",
" \"\"\"Generate compositions of total into parts, each between 1 and 9.\"\"\"\n",
" if parts == 1:\n",
" if 1 <= total <= 9:\n",
" yield (total,)\n",
" return\n",
"\n",
" lo = max(1, total - 9 * (parts - 1))\n",
" hi = min(9, total - (parts - 1))\n",
"\n",
" for first in range(lo, hi + 1):\n",
" for rest in compositions(total - first, parts - 1):\n",
" yield (first,) + rest\n",
"\n",
" found = set()\n",
"\n",
" # Single-digit palindromes.\n",
" if digit_sum <= 9:\n",
" found.add(str(digit_sum))\n",
"\n",
" # Even-length palindromes: left + reverse(left).\n",
" # These exist only when the digit sum is even.\n",
" if digit_sum % 2 == 0:\n",
" half_sum = digit_sum // 2\n",
"\n",
" for half_length in range(1, half_sum + 1):\n",
" for left in compositions(half_sum, half_length):\n",
" found.add(\"\".join(map(str, left + left[::-1])))\n",
"\n",
" # Odd-length palindromes: left + center + reverse(left).\n",
" # These must be checked for every digit sum because the center digit\n",
" # can make the total digit sum either even or odd.\n",
" for half_length in range(1, (digit_sum - 1) // 2 + 1):\n",
" for center in range(1, 10):\n",
" remaining = digit_sum - center\n",
"\n",
" if remaining < 0 or remaining % 2:\n",
" continue\n",
"\n",
" left_sum = remaining // 2\n",
" if not half_length <= left_sum <= 9 * half_length:\n",
" continue\n",
"\n",
" for left in compositions(left_sum, half_length):\n",
" digits = left + (center,) + left[::-1]\n",
" found.add(\"\".join(map(str, digits)))\n",
"\n",
" result = sorted(found, key=lambda x: (len(x), x))\n",
" return [int(x) for x in result] if as_int else result"
]
},
{
"cell_type": "markdown",
"id": "45edda37-33a7-4fbb-af18-4d5a8a18a9f6",
"metadata": {},
"source": [
"# Timing Results\n",
"\n",
"Here we see the time each function takes to run the test suite."
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "e299bd97-e997-49f9-8160-e6500d9dbf23",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 627 ms, sys: 68.1 ms, total: 695 ms\n",
"Wall time: 695 ms\n"
]
}
],
"source": [
"%time test(palindromes_with_digit_sum)"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "3d830e10-711a-4702-aca7-7d6a16b03c31",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 7.31 s, sys: 96.8 ms, total: 7.41 s\n",
"Wall time: 7.42 s\n"
]
}
],
"source": [
"%time test(gemini_generate_palindromes)"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "30fc1f93-9921-4512-81b8-0320208f0233",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 9.95 s, sys: 120 ms, total: 10.1 s\n",
"Wall time: 10.1 s\n"
]
}
],
"source": [
"%time test(chatGPT_palindromes_with_digit_sum)"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "ceb02172-c40c-4db8-8b6d-49e29f0d4367",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 15.4 s, sys: 544 ms, total: 15.9 s\n",
"Wall time: 16.1 s\n"
]
}
],
"source": [
"%time test(claude_find_palindromes)"
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "2b058781-1692-44c6-8250-be9f1bde1be4",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU times: user 14.6 s, sys: 261 ms, total: 14.8 s\n",
"Wall time: 14.9 s\n"
]
}
],
"source": [
"%time test(kimi_palindromes_with_digit_sum)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+2 -1
View File
@@ -7,8 +7,9 @@ notebooks = {
'Large Language Models': [
("AlphaCode Automated Programming", 2022, 'AlphaCode.ipynb', "Analysis of AlphaCode's automated solution to a coding problem"),
('Advent of Code 2025: AI LLM Edition', 2025, 'Advent-2025-AI.ipynb', 'Using large language coding models to solve AoC puzzles'),
("Effectiveness of Language Models", 2019, 'Goldberg.ipynb', "A re-implementation in Python 3 of Yoav Goldberg's unreasonably effective character-level n-gram language model."),
('Effectiveness of Language Models', 2019, 'Goldberg.ipynb', "A re-implementation in Python 3 of Yoav Goldberg's unreasonably effective character-level n-gram language model."),
('How to Do Things with Words: NLP in Python', 2018, 'How%20to%20Do%20Things%20with%20Words.ipynb', 'Spelling Correction, Secret Codes, Word Segmentation, and more'),
('Integer Palindromes', 2026, 'palsum.ipynb', 'Finding all palindromic integers with no zero digit (human vs LLM solutions)'),
('The Languages of English, Math, and Programming', 2024, 'Triplets.ipynb', 'LLMs do better at producing a program to solve this puzzle than they do at solving it directly'),
("LLMs, Theory of Mind, and Cheryl's Birthday", 2024, 'CherylMind.ipynb', "Do LLMs have enough theory of mind to solve the Cheryl's Birthday puzzle?"),
("One Letter Off", 2023, 'OneLetterOff.ipynb', "Word game; use of large language model word embeddings to generate clues."),