# Make 28 with the numbers 2020

Try to make 28 from the numbers 2020.
Allowed operations: +, -, x, ÷, ! (factorial), exponentiation, square root, squaring, parentheses.

• Just curious, is there a significance to you choosing "28"? Jan 2 '20 at 22:13

Here's an attempt, since you allow squaring as an operation:

(2²)! + 0! + 2 + 0! = 24 + 1 + 2 + 1 = 28

• u used 3 times 2 , u must use just 2 times , and 0 too Jan 2 '20 at 11:15
• No, the small 2 is the 'squaring' operation. Jan 2 '20 at 11:16
• Oh yeah like square root , thx :3 Jan 2 '20 at 11:27
• A similar point to you as I just made to Dmitry: swap the order of your second and third terms and you keep all 4 digits in their original ordering too, for a more satisfyingly perfect solution :)
– Stiv
Jan 2 '20 at 13:58
• @Stiv thanks; I wouldn't mind if you did such an edit yourself. (But I understand other users might mind.) Jan 2 '20 at 14:03

Based on my previous answer of a similar question, I created a Python program to brute-force all the possible solutions.

To calculate the number of possible solutions:

1. We have $$5$$ possible parsing trees:

2. We have $$6$$ possible permutations of numbers: $$0022$$, $$0202$$, $$0220$$, $$2002$$, $$2020$$ and $$2200$$.

3. Each leaf node may present the plain number or it applied with one of the following unary operations: unary minus, factorial, square or square root. This is $$5$$ possibilities for each leaf node, so $$5^4 = 625$$.

4. Each interior node may present addition, subtraction, multiplication, division or exponentiation, possibly with one of the unary operations. This is $$5^2 = 25$$ possibilities for each interior node. Since we have $$3$$ interior nodes, it is $$25^3 = 15625$$.

5. So the total of possible parse trees are $$5 \times 6 \times 625 \times 15625 = 292,968,750$$.

That is a big number, but not too big for a computer with some time.

However, there is two caveats:

1. I had to prune a few problematic cases in my program to make it don't freeze trying to calculate a few very large numbers that shows up. Namely, anything relying on a factorial or an exponent larger than 100 is pruned. I think that it is extremely unlikely that 28 will ever be the eventual answer when any of those large numbers shows up.

2. Factorials, square roots, squares and unary minus might be applied multiple times to the same operand. However, my program applies them at most once for each node in the three. I will leave those out for now, because the program already generate a large number of results and already takes a lot of time with that. In fact the number of possible parsing trees is infinite because you can always insert another unary operator (although pruning problematic cases is certainly possible). Adding those possibilities would only make the number of possible solutions go higher (possibly exponentially).

Here is the code:

from dataclasses import dataclass
from enum import Enum
from typing import Callable, Dict, Generic, List, TypeVar, Union

number = Union[int, float]

class Op:
def op(self) -> number:
raise Exception("Should override")

def __str__(self):
return "Junk"

class Num(Op):
def __init__(self, a: number) -> None:
self.__a = a

def op(self) -> number:
return self.__a

def __str__(self):
return str(self.__a)

# Not currently used. But I'll left it here if you want to play with it.
class Concat(Op):
def __init__(self, a: Op, b: Op) -> None:
self.__a = a
self.__b = b

def op(self) -> number:
a: number = self.__a.op()
b: number = self.__b.op()
if int(a) == float(a): a = int(a)
if int(b) == float(b): b = int(b)
x: str = str(a) + str(b)
try:
return int(x)
except Exception:
return float(x)

def __str__(self):
return f"({self.__a} c {self.__b})"

def __init__(self, a: Op, b: Op) -> None:
self.__a = a
self.__b = b

def op(self) -> number:
return self.__a.op() + self.__b.op()

def __str__(self):
return f"({self.__a} + {self.__b})"

class Sub(Op):
def __init__(self, a: Op, b: Op) -> None:
self.__a = a
self.__b = b

def op(self) -> number:
return self.__a.op() - self.__b.op()

def __str__(self):
return f"({self.__a} - {self.__b})"

