Problem 32 — Pandigital Products

solved Mon, 11 Nov 2019, 23:27 · level 1 · difficulty 5% projecteuler.net github

We shall say that an $n$-digit number is pandigital if it makes use of all the digits $1$ to $n$ exactly once; for example, the $5$-digit number, $15234$, is $1$ through $5$ pandigital.

The product $7254$ is unusual, as the identity, $39 \times 186 = 7254$, containing multiplicand, multiplier, and product is $1$ through $9$ pandigital.

Find the sum of all products whose multiplicand/multiplier/product identity can be written as a $1$ through $9$ pandigital.

HINT: Some products can be obtained in more than one way so be sure to only include it once in your sum.

Problem Analysis

We are asked to find every multiplication identity a * b = c whose three numbers, written side by side, use each digit from 1 to 9 exactly once - a property called being pandigital - and then to sum the distinct products. A naive search over wide ranges of a and b would waste enormous effort, so the first thing I did was reason about digit counts to shrink the search space before writing any enumeration code.

The pivotal observation is a digit-count identity. The concatenation of a, b, and c must be exactly 9 characters long, so the digit-lengths of the three numbers sum to 9. Multiplication, in turn, almost preserves additivity of length: the product of an m-digit number and an n-digit number has either m+n-1 or m+n digits. This follows directly from logarithms, since the digit-count of x is roughly log10(x) + 1 and logarithms turn products into sums. Combining "lengths sum to 9" with "product length is about the sum of operand lengths" leaves only two consistent shapes for (len(a), len(b), len(c)): (1, 4, 4) and (2, 3, 4). Every other arrangement - a 3x3 product, a 1x3 product, and so on - fails this arithmetic and can be discarded without ever computing a product. This combinatorial pruning step is the heart of the solution.

With the two splits fixed, I enumerate ordered selections of digits for a and then for b from the digits a did not use, compute c = a * b, and verify pandigitality. The number of candidates is a small fixed constant (on the order of a couple of thousand), so the runtime is O(1) - it does not grow with any input, because the input is fixed at the digits 1 through 9.

Solution Approach

Constrained Permutation Enumeration with Digit-Split Filtering (Python and C)

Both the Python and C solutions implement the same algorithm; they differ only in how they generate the permutations. The steps are:

  • Fix the two valid splits (a_len, b_len) as (1, 4) and (2, 3). In each, the product has 4 digits, giving 1+4+4=9 or 2+3+4=9.
  • For each split, generate every ordered selection of a_len digits from {1,...,9} to form a, then every ordered selection of b_len digits from the unused digits to form b.
  • Compute c = a * b and check that the concatenation of all three is 1-through-9 pandigital. Because a and b already draw distinct digits from the pool, this check effectively verifies that c supplies exactly the remaining digits.
  • Insert each valid product into a set so a product reachable through more than one (a, b) pair is counted only once, then sum the set.

The Python version expresses the whole search as a single set comprehension. It loops over the two splits, uses itertools.permutations(nine_digits, a_len) for a, and feeds a generator (d for d in nine_digits if d not in a) into a second itertools.permutations(..., b_len) call so that b draws only from unused digits. The walrus operator (:=) assigns a_str, b_str, and c inside the filter condition so they are computed once and reused. Wrapping the generator in set() deduplicates, and sum() finishes the job. The helper is_nine_pandigital confirms both that the decimal string is 9 characters long and that its character set equals {1,...,9}.

The C version reproduces the same logic with explicit nested loops, since there is no permutation library to lean on. For the (1, 4) split it builds a rem[] array of the eight digits not used by a, then runs four nested loops with collision guards (if (q1 == q0) continue) to enumerate the ordered 4-selections. The (2, 3) split uses two outer loops for a and three inner loops for b. The pandigital test formats the three integers into a buffer with snprintf, rejects immediately if the combined length is not 9, then marks each digit in a seen[10] array, rejecting any repeat or any zero. Uniqueness is enforced by set_add, which linearly scans the small products[] array before inserting.

workbench

Test cases

categoryinputanswer
main 45228

Results

solutioncategoryanswerverdict avg seconds cavg seconds py
p0032_s0 main 45228 correct 0.002180 (63) 0.013388 (63)

Files

Tags & blogs