Here is a game a friend taught me on the drive to university, that gave me many hours of entertainment. To illustrate it, let me lay out some definitions:
A digit string is an ordered list of numbers (integers from 0 to 9 in our case), such as 15397. This is not to be interpreted as fifteen thousand three hundred and ninety seven, but as one followed by five, three, nine, and seven.
\(1\times (5-3)+9\div 7\) is an expression of the digit string 15397. Between the digits we place some operation from addition, subtraction, multiplication and division. We are allowed to use an operation as many times as we like (including not at all), to use as many parentheses as we like, but we cannot reorder the digits and every two digits must have an operation between them.
A solution of the digit string is some expression that evaluates to zero. \(9-9\), \(1+2-3\), \(8-2\times 4\) and \(2-6\div (1+2)\) are solutions of 99, 123, 824 and 2612 respectively. Clearly any digit string which contains a zero can be solved using only multiplications. Eg. \(5\times0\times3\) is a solution of 503.\(1\times (5-3)+9\div 7\) is clearly not a solution to 15397, but can you find one? \((1+5+3-9)\times7\) Can you solve 194878? No. You can't. It is impossible.
Given some source of digit strings, the game is to be the first to find a solution. For example, Irish vehicle registration plates (numberplates) take the form 251-WX-13946, so you can use the last section of digits. Can you solve this one? I'll show you later ;) On the drive to university, my friend and I would play the game on the other cars around us. I like to think I won slightly more often (he was driving).
Now that you know the game, let me introduce the very interesting question:
Just kidding, the answer to this is clearly infinitely many. We can solve 11, by using the expression 1-1. Then we can solve any expression containing 11. For example, $abc11xyz$ is solved by $a\times b\times c\times (1-1)\times x\times y\times z$. This is an infinite family of digit strings which have a solution. Among them are 11, 111, 1111, 11111, 111111, 1111111 etc.
This is a far less trivial question. Clearly, as we consider longer and longer strings, it becomes harder for them to not have a solution. Indeed, it can be easily shown that the fraction of digit strings of length $L$ which are not solvable tends to 0 as $L$ grows.
It is not obvious, however, if there are finitely many or infinitely many unsolvables. To illustrate this a little, let me show you a similar property. A square is a digit string of the form $'XX'$ for some string $'X'$. For example, $11$, $2323$ and $97349734$ are squares, but $23$ and $223$ are not. A string is called square-free if none of its substrings are a square. For example, 1, 23531 and 976975 are square-free, but $223$ and $976769$ are not. Similar to the property we are interested in, as $L$ grows, square-free strings of length $L$ become exceedingly rare. However, it is known that there are infinitely many square-free strings (as long as you allow more than 2 characters). To learn more about square-free numbers, see this Wikipedia article, this OEIS page and links therein.
After some thinking though, I can assure you that there are finitely many. If the length of a digit string is 20 or more, then I guarantee it is solvable.
Coming soon. Actually is not essential though, given the 'When have we found them all?' section, because if our program terminates, we have found them all, and so there are finitely many.
With this knowledge, we know that we need only check numbers up to $10^{20}$. This is a huge ceiling, which is not too helpful. But, in principle this is a finite search space. Also, we will be able to tell if we finish early.
For each $L>0$, $U(L)$ denotes the number of digit strings of length $L$ that are not solvable. The idea is we calculate $U(1)$, then $U(2), U(3)$ and so on.
This means that any digit string of length $L$ has a solution. Take any digit string of length $>L$. It has a substring which has length $L$, for example its first $L$ digits. But this substring has a solution, and so the string is solvable. Thus no string of length $>L$ is solvable, so we are finished.
Thus if we, for each successive length, count how many strings of that length cannot be solved, we can stop searching once we reach a length for which the counter is zero. We haved proved above this will happen no later than length 20, but hopefully sooner! Also, there is another way we may stop...
This means that only one digit string of length $L$ cannot be solved. Take any digit string of length $>L$. It has (at least) two substrings of length $L$: the ones starting at the first digit and the second digit. There are two possibilities: both them are the unsolvable of length $L$, or otherwise. If otherwise, then at least one of them is not that unsolvable, so has a solution. Then the string would have a solvable substring, so is solvable itself. But if both of them are the unsolvable of length $L$, then their first digits match. Then the first two digits of the string are equal, so the string is solvable. Thus no string of length $>L$ is solvable and we are done.
Together, these facts justify trying a brute force approach — just check every digit string!
If you are skimming this page, you can skip this section. All you need to know is that I have a python script to produce the expressions, and I know roughly how many there will be.
The goal of this section is to find all of the expressions for a string of length $L$. We denote the number of these by $E(L)$, and I am curious how fast this grows.... Let's start by listing out some small expressions.
We denote a string of length $L$ as being the first $L$ letters of the alphabet. Clearly $E(1)=1$ as $a$ is the only expression of the string $a$. There is no space to place an operation!
Here are the $E(2)=4$ of a string of length 2:
$$ a+b, a-b, a\times b, a\div b$$And here are the $E(3)=24$ of a string of length 3. Check that you agree.
$$\begin{align} &a+b+c &&a+b-c &&&a+b\times c &&&&a+b\div c \\ &a-b+c &&a-b-c &&&a-b\times c &&&&a-b\div c \\ &a\times b+c &&a\times b-c &&&a\times b\times c &&&&a\times b\div c \\ &a\div b+c &&a\div b-c &&&a\div b\times c &&&&a\div b\div c \\ &a\times (b+c) &&a\times (b-c) &&&(a+ b)\times c &&&&(a- b)\times c \\ &a\div (b+c) &&a\div (b-c) &&&(a+b)\div c &&&&(a- b)\div c \end{align} $$
However we see there is a lot of redundancy in this listing. Everytime $+$ appears, there is another string that is identical except that $+$ is replaced by $-$, and vice verasa. The same is true for the pair $\times, \div$. Removing all of the expressions with $-$ or $\div$, we cut down the list by a factor of $2^{L-1}$, a factor of two for each of the $L-1$ operations. So, we can write the above lists more compactly as:
$$\begin{align} &L=1 &&L=2 &&&L=3 \\ &a &&a+b &&&a+b+c \\ & &&a\times b &&&a+b\times c \\ & && &&&a\times b+ c \\ & && &&&(a+b)\times c \\ & && &&&a\times (b+ c) \\ & && &&&a\times b\times c \end{align}$$
You can get back the original lists by including the copies of these with various numbers of $+$ and $\times$ replaced with $-$ and $\div$. Using just $+$ and $\times$ then, how many expressions can we make? I find 22, see that you agree. Do not worry about the column titles, they are explained later when I show you the procedure.
$$\begin{align} &\text{Split after 1}, + &&\text{Split after 1}, \times &&&\text{Split after 2} &&&& \text{Split after 3} \\ &a+b+c+d &&a\times(b+c+d) &&&a\times b+c+d &&&&a\times b\times c+d \\ &a+b+c\times d &&a\times(b+c\times d) &&&a\times b+c\times d &&&&a\times(b+c)+d \\ &a+b\times c+d &&a\times(b\times c+d) &&&(a+b)\times(c+d) &&&&(a+b)\times c+d \\ &a+b\times c\times d &&a\times b\times c\times d &&&(a+b)\times c\times d &&&&(a+b+c)\times d \\ &a+b\times(c+d) &&a\times b\times (c+d) &&& &&&&(a+b\times c)\times d \\ &a+(b+c)\times d &&a\times(b+c)\times d &&& &&&&(a\times b+c)\times d \end{align}$$
This means that $E(4)=2^3 \times 22 = 176$. Good thing I didn't write them all out...
When constructing expressions, we need to know what we need should wrap parentheses around. For example $a+b\times c$ is different to $(a+b)\times c$, but the same as $a+(b\times c)$.
An expression is called free if there is an addition or subtraction not within parentheses. Here are some free expressions:
$$a+b+c \qquad a+b\times c \qquad a\times b+c$$If a string is not free, we say it is bound. Of the expressions discussed already, the following are bound.
$$a\times(b+c) \qquad (a+b)\times c \qquad a\times b\times c$$The number of free and bound strings of length $L$ are denoted by $F(L)$ and $B(L)$ respectively. Looking at the previous lists of expressions, check that you agree $F(1)=0, F(2)=2, F(3)=12, F(4)=88$ and $B(1)=0, B(2)=2, B(3)=12, B(4)=88$.
At this point we might conjecture that $F(L)=B(L)$ for every $L>1$. This is proved later :)
coming soon. For now, you can read the python implementation.
We aim to implement the above strategy in Python to find the expressions of length $L$. To do this, we need to know them for length less than $L$. The only expression of length 1 is bound, and we will represent it using a blank symbol, such as a spcae or an underscore, to signify this is where the digit should be placed.
# initialise
blank = ' '
free_exprs = [[], []]
bound_exprs = [[], [blank]]
Then we use the procedure designed above to find the free and bound expressions of length $L$. These will need to be appended to 'free_exprs' and 'bound_exprs' respectively to help find the expressions of length $L+1$ and so on. Because it is called only a handful of times, it does not need to be hyper-optimised, so I write them in these 'extend' blocks for readability.
def find_next_exprs(
free_exprs, bound_exprs,
ops_add, ops_mult):
L = len(free_exprs)
new_free_exprs = []
new_bound_exprs = []
# split after first digit
new_free_exprs.extend(
f"{blank}{op}{right}"
for op in ops_add
for right in free_exprs[-1]
)
new_free_exprs.extend(
f"{blank}{op}{right}"
for op in ops_add
for right in bound_exprs[-1]
)
new_bound_exprs.extend(
f"{blank}{op}({right})"
for op in ops_mult
for right in free_exprs[-1]
)
new_bound_exprs.extend(
f"{blank}{op}{right}"
for op in ops_mult
for right in bound_exprs[-1]
)
for i in range(2, L):
# split after i'th digit
new_free_exprs.extend(
f"{left}{op}{right}"
for left in bound_exprs[i]
for op in ops_add
for right in free_exprs[L-i]
)
new_free_exprs.extend(
f"{left}{op}{right}"
for left in bound_exprs[i]
for op in ops_add
for right in bound_exprs[L-i]
)
new_bound_exprs.extend(
f"({left}){op}({right})"
for left in free_exprs[i]
for op in ops_mult
for right in free_exprs[L-i]
)
new_bound_exprs.extend(
f"({left}){op}{right}"
for left in free_exprs[i]
for op in ops_mult
for right in bound_exprs[L-i]
)
return new_free_exprs, new_bound_exprs
Using a small program as follows, we can verify that it gives the correct expressions.
def print_exprs(ops_add, ops_mult, L_max):
free_exprs = [[], []]
bound_exprs = [[], [blank]]
for L in range(2, L_max):
new_free_exprs, new_bound_exprs = find_next_exprs(
free_exprs, bound_exprs, ops_add, ops_mult)
free_exprs.append(new_free_exprs)
bound_exprs.append(new_bound_exprs)
a = len(new_free_exprs)
b = len(new_bound_exprs)
print()
print(f"Free: F({L})={a}. Bound: B({L})={b}. Total: E({L})={a+b}")
print(new_free_exprs)
print(new_bound_exprs)
Using ops_add='+-'
,
ops_mult='*/'
and
L_max=4
, we can get the expressions of length 2 and 3. To make them easier
to read, we use
blank='a'
.
>>> print_exprs('+-', '*/', 4) Free: F(2)=2. Bound: B(2)=2. Total: E(2)=4 ['a+a', 'a-a'] ['a*a', 'a/a'] Free: F(3)=12. Bound: B(3)=12. Total: E(3)=24 ['a+a+a', 'a+a-a', 'a-a+a', 'a-a-a', 'a+a*a', 'a+a/a', 'a-a*a', 'a-a/a', 'a*a+a', 'a*a-a', 'a/a+a', 'a/a-a'] ['a*(a+a)', 'a*(a-a)', 'a/(a+a)', 'a/(a-a)', 'a*a*a', 'a*a/a', 'a/a*a', 'a/a/a', '(a+a)*a', '(a+a)/a', '(a-a)*a', '(a-a)/a']
We can also check all of the expressions of length 4 using only
addition and multiplication, using
ops_add='+'
,
ops_mult='*'
and
L_max=5
.
Free: F(2)=1. Bound: B(2)=1. Total: E(2)=2 ['a+a'] ['a*a'] Free: F(3)=3. Bound: B(3)=3. Total: E(3)=6 ['a+a+a', 'a+a*a', 'a*a+a'] ['a*(a+a)', 'a*a*a', '(a+a)*a'] Free: F(4)=11. Bound: B(4)=11. Total: E(4)=22 ['a+a+a+a', 'a+a+a*a', 'a+a*a+a', 'a+a*(a+a)', 'a+a*a*a', 'a+(a+a)*a', 'a*a+a+a', 'a*a+a*a', 'a*(a+a)+a', 'a*a*a+a', '(a+a)*a+a'] ['a*(a+a+a)', 'a*(a+a*a)', 'a*(a*a+a)', 'a*a*(a+a)', 'a*a*a*a', 'a*(a+a)*a', '(a+a)*(a+a)', '(a+a)*a*a', '(a+a+a)*a', '(a+a*a)*a', '(a*a+a)*a']
Looks like we are working perfectly :)
To get an idea, let us run the following code. It is nothing special, just has some pretty print statements thrown in.
def print_num_of_exprs(ops_add, ops_mult, L_max):
print(f"{'Length':<{6}}"
f"{'Free':>{10}}"
f"{'Bound':>{10}}"
f"{'Total':>{10}}"
)
print("-" * 36)
print(f"{1:>6}"
f"{0:>10}"
f"{1:>10}"
f"{1:>10}"
)
free_exprs = [[], []]
bound_exprs = [[], [blank]]
for L in range(2, L_max):
new_free_exprs, new_bound_exprs = find_next_exprs(
free_exprs, bound_exprs, ops_add, ops_mult)
free_exprs.append(new_free_exprs)
bound_exprs.append(new_bound_exprs)
a = len(new_free_exprs)
b = len(new_bound_exprs)
print(f"{L:>6}"
f"{a:>10}"
f"{b:>10}"
f"{a+b:>10}"
)
Running this, we find there are... dangerously many. Quite a few, even. If we need to use the expressions of length 10, it will take forever, there are 100 billion of them! And God help us if we need the longer lengths, I don't even have the memory to store them!
>>> print_num_of_exprs('+-', '*/'', 12) Length Free Bound Total ------------------------------------ 2 2 2 4 3 12 12 24 4 88 88 176 5 720 720 1440 6 6304 6304 12608 7 57792 57792 115584 8 547712 547712 1095424 9 5323008 5323008 10646016 10 52761088 52761088 105522176 MemoryError
Searching for this last column on the OEIS suggests that $E(L) =$ A156017$(L-1)$. I later prove this to be the case, from which it follows that
$$ E(L) \sim \frac{\sqrt{3\sqrt{2}-4}}{4\sqrt{\pi}} \frac{\left(6+4\sqrt{2}\right)^L}{L^{3/2}}, $$in the sense that $f(n)\sim g(n) \iff \lim\limits_{n\to\infty} \frac{f(n)}{g(n)} = 1$. From this it is immediate that
$$ \lim\limits_{L\to\infty} \frac{E(L+1)}{E(L)} = 6+4\sqrt{2}$$which is to say that each time we increase the length by one, the number of expressions grows by about a factor of $6+4\sqrt{2} \approx 11.65685$. This grows even faster than the number of digit strings of length $L$! (assuming we allow less than 12 digits.)
Coming soon :)
A quick search for 'Numberplate Game' finds this blog post, where for each number up to 10,000, the author checks how many expressions are solutions. He finds that only 20% of these numbers are unsolvable. (I think there is a small difference to the rules of the game he is playing, but it is in essence the same problem.)
I am only interested in whether the string is solvable, so we can go faster! We will first generate all of the expressions, then for each number, iterate through these expressions until we hit a zero, or run out. This should be a dramatic improvement, as we saw there is a god-awful number of possible expressions.
Here is the rough blueprint of our method. There are a few things to note. The expressions can be made using the procedure of Chapter 0. We do not even consider digit strings that contain a zero, as they are trivially solvable. For each steo in the while loop, we increment the length by one and are counting the number of arrays of this that do not have a solution. As we proved earlier if this count is either 0 or 1 we can stop.
import itertools
base = 10
ops_mult='+-'
ops_mult='*/'
digits = list(range(1, base))
length = 1
count = base-1
total = count
while count > 1:
length += 1
# find expressions with new length
count = 0
for array in itertools.product(digits, repeat=length):
if has_no_solution(array):
count += 1
total += count
Imagine we are trying to check if $7123$ is solvable, and we try the expression $7\div(1+2-3)$. Our poor python script would not be happy, that's for sure.
>>> print(7/(1+2-3))
ZeroDivisionError: division by zero
This problem can easily be avoided, though. If the operation '$a\div b$' raises this error, it is because $b=0$. Clearly then $a\times b=0$. So, if we were to first try $7\times (1+2-3)$, this would give zero. We would stop trying expressions, and so would never try $7\div (1+2-3)$.
So, imagine if we sort the expressions by the number of divisions. Then, we would never try $a\div b$ without first verifying $a\times b$ was non-zero, which means $b$ is non-zero and $a\div b$ is not problematic. The cost to sorting the strings like this is not relatively large, as it needs to be done only once, when the list of expressions is generated.
The string 13946 is particularly annoying to work with, but serves as a good example. It can be solved by $1-3\div9-4\div6$. (In fact, this is the only solution. It not only needs a division to be solved, it actually needs 2!) You can see this is a solution, right? Well, Python can't...
>>> print(1-3/9-4/6)
1.1102230246251565e-16
We must try to avoid these floating point errors somehow. Consider working with pairs of integers $(a,b)$, for $b>0$ and $\text{gcd}(a,b)=1$. In this language, $(a,b)$ is understood as $a/b$. Then $(a,1)$ would be the integer $a$, and $(0,b)$ would be 0. These are the rationals $\mathbb{Q}$, which are closed under the four operations were are using. The operations can then be written purely in terms of integer arithmetic:
$$\begin{aligned} (a,b)+(c,d) &= (a\times d+b\times c, b\times d) \\ (a,b)-(c,d) &= (a\times d-b\times c, b\times d) \\ (a,b)\times (c,d) &= (a\times c, b\times d) \\ (a,b)\div(c,d) &= (a\times d, b\times c) \end{aligned}$$(At the end, remove common factors from the numerator and denominator.) Of course, I'm not actually bothered to code all of that up myself. There is a Python module called 'fractions' which does this for us. You can read about it here. '$\text{Fraction}(a,b)$' is an object that behaves just like $(a,b)$ above. We never need to worry about floating point errors again!
Two small comments on this. Firstly, yes, the expressions now take longer to compute. An addition now requires 3 multiplications, an addition, and a gcd calculation. But we need the accuracy this provides. Luckily, we already agreed to sort the expressions by how many divisions they have. So, we can use integer arithmetic on the first group, those with no divisions. Then, use the fractions approach only for the rest. This should salvage back some time.
Secondly, the floating point error problem could be avoided altogether by carefully redefining the game is such a way that at every step in the evaluation of the expression, all values must be integers. For example, you could consider 13946 to be unsolvable, because in the steops of calculating $1-3\div9-4\div6$ you have some non-integers $1/3$ and $2/3$. This is easy to do when actually playing in person. It is not so easy, and certainly would be slow, to ask the program to ensure we have an integer every time we divide. You are welcome to try that yourself :)
Using everything we have talked about, let's put this into action. First, when we generate the new expressions, we need to split them up into those with divisions, and those without. Also, we must order the ones with divisions by how many divisions they have. This can be done with the following function.
def order_exprs_by_divs(exprs, length):
"""
Given a list of expressions, and their common length,
returns two arrays. First, those without division.
Second, those with division, sorted by how many divisions are used.
"""
exprs_wo_div = []
temp = [[] for _ in range(length)]
for expr in exprs:
div_count = expr.count('/')
if div_count == 0:
exprs_wo_div.append(expr)
else:
temp[div_count].append(expr)
exprs_w_div = sum(temp, [])
return exprs_wo_div, exprs_w_div
We will need to evaluate these expressions over each array of values.
To prepare for this, we replace the blank character in a smart way.
What I have chosen to do is replace the i'th appearance of the balnk
character with 'x[i]', starting with i=0. Then, we can evaluate the
string by just passing in an array of values called x. For example,
using a as the blank character, the expression
'a*(a+a)'
would then
become
'x[0]*(x[1]+x[2])'
. Using the array of values
x=[1,2,3]
, the expression would give 1*(2+3)=5. Precompiling the expressions
in advance like this also is just way faster than doing each time.
def precompile_exprs(exprs, indices):
"""
Precompiles the expressions.
Replaces blank with 'x[{}]', then '{}' with the integers 0,1,... from indices.
Must use eval(expr, {}, {x:values}) to evaluate.
"""
return [compile(expr.replace(blank, 'x[{}]').format(*indices), 'string', 'eval')
for expr in exprs]
Next, we need to actually evaluate them when given a digit string, and stop if we hit a zero. We do integer arithmetic on the ones without divisions, and rational arithemtic on the rest. That way, if we find a zero without using a division, we save ourselves from needing to use rational arithmetic.
from fractions import Fraction
def has_no_solution(array, exprs_wo_div, exprs_w_div):
"""
False if any expression evaluates to 0, True otherwise.
Checks expressions in order of increasing
number of divisions
"""
namespace = {'x': array}
for expr in exprs_wo_div:
if eval(expr, {}, namespace) == 0:
return False
namespace = {'x': [Fraction(v) for v in array]}
for expr in exprs_w_div:
if eval(expr, {}, namespace) == 0:
return False
return True
With these, out blueprint now looks like this. All that is needed is some print statements to show the count at the end of each loop, but I'll let you design your own.
base = 10
ops_add = '+-'
ops_mult = '*/'
# initial set up
start_message(base)
start_time = time.time()
digits = list(range(1, base))
length = 1
count = base-1
total = count
count_per_length = [0,count]
while count > 1:
length += 1
F, B = find_next_exprs(free_exprs, bound_exprs, ops_add, ops_mult)
free_exprs.append(F)
bound_exprs.append(B)
exprs_wo_div, exprs_w_div = order_exprs_by_divs(F+B, length)
exprs_wo_div = precompile_exprs(exprs_wo_div, list(range(length)))
exprs_w_div = precompile_exprs(exprs_w_div, list(range(length)))
count = 0
for array in itertools.product(digits, repeat=length):
if has_no_solution(array, exprs_wo_div, exprs_w_div):
count += 1
count_per_length.append(count)
total += count
print(count_per_length)
Running the python program built above, we find that it terminates at length 7. There are exactly 3140 digit strings which have no solution, the longest of them being 8985898 — the only unsolvable of length 7.
Length Count Total Time(s) ----------------------------------------- 1 9 9 0.00100 2 72 81 0.00299 3 455 536 0.07879 4 1500 2036 1.98668 5 1014 3050 20.64676 6 89 3139 108.63134 7 1 3140 1236.94141 -----------------------------------------
It does take a while to get there though. All but 90 of them were found in 20 seconds, but we did not finish for another 20 minutes! This is what we get for using brute force... Also, I am sure you can imagine the original versions of this code took hours to run.
So that's it, we have solved the problem! Now we ask, has anyone done this before? If anyone else had, they would probably have put some evidence of it online. For example, the only unsolvable of length 7 is 8985898. Looking this up on the Online Encyclopedia of Integer Sequences (OEIS), we find that 8985898 appears in exactly one sequence, A288350$(n)$, which is 1, 12, 124, 1251, 12721, 169896, 8985898. Its title reads "Lexically smallest string of $n$ digits from 1...9, such that no formula using the single digits in the given order exists that evaluates to 0." This is exactly the problem we were solving, and it was solved at least as far back as 2017. This saves me the hassle of having to write you with a list of all of the unsolvable strings, as they already exist.
So, how many unsolvables are there:
with 1 digit? Clearly 9, which are 1,2,3,4,5,6,7,8,9.
with 2 digits? Clearly 72, which are '$ab$' for $a,b\in\{1,2,3,4,5,6,7,8,9\}$ with $a\neq b$.
with 3 digits? See A288353, there are 455.
with 4 digits? See A288354, there are 1500.
with 5 digits? See A288355, there are 1014.
with 6 digits? See A288356, there are 89.
with 7 digits? Only 1, which is 8985898.
with >7 digits? None!
in total? 3140.
The number for each length $L$>0 is given by A288351$(L)$, which is 9, 72, 455, 1500, 1014, 89, 1, and 0 thereafter.
Funny story: before I realised I had to be careful with division, floating point errors caused my code to think 13946 was unsolvable. (Recall the only solution to this string is 1-3/9-4/6.) This made the 'total' 3141, and I remember being just a little panicked that $\pi$ seemed to show up...
So there we go. There are 3140 that cannot be solved. So that's the end right? No.
Coming soon
For one thing, I'm a masochist with an addiction to optimisation. The more important reason, is why are we using the digits $0,\cdots, 9$? The game can of course be played for digits $0, \cdots, b-1$ for any other integer $b>1$. What does the answer look like then? You might notice, that I always had a variable called 'base'...