class Times(Op):
def __init__(self, a: Op, b: Op) -> None:
self.__a = a
self.__b = b

def op(self) -> number:
return self.__a.op() * self.__b.op()

def __str__(self):
return f"({self.__a} * {self.__b})"

class Div(Op):
def __init__(self, a: Op, b: Op) -> None:
self.__a = a
self.__b = b

def op(self) -> number:
return self.__a.op() / self.__b.op()

def __str__(self):
return f"({self.__a} / {self.__b})"

class Pow(Op):
def __init__(self, a: Op, b: Op) -> None:
self.__a = a
self.__b = b

def op(self) -> number:
e: number = self.__b.op()
if e > 100: raise ValueError('Too large')
return self.__a.op() ** self.__b.op()

def __str__(self):
return f"({self.__a} ^ {self.__b})"

class UnaryMinus(Op):
def __init__(self, a: Op) -> None:
self.__a = a

def op(self) -> number:
return -self.__a.op()

def __str__(self):
return f"-{self.__a}"

class Square(Op):
def __init__(self, a: Op) -> None:
self.__a = a

def op(self) -> number:
return self.__a.op() ** 2

def __str__(self):
return f"{self.__a}²"

class SquareRoot(Op):
def __init__(self, a: Op) -> None:
self.__a = a

def op(self) -> number:
return self.__a.op() ** (1 / 2)

def __str__(self):
x: str = f"{self.__a}"
if x[0] == '(' and x[-1] == ')': return f"sqrt{x}"
return f"sqrt({x})"

fat_table: List[int] = [1]

def factorial(x: number) -> int:
z: int = int(x)
if z < 0: raise ValueError('No factorial of negative numbers!')
if z > 100: raise ValueError('Too large')
if len(fat_table) > z: return fat_table[z]
f: int = factorial(z - 1) * z
fat_table.append(f)
return f

class Factorial(Op):
def __init__(self, a: Op) -> None:
self.__a = a

def op(self) -> number:
return factorial(self.__a.op())

def __str__(self):
return f"{self.__a}!"

# Not currently used. But I'll left it here if you want to play with it.
class Dot(Op):
def __init__(self, a: Op, b: Op) -> None:
self.__a = a
self.__b = b

def op(self) -> number:
a: number = self.__a.op()
b: number = self.__b.op()
if int(a) == float(a): a = int(a)
if int(b) == float(b): b = int(b)
x: str = str(a) + '.' + str(b)
return float(x)

def __str__(self):
return f"({self.__a} d {self.__b})"

def leafs(op1: int) -> List[Op]:
return [Num(op1), UnaryMinus(Num(op1)), Square(Num(op1)), SquareRoot(Num(op1)), Factorial(Num(op1))]

def combine(op: str, op1: Op, op2: Op) -> Op:
if len(op) == 2:
if op[0] == '-': return UnaryMinus(combine(op[1], op1, op2))
if op[0] == '2': return Square(combine(op[1], op1, op2))
if op[0] == 'R': return SquareRoot(combine(op[1], op1, op2))
if op[0] == '!': return Factorial(combine(op[1], op1, op2))
raise Exception("WTF!?")
if op == '+': return Add(op1, op2)
if op == '-': return Sub(op1, op2)
if op == '*': return Times(op1, op2)
if op == '/': return Div(op1, op2)
#if op == 'c': return Concat(op1, op2)
if op == '^': return Pow(op1, op2)
#if op == 'd': return Dot(op1, op2)
raise Exception("WTF!?")

def join(p: str, na: Op, nb: Op, nc: Op, nd: Op, x: str, y: str, z: str) -> Op:
if p == 'balanced': return combine(z, combine(x, na, nb), combine(y, nc, nd))
if p == 'lefty': return combine(z, combine(y, combine(x, na, nb), nc), nd)
if p == 'righty': return combine(x, na, combine(y, nb, combine(z, nc, nd)))
if p == 'zigzag': return combine(z, na, combine(y, combine(x, nb, nc), nd))
if p == 'zagzig': return combine(z, combine(y, na, combine(x, nb, nc)), nd)
raise Exception("WTF!?")

