Add files via upload

This commit is contained in:
Peter Norvig authored and GitHub committed 2026-10-05 15:48:49 -07:00
1 parent f489ad80ed
commit 9d3be567d8
1 file changed
+329 -229
+329 -229
View File
@@ -9,24 +9,24 @@
"\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",
"Consider this problem:\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",
"***Write a function to return the integer [palindromic numbers](https://en.wikipedia.org/wiki/Palindromic_number) written in base 10, with no zero digits, and whose digits add up to N.***\n",
"\n",
"\n",
"(This is a generalization 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) which asked only for the count of palindromes with digit sum *N* = 13.) \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",
"- A palindrome is a sequence of characters. It is the base 10 string of characters, not the integer itself, that is a palindrome.\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",
" - The empty string is [generally considered](https://en.wikipedia.org/wiki/Empty_string) a palindrome.\n",
"- Valid palindromes can be classified into two kinds:\n",
" - Base cases: a valid palindrome can be the empty string (when *N* = 0) or can be a single digit (when 1 ≤ *N* ≤ 9).\n",
" - Multi-digit cases: a valid palindrome can have first and last digit *d*, and a middle part that is a valid palindrome.\n",
" - Middle parts can be found by recursively asking for valid palindromes with digit sum equal to *N* - 2*d*.\n",
"\n",
"# My Solution\n",
"# Solution\n",
"\n",
"Those thoughts easily translate into a function that is concise and reasonably efficient:"
]
@@ -43,11 +43,11 @@
"@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",
" base_cases = [''] if N == 0 else [str(N)] if 1 <= N <= 9 else [] # 0 or 1 digits\n",
" multi_cases = [f'{d}{middle}{d}' # 2 or more digits\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"
" return multi_cases + base_cases"
]
},
{
@@ -79,148 +79,114 @@
"palindromes_with_digit_sum(7)"
]
},
{
"cell_type": "markdown",
"id": "9a2e7597-81c8-4461-8dcb-dc56bbba42d0",
"metadata": {},
"source": [
"That looks right."
]
},
{
"cell_type": "markdown",
"id": "925553ad-9dab-4290-84c5-6974fc9a79e2",
"metadata": {
"jp-MarkdownHeadingCollapsed": true
},
"source": [
"# TL;DR: Comparing Human and LLM Solutions\n",
"\n",
"I asked four Large Language Model coding agents (Gemini 3.1 Pro, GPT 5.6 Sol, Claude Sonnet 5, and Kimi K3) to solve this problem.\n",
"\n",
"On the **positive** side, they all wrote correct code that passed all my tests, and they all got the idea that the first part of the digit string has to mirror the last part, and that you shouldn't generate half-parts with a sum greater than *N*/2.\n",
"\n",
"On the **negative** side, they all ran 10 to 20 times slower compared to my solution (on the particular test suite), largely because none of them used `@cache`. Only Gemini produced what I considered a clear, simple solution (basically the same as mine, but using statements instead of expressions). All the others had overly complicated code (with a line count 6 times mine). Only GPT provided type hints.\n",
"\n",
"Looking at the code they wrote, it seems like they all failed to realize that the size of the result increases exponentially as roughly 2<sup>*N*/2</sup>, and that caching could reduce an exponential number of calls. All but Gemini failed to realize that there is a simple recursive rule that determines the results for *N* in terms of the results for *N-2* through *N*-18. So I decided to give each LLM a second chance, this time using a prompt that contains two hints:\n",
"\n",
">*Write a function to return the integer palindromic numbers written in base 10, with no zero digits, and whose digits add up to N.*\n",
">\n",
">*Keep in mind these two very general CS ideas:*\n",
">\n",
">1. *Functions that are called repeatedly with the same argument can often benefit from caching.*\n",
">\n",
">2. *A problem with parameter N can often be broken down to a call or calls to the same function with smaller parameter values, followed by some adaptation to combine the smaller result(s) into the final result.*\n",
"\n",
"\n",
"For each coding agent, the table below lists the lines of code (including blank lines and comments), the run time in seconds on the test suite, and my subjective code style judgment. For the non-human agents, there are also columns for their second prompt (incorporating the two hints). The hints helped a lot! That is a bit surprising: surely the models in some sense \"knew\" the two hints – they had seen lots of example problems where each of the hints were applied – but they failed to properly apply what they \"knew\" without the hint. So (at least this time) the human programmer came out on top.\n",
"\n",
"|Author|Lines 1|Time 1|Style 1|Lines 2|Time 2|Style 2|\n",
"|-----|---:|---:|:---:|---:|---:|:---:|\n",
"|Human |10| 0.7|Good|– |– |– |\n",
"|Gemini |19| 7.5|Good|42|2.5|Okay|\n",
"|GPT |60|10.1|Bad |25|1.1|Good|\n",
"|Claude |62|16.4|Bad |20|1.9|Good|\n",
"|Kimi |62|14.6|Bad |31|1.0|Good|\n",
"\n",
"Interestingly, Gemini, which had the best and most concise code with the first prompt, ended up with the least-good and most verbose code after the hints (the hint doubled the lines of code for Gemini and cut it in half for the others)."
]
},
{
"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",
"Here are some facts I discovered while writing my code:\n",
" - If *N* is odd, each palindrome must have an odd digit-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",
" - 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 digit sum *N* = 7). But for the case when *N* = 8, one solution is \"8\", and we can get \"181\", but 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:"
"Exploring the counts of number of valid palindromes, and whether they double when *N* increases by 2:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "7215d254-48ac-449c-8712-dea49189abb9",
"id": "886cca47-a2d7-4d8b-8e87-90d1c48f56e1",
"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"
"count( 2)/count( 0) = 2/1 = 2.000\n",
"count( 3)/count( 1) = 2/1 = 2.000\n",
"count( 4)/count( 2) = 4/2 = 2.000\n",
"count( 5)/count( 3) = 4/2 = 2.000\n",
"count( 6)/count( 4) = 8/4 = 2.000\n",
"count( 7)/count( 5) = 8/4 = 2.000\n",
"count( 8)/count( 6) = 16/8 = 2.000\n",
"count( 9)/count( 7) = 16/8 = 2.000\n",
"count(10)/count( 8) = 31/16 = 1.938\n",
"count(11)/count( 9) = 31/16 = 1.938\n",
"count(12)/count(10) = 62/31 = 2.000\n",
"count(13)/count(11) = 62/31 = 2.000\n",
"count(14)/count(12) = 124/62 = 2.000\n",
"count(15)/count(13) = 124/62 = 2.000\n",
"count(16)/count(14) = 248/124 = 2.000\n",
"count(17)/count(15) = 248/124 = 2.000\n",
"count(18)/count(16) = 496/248 = 2.000\n",
"count(19)/count(17) = 496/248 = 2.000\n",
"count(20)/count(18) = 991/496 = 1.998\n",
"count(21)/count(19) = 991/496 = 1.998\n",
"count(22)/count(20) = 1980/991 = 1.998\n",
"count(23)/count(21) = 1980/991 = 1.998\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": [
""
"# Does the count double when N increases by 2?\n",
"for N in range(22):\n",
" print(f'count({N+2:2})/count({N:2}) = {count(N+2):4}/{count(N):<4} '\n",
" f'= {count(N+2) / count(N):.3f}')"
]
},
{
@@ -228,32 +194,16 @@
"id": "9798c834-da89-4ff6-99e7-ae393dde9e93",
"metadata": {},
"source": [
"# LLM Solutions\n",
"Below are the solutions found by all four coding agents, for both prompts. They each chose their own function names, but I appended the model name so we can keep track, and included a `_2_` in the name of functions that came from the second prompt.\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",
"# Gemini \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"
"For the first prompt:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"execution_count": 4,
"id": "f2e63e96-7cd9-4459-a626-f09f22bd8e84",
"metadata": {},
"outputs": [],
@@ -281,15 +231,74 @@
},
{
"cell_type": "markdown",
"id": "b2794730-0eba-41c2-9cc8-38bf0250ce26",
"id": "3487c9dd-e943-478b-b0a2-e1580f09bd09",
"metadata": {},
"source": [
"# GPT 5.6 Sol's Solution"
"For the second prompt, with the two hints:"
]
},
{
"cell_type": "code",
"execution_count": 7,
"execution_count": 5,
"id": "d3454173-ed42-4258-925a-bb9125ea9072",
"metadata": {},
"outputs": [],
"source": [
"from functools import lru_cache\n",
"from typing import List\n",
"\n",
"def gemini_2_zero_free_palindromes(n: int) -> List[int]:\n",
" \"\"\"\n",
" Returns a sorted list of integer palindromes (no zero digits) \n",
" whose digits sum exactly to `n`.\n",
" \"\"\"\n",
" if n <= 0:\n",
" return []\n",
"\n",
" @lru_cache(None)\n",
" def build_palindromes(target_sum: int) -> List[str]:\n",
" # If the sum overshoots, this path is invalid\n",
" if target_sum < 0:\n",
" return []\n",
" \n",
" # Base case for even-length palindromes: the sum is perfectly depleted,\n",
" # so the innermost \"middle\" is simply an empty string.\n",
" if target_sum == 0:\n",
" return [\"\"]\n",
" \n",
" results = []\n",
" \n",
" # Base case for odd-length palindromes: a single digit in the exact middle.\n",
" # Because zero digits are disallowed, it must strictly be 1 through 9.\n",
" if 1 <= target_sum <= 9:\n",
" results.append(str(target_sum))\n",
" \n",
" # Recursive step: wrap a smaller palindrome with a pair of identical digits (1-9)\n",
" # on the left and right, subtracting their combined value from the target sum.\n",
" for digit in range(1, 10):\n",
" remaining_sum = target_sum - (2 * digit)\n",
" \n",
" if remaining_sum >= 0:\n",
" for inner_pal in build_palindromes(remaining_sum):\n",
" results.append(str(digit) + inner_pal + str(digit))\n",
" \n",
" return results\n",
"\n",
" # Generate the string representations, convert to integers, and return sorted\n",
" return sorted(int(p) for p in build_palindromes(n))"
]
},
{
"cell_type": "markdown",
"id": "b2794730-0eba-41c2-9cc8-38bf0250ce26",
"metadata": {},
"source": [
"# GPT "
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "1fa05867-c009-44a1-b084-6198c3798123",
"metadata": {},
"outputs": [],
@@ -325,7 +334,7 @@
" yield from visit(0, target)\n",
"\n",
"\n",
"def chatGPT_palindromes_with_digit_sum(n: int) -> Iterator[int]:\n",
"def GPT_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",
@@ -356,12 +365,46 @@
" yield int(palindrome)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "585a1991-342b-4b7d-b75c-4ceb9284cce2",
"metadata": {},
"outputs": [],
"source": [
"from functools import cache\n",
"\n",
"@cache\n",
"def GPT_2_palindromes(n):\n",
" \"\"\"Return all positive base-10 palindromes with no zero digits\n",
" whose digits sum to n.\n",
" \"\"\"\n",
" if n == 0:\n",
" return (\"\",) # empty palindrome, useful for constructing 11, 22, ...\n",
"\n",
" result = []\n",
"\n",
" # A one-digit palindrome.\n",
" if 1 <= n <= 9:\n",
" result.append(str(n))\n",
"\n",
" # Wrap a smaller palindrome with matching digits d ... d.\n",
" # This adds 2*d to the digit sum.\n",
" for d in range(1, 10):\n",
" if 2 * d > n:\n",
" break\n",
" for middle in GPT_2_palindromes(n - 2 * d):\n",
" result.append(str(d) + middle + str(d))\n",
"\n",
" return tuple(result)"
]
},
{
"cell_type": "markdown",
"id": "71ed927d-de25-4f9d-85a7-18c60fdab010",
"metadata": {},
"source": [
"# Claude Sonnet 5's Solution"
"# Claude "
]
},
{
@@ -435,17 +478,46 @@
" return numbers"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "f3eb9970-4dbf-43c1-a3dc-235be81861ed",
"metadata": {},
"outputs": [],
"source": [
"from functools import lru_cache\n",
"\n",
"@lru_cache(maxsize=None)\n",
"def claude_2_palindromes_with_digit_sum(n):\n",
" \"\"\"Palindromic integers in base 10, with no zero digits,\n",
" whose digits add up to n, as a sorted tuple.\"\"\"\n",
" if n < 1:\n",
" return ()\n",
" result = []\n",
" if n <= 9:\n",
" result.append(n) # single digit\n",
" for d in range(1, min(9, n // 2) + 1):\n",
" rest = n - 2 * d\n",
" if rest == 0:\n",
" result.append(11 * d) # just \"dd\"\n",
" else:\n",
" for p in claude_2_palindromes_with_digit_sum(rest): # smaller subproblem\n",
" k = len(str(p))\n",
" result.append(d * 10**(k + 1) + p * 10 + d) # wrap: d p d\n",
" return tuple(sorted(result))"
]
},
{
"cell_type": "markdown",
"id": "549ea6b6-21f3-49c8-b54a-e70710edbfd5",
"metadata": {},
"source": [
"# Kimi K3's Solution"
"# Kimi "
]
},
{
"cell_type": "code",
"execution_count": 9,
"execution_count": 10,
"id": "0754167e-a8bb-45a7-80a3-8dd15645e7a2",
"metadata": {},
"outputs": [],
@@ -514,109 +586,137 @@
" 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",
"id": "18c3302d-d6c8-48fa-899f-65991473513a",
"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"
]
}
],
"outputs": [],
"source": [
"%time test(gemini_generate_palindromes)"
"from functools import cache\n",
"\n",
"\n",
"@cache\n",
"def kimi_2_palindromes_with_digit_sum(n: int) -> tuple[str, ...]:\n",
" \"\"\"\n",
" Return all palindromic strings composed of nonzero digits\n",
" whose digits sum to n.\n",
"\n",
" The empty string is included only for n == 0 so that it can be\n",
" used to construct two-digit palindromes such as 11 and 22.\n",
" \"\"\"\n",
" if n == 0:\n",
" return (\"\",)\n",
"\n",
" result = []\n",
"\n",
" # A one-digit palindrome.\n",
" if 1 <= n <= 9:\n",
" result.append(str(n))\n",
"\n",
" # Surround a smaller palindrome with equal nonzero digits.\n",
" # The digit sum increases by 2*d.\n",
" for d in range(1, 10):\n",
" if 2 * d > n:\n",
" break\n",
"\n",
" for middle in kimi_2_palindromes_with_digit_sum(n - 2 * d):\n",
" result.append(f\"{d}{middle}{d}\")\n",
"\n",
" return tuple(result)"
]
},
{
"cell_type": "markdown",
"id": "d0199f94-d8d6-4728-8dc6-11a3ee191abc",
"metadata": {},
"source": [
"# Unit Tests and Timing Results\n",
"\n",
"My function `test` runs a series of test cases for various values of *N* up to 42 (for which there are 2,007,494 valid palindromes) and returns the run time in seconds. I use the local function `f` for canonicalization: an implementation might reasonably choose to produce ints instead of strings, and might return (or yield) the palindromes in any order, not necessarily lexicographic order. If the palindrome function has a cache, it is cleared at the start. "
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "30fc1f93-9921-4512-81b8-0320208f0233",
"id": "0034dd91-13ab-498d-9e9c-9f7debfd00e7",
"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"
]
}
],
"outputs": [],
"source": [
"%time test(chatGPT_palindromes_with_digit_sum)"
"import time\n",
"\n",
"def test(palindrome_function) -> float:\n",
" \"\"\"Tests for the palindrome digit sum problem; return total run time.\"\"\"\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",
" start_time = time.perf_counter()\n",
"\n",
" # Tests for small values of N\n",
" assert f(0) == [''] or f(0) == [] # Unspecified; 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 number of palindromes.\n",
" for N, number in {38: 502_864, 40: 1_004_737, 42: 2_007_494}.items():\n",
" assert len(f(N)) == number == len(f(N + 1))\n",
"\n",
" return time.perf_counter() - start_time # Total run time"
]
},
{
"cell_type": "markdown",
"id": "d2fd7ae9-6a16-404d-aeb9-81ed52ae2a01",
"metadata": {},
"source": [
"Here we see the time each function takes to run the test suite. Note all assertions were valid for all functions."
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "ceb02172-c40c-4db8-8b6d-49e29f0d4367",
"id": "e23fcf62-dc2c-4c95-9f21-d07197d70743",
"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"
" 0.7 sec palindromes_with_digit_sum\n",
" 7.5 sec gemini_generate_palindromes\n",
"10.1 sec GPT_palindromes_with_digit_sum\n",
"16.4 sec claude_find_palindromes\n",
"14.6 sec kimi_palindromes_with_digit_sum\n",
" 2.5 sec gemini_2_zero_free_palindromes\n",
" 1.1 sec GPT_2_palindromes\n",
" 1.9 sec claude_2_palindromes_with_digit_sum\n",
" 1.0 sec kimi_2_palindromes_with_digit_sum\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)"
"functions = (palindromes_with_digit_sum,\n",
" gemini_generate_palindromes,\n",
" GPT_palindromes_with_digit_sum,\n",
" claude_find_palindromes,\n",
" kimi_palindromes_with_digit_sum,\n",
" gemini_2_zero_free_palindromes,\n",
" GPT_2_palindromes,\n",
" claude_2_palindromes_with_digit_sum,\n",
" kimi_2_palindromes_with_digit_sum)\n",
"\n",
"for fn in functions:\n",
" print(f'{test(fn):4.1f} sec {fn.__name__}')"
]
}
],