Teaser 3306: Pin-up
From The Sunday Times, 1st February 2026 [link] [link]
Over the years I have used seven different PINs. The first consisted of a four-digit number (the first digit not being zero), and thereafter each new PIN was obtained by swapping over two of the digits of the previous PIN; each time this increased the PIN.
The first PIN (not surprisingly!) was divisible by 1, the second was divisible by 2, the third by 3, and so on, with the seventh divisible by 7 (in fact the seventh PIN was the only one divisible by 7).
What was the third PIN?
[teaser3306]
Jim Randell 7:06 am on 1 February 2026 Permalink |
It is quicker (and neater) to build the list in reverse order. We can start with a final PIN that is a multiple of 7 (and 3), and then work backwards, undoing the swaps, to get back to the initial PIN.
This Python program runs in 76ms. (Internal runtime is 6.8ms).
from enigma import (irange, subsets, nsplit, nconcat, swap, printf) # generate possible swaps by index (on a 4-digit sequence) swap_inds = list(subsets(irange(4), size=2)) # solve the puzzle for a sequence of (<number>, <digit>) pairs # at each stage finding the _previous_ PIN def _solve(ss): k = len(ss) # are we done? if k == 7: yield ss else: (n, ds) = ss[0] # swap 2 digits of the following PIN for (i, j) in swap_inds: ds1 = swap(ds, i, j) # no leading zeros if ds1[0] == 0: continue n1 = nconcat(ds1) # previous PINs must be: smaller; not be divisible by 7; divisible by their position (= 7 - k) if n1 < n and n1 % 7 > 0 and n1 % (7 - k) == 0: yield from _solve([(n1, ds1)] + ss) # solve the puzzle for final PIN <n> def solve(n): # final PIN is divisible by 7 if n % 7 > 0: return # find previous PINs for ss in _solve([(n, nsplit(n))]): # return the list of numbers yield tuple(n for (n, ds) in ss) # consider the final PIN (divisible by 7 (and 3)) for n in irange.round(1000, 9999, step=21, rnd='I'): for ns in solve(n): printf("{ns}")Solution: The third PIN is 5286.
There are 2 possible sequences of PINs that differ at the second PIN:
LikeLike
Ruud 11:19 am on 1 February 2026 Permalink |
How do you know that the final PIN does not start with 0 ?
LikeLike
Jim Randell 11:47 am on 1 February 2026 Permalink |
@Ruud: The first PIN has no leading zero, so is greater than 999. And each subsequent PIN is larger than the one preceding it, so all the PINs are greater than 999, hence none of them have a leading zero.
LikeLike
ruudvanderham 11:27 am on 1 February 2026 Permalink |
Ah, I see: because it has to be greater than the first PIN, which is >=1000 .
But, how do you know that the final PIN is divisible by 3?
LikeLike
Jim Randell 11:48 am on 1 February 2026 Permalink |
@Ruud: If a number is divisible by 3 than any number formed from a rearrangement of the digits is also divisible by 3. Hence all the PINs must be divisible by 3.
So if the final PIN is divisible by both 7 and 3, then it must be divisible by 21 (= LCM(7, 3)).
LikeLike
Jim Randell 12:25 pm on 2 February 2026 Permalink |
@Ruud: Did you try this? It produces no output for me.
If you want to go in steps of 21 you need to start with the final PIN and work backwards, because the final PIN is the only one that is divisible by 7.
LikeLike
Ruud 1:10 pm on 2 February 2026 Permalink |
I thought I did, but apparently I did not. Can you remove this?
LikeLike
Ruud 9:34 am on 1 February 2026 Permalink |
import istr def solve(pins): for i0, i1 in istr.combinations(range(4), r=2): n = len(pins) + 1 pin = istr.join(pins[-1][int(i0)] if i == i1 else pins[-1][int(i1)] if i == i0 else pins[-1][i] for i in range(4)) if pin > pins[-1] and pin.is_divisible_by(n): if n == 7: yield pins + [pin] elif not pin.is_divisible_by(7): yield from solve(pins + [pin]) for pin in istr(range(1000,10000)): for pins in solve([pin]): print(pins)LikeLike
Frits 1:32 pm on 1 February 2026 Permalink |
from itertools import combinations # generate and check the k-th PIN (decreasing k) def check(s, k): if not k: yield int(''.join(s[4])) # third PIN else: # swap 2 digits for i, j in combinations(range(4), 2): # swap should result in a lower number > 999 if s[-1][i] <= s[-1][j] or (i == 0 and s[-1][j] == "0"): continue new = [s[-1][j] if y == i else s[-1][i] if y == j else s[-1][y] for y in range(4)] # the seventh PIN was the only one divisible by 7 if (n := int(''.join(new))) % k == 0 and n % 7: yield from check(s + [new], k - 1) sols = set() # first possible seventh PIN divisible by 21 (div 7, div 3) f = next(i for i in range(2001, 2024, 3) if i % 7 == 0) for p7 in range(f, 10000, 21): # div 5 case if "5" in (s := str(p7)): # we need an even digit for the div 4 case if set("02468").isdisjoint(s): continue elif "0" in s: # we need at least 2 even digits for the div 4 case if sum(int(x) % 2 for x in s) > 2: continue else: continue # we need at least 7 different permutations if len(set(s)) < 3: continue # generate all PINs and store the third PIN for p3 in check([s], 6): sols.add(p3) print(f"answer: {' or '.join(str(s) for s in sols)}")LikeLike
Ruud 5:43 am on 2 February 2026 Permalink |
ChatGPT solved it in 47 seconds:
from itertools import permutations, combinations def swaps(a, b): """Return True if b can be obtained from a by swapping exactly two digits.""" diffs = [i for i in range(4) if a[i] != b[i]] return len(diffs) == 2 and a[diffs[0]] == b[diffs[1]] and a[diffs[1]] == b[diffs[0]] def valid_chain(chain): """Check divisibility, increasing order, and swap condition.""" for i in range(7): if chain[i] % (i+1) != 0: return False if i > 0: if chain[i] <= chain[i-1]: return False if not swaps(str(chain[i-1]), str(chain[i])): return False # only the 7th is divisible by 7 return all(chain[i] % 7 != 0 for i in range(6)) solutions = [] # choose 4 distinct digits, first not zero digits = [str(d) for d in range(10)] for digs in combinations(digits, 4): for perm in permutations(digs): if perm[0] == "0": continue nums = sorted( int("".join(p)) for p in permutations(perm) if p[0] != "0" ) for chain in permutations(nums, 7): if valid_chain(chain): solutions.append(chain) # print solutions and the third PIN for sol in solutions: print(sol, "→ third PIN:", sol[2])Scary …
LikeLike
Ruud 6:10 am on 2 February 2026 Permalink |
Maybe less scary than I thought.
Although, ChatGPT gave the proper answer, it can’t have been done with this pure brute force program. It takes for ever, even for one PIN.
LikeLike
Jim Randell 3:48 pm on 3 February 2026 Permalink |
This code looks like it might solve the puzzle, although it has decided to generate all possible sequences of 7 numbers and the check to see if it can find any that work (which itself quite a slow approach). But the code it has written for doing this is terrible.
First it selects 4 digits from 10 (= C(10, 4) = 210 arrangements, and also means it is assuming that all the digits are different), then for each of these it considers all possible permutations of the digits (= P(4, 4) = 24 arrangements), discarding those that begin with a zero. It then makes a sorted list of all arrangements of these digits that do not have a leading zero (up to P(4, 4) = 24 arrangements), and then considers all possible 7 digit permutations of this list (not combinations so they are already correctly sorted), which accounts for up to 1,744,364,160 checks for each of the possible 210 × 24 = 5,040 sequences of digits. So in total it is trying about 8.7 × 10^12 possible sequences, including many repeated checks.
It is no wonder it does not run in a reasonable time. For quite a straightforward problem ChatGPT has made a real mess of it.
As Samuel Johnson might say, “[it] is like a dog walking on his hind legs. It is not done well; but you are surprised to find it done at all”.
In my experience ChatGPT is good at producing something that looks plausible. So you need to know enough about what you are asking it, so that you can determine if the answer is actually reasonable. And this case, it is a massive failure.
The really scary thing is that some people treat its responses as though it knows what it is talking about.
LikeLiked by 1 person
Ruud 7:06 am on 2 February 2026 Permalink |
I asked ChatGPT again (mentioning that it didn’t finish in reosonable time). And now it comes with a solution. Amazing …
from collections import defaultdict SWAP_PAIRS = [(0,1), (0,2), (0,3), (1,2), (1,3), (2,3)] def swap_digits(s, i, j): if i == j: return s lst = list(s) lst[i], lst[j] = lst[j], lst[i] return "".join(lst) def build_neighbors(): """ neighbors[n] = sorted list of all valid 4-digit numbers obtainable by swapping two digits of n (leading digit cannot become 0). """ neighbors = [[] for _ in range(10000)] for n in range(1000, 10000): s = f"{n:04d}" seen = set() for i, j in SWAP_PAIRS: t = swap_digits(s, i, j) if t[0] == "0": continue m = int(t) if m != n and m not in seen: seen.add(m) neighbors[n] = sorted(seen) return neighbors def find_chains(): neighbors = build_neighbors() # Precompute which numbers satisfy divisibility by k (k=1..7) div_ok = {k: [False]*10000 for k in range(1, 8)} for n in range(1000, 10000): for k in range(1, 8): div_ok[k][n] = (n % k == 0) solutions = [] def dfs(chain): step = len(chain) + 1 # next step index (1..7) if step == 8: solutions.append(chain[:]) return prev = chain[-1] for nxt in neighbors[prev]: if nxt <= prev: continue if not div_ok[step][nxt]: continue # only the 7th is divisible by 7 if step < 7 and nxt % 7 == 0: continue dfs(chain + [nxt]) # Step 1: any 4-digit number works for divisibility by 1, # but it must allow an increasing chain of swaps. # Still, searching all 9000 starts is fine with pruning. for start in range(1000, 10000): # (Optional) tiny prune: if start has no larger neighbors, skip if not neighbors[start] or neighbors[start][-1] <= start: continue dfs([start]) return solutions if __name__ == "__main__": sols = find_chains() print(f"Found {len(sols)} solution chain(s).") for ch in sols: print(" -> ".join(map(str, ch)), " | third PIN:", ch[2])LikeLike
Ruud 4:26 pm on 3 February 2026 Permalink |
I fully agree with you. But when told ChatGPT that this code does not run in reasonable time, it immediately came up with a way better solution (see my reply). And I find that amazing.
LikeLike
GeoffR 10:09 am on 4 February 2026 Permalink |
Interesting reply from ChatGPT, after I put to it that it was not very efficient at coding teasers.
It looks as though the problem is recognised and things may improve in the future. Quite a lot of detailed comments – things can only get better!
https://chatgpt.com/share/698313ad-02fc-8000-ab4c-2a3873afb266
LikeLike