def do_it_all() -> None:

nums: List[List[int]] = [
[0, 0, 2, 2], [0, 2, 0, 2], [0, 2, 2, 0], [2, 0, 0, 2], [2, 0, 2, 0], [2, 2, 0, 0]
]

trees: List[str] = ['balanced', 'lefty', 'righty', 'zigzag', 'zagzig']

ops1: List[str] = ['+', '-', '*', '/', '^']
ops2: List[str] = ['-', '2', 'R', '!']

ops: List[str] = ops1.copy()
for o1 in ops1:
for o2 in ops2:
ops.append(o2 + o1)

q: int = 0

for p in trees:
for a in nums:
for x in ops:
for y in ops:
for z in ops:
for a0 in leafs(a[0]):
for a1 in leafs(a[1]):
for a2 in leafs(a[2]):
for a3 in leafs(a[3]):
q += 1
#if q % 1000000 == 0: print(q)
t: Op = join(p, a0, a1, a2, a3, x, y, z)
try:
n: number = t.op()
if n == 28:
print(str(t))
#else:
#print(str(t) + " = " + str(n))
except Exception as fuuuu:
#print(str(t) + ": " + str(fuuuu))
pass
#print(q)

do_it_all()


After some minutes, it spits a total of:

$$12,668$$ solutions that gives $$28$$ as the answer.

Here are few of them that I got out randomly from the output:

$$(0! + 0!)^{2} + (\sqrt{2} ^ {(2^2)})!$$ $$(0 + 2^2)! + (0^2 + 2!)^2$$ $$(2^2 \div (0 + 0)!)! + 2^2$$ $$\sqrt{((2^2 - 0)! + 2^2)^2 + 0}$$ $$2^2 + ((0 + 0)! \times 2^2)!$$ $$(0^2 + (2 - \sqrt{0})^2)! + 2^2$$

The complete output is here. Of course I couldn't put it all in this post because there are too many solutions found (as stated above).

• Great work! Were there any solutions that didn't involve squaring? Jan 7 '20 at 23:44
• @DmitryKamenetsky I didn't looked carefully for that, but at a quick eye-scan in the results, I found none. Jan 8 '20 at 0:23
• thank you. That confirms my suspicion that it is impossible without squaring. Jan 8 '20 at 1:27

Here is a solution that keeps the order of the digits:

squared(2)*(0!+(2+0!)!) = 4 * 7 = 28.

• In fact, swap around the two terms in the bracket to the right of the multiplication symbol and you keep all 4 digits in their original ordering too, for a more perfect solution :)
– Stiv
Jan 2 '20 at 13:55
• @Stiv excellent idea! Fixed. Jan 2 '20 at 13:57

Additional solution, keeeping the digits in order:

$$(2^2 + 0!)^2 + 2 + 0! = 5^2 + 3 = 28$$

$$(2^{2^2}-2)\times(0!+0!)=(2^4-2)\times2=14\times2=28$$.
Uses the 'square' operation doubled up.

The digits are out of order, but the sum still works. The given digits are on the base line. Operations: square, multiply, subtract, add, factorial.

$${2^2}^2 \cdot 2 - (0! + 0!)^2 = 16 \cdot 2 - 2^2 = 28$$

• 2^2^2 = 16, not 8, no matter which order I try ... Jan 4 '20 at 8:55
• @Glorfindel Oops. Here's a better version. Jan 4 '20 at 11:02

$$square(2)!+0!+2+0!=28$$
$$square(2)\cdot((0!+2)!+0!)=28$$

If we use the parentheses $$\left(\!\!{\choose }\!\!\right)$$ with interpretation as multiset coefficient: $$\left(\!\!{n\choose k}\!\!\right)$$ = $${n+k-1\choose k}$$ = $$\frac{(n+k-1)!}{(n-1)!\space k!}$$

$$\left(\!\!{(2+0!)!+0!\choose 2}\!\!\right) = \left(\!\!{7\choose 2}\!\!\right) = {8\choose 2} = \frac{8!}{6!\space 2!} = 28$$