The last few months have been a little crazy with consulting work and hence tweeting, blogging, comic, and general recreational math have taken a back seat. Last week, I finally had a “normal” week and I wandered over to Ben Vitale’s blog, where I got to read about this puzzle.
The rules are simple: using the factorial symbol, square root function, ceiling function, and floor function, find compositions that will lead to a fixed point solution for a given positive integer
Notice that
as all I did was shift the order of the function compositions. (Generally, we’d have to take care since we want to avoid taking the factorial of a non-integer. And yes, we could still do this with the use of the gamma function, but this article is about dumb programs and I want to stay in the realm of stupid.) You’ll notice that Ben used the double factorial and thus produced a different, minimal version for
Since, 6!! = 48 whose square root is between 6 and 7, which when rounded down is 6, gives the desired solution. This means that we don’t necessarily have unique minimal representations for a given
I putzed around with these by hand just to get a feel for how tricky this would be and to see if I could come up with some simple heuristics for stumbling into a solution — I didn’t really care if the solution was minimal. A few things were immediately apparent: I don’t want a sequence of function compositions to get me to 2 or lower as I would be stuck (unless if
>>> factorial(80)
71569457046263802294811533723186532165584657342365752577109445058227039255480148842668944867280814080000000000000000000
Even decimal
module.
So with a little bit of Python and a little bit of
But then, I found some “crazy” minimal or near-minimal solutions that are a pain to typeset.
I suppose I could have written more code to generate the correct
But, wait, I lied! If I sacrifice a little sleep, then I do have enough free time for this. Here’s 20 to 48 with the
I got tired of waiting for
And we would say it like this
The floor of the square root of the square root of the square root of the square root of the square root of the double factorial of the floor of the square root of the square root of the square root of the square root of the square root of the double factorial of the ceiling of the square root of the square root of the square root of the square root of the square root of the double factorial of the floor of the square root of the square root of the square root of the square root of the square root of the factorial of the double factorial of the ceiling of the square root of the square root of the factorial of the factorial of the ceiling of the square root of the square root of the square root of the factorial of the square root of forty-nine equals forty-nine.
(Don’t worry that phrase was autogenerated.)
There was one case for
The Code
How dumb is the code? It’s this dumb.
>>> def fp(n): def identity(x): return x def doublefactorial(x): s = 1 if x%2 == 0: for i in range(2,x,2): s *= i s *= x else: for i in range(1,x,2): s *= i s *= x return s funcs = [math.factorial, math.sqrt, doublefactorial, math.ceil, math.floor] fseq = [random.choice(funcs[:3])] v = fseq[0](n) f0 = None f = None while v != n: if math.ceil(v) == n: f = math.ceil elif math.floor(v) == n: f = math.floor elif v > 150: f = math.sqrt elif v < 3: f = math.ceil elif int(v) == v: f = random.choice(funcs[:3]) else: f = random.choice(funcs) if (f == math.factorial or f == doublefactorial) and int(v) != v: f0 = random.choice(funcs[3:]) else: f0 = identity try: f(f0(v)) except Exception as e: continue finally: pass if f(f0(v)) <= 2: continue if f0 != identity: fseq.append(f0) fseq.append(f) v = f(f0(v)) return fseq
And that just generates one random solution. For any given number, I can get a good guess as to what a minimal solution is with this wrapper code --- the highlight is that I get to use Python's for-else
.
>>> for i in range(10000): if len(s) == 10000: s = [] s.append(fp(60)) else: m = min(len(j) for j in s) for j in s: if len(j) == m: print(j) break
And this generates the
>>> def functotex(funclist,v): ftexmap = {"floor": ["\\Bigg\\lfloor","\\Bigg\\rfloor"],"ceil": ["\\Bigg\\lceil","\\Bigg\\rceil"],"sqrt":["\\sqrt{","}"],"factorial":["\\Bigg(","!\\Bigg)"],"doublefactorial":["\\Bigg(","!!\\Bigg)"]} texlist = [] cursor = 0 n = len(texlist) for f in reversed(funclist): if "floor" in str(f): texlist.insert(cursor,ftexmap["floor"][0]) cursor += 1 texlist.insert(cursor,ftexmap["floor"][1]) n += 2 if "ceil" in str(f): texlist.insert(cursor,ftexmap["ceil"][0]) cursor += 1 texlist.insert(cursor,ftexmap["ceil"][1]) n += 2 if "sqrt" in str(f): texlist.insert(cursor,ftexmap["sqrt"][0]) cursor += 1 texlist.insert(cursor,ftexmap["sqrt"][1]) n += 2 if "factorial" in str(f) and "doublefactorial" not in str(f): texlist.insert(cursor,ftexmap["factorial"][0]) cursor += 1 texlist.insert(cursor,ftexmap["factorial"][1]) n += 2 if "doublefactorial" in str(f): texlist.insert(cursor,ftexmap["doublefactorial"][0]) cursor += 1 texlist.insert(cursor,ftexmap["doublefactorial"][1]) n += 2 texlist.insert(cursor,str(v)) return texlist, "".join(texlist)
Musings
I didn't really make too big of a study of this. If we look at successive solutions long enough, it feels that there is some general principle at play to recursively generate solutions, but that's a magic in and of itself.
A basic observation is that the sequence of function compositions produces a sequence of numbers that oscillate to the solution. In general, we overshoot with factorials and steadily come down with square roots.
Another basic observation is that hacking together a programmatic solution is fun and even a dumb program can give good solutions, though it did get stuck for
Of course, one simple improvement would be that as soon as a solution is found, I should have my program check for a shorter path within the sequence of compositions by tracking the sequence of values that result at each composition step. If I were to find a duplicate value, then I can clearly shorten the sequence. For example, if I observe the following collection of
then, rather than continue from
Anyhow, here are a few last ones for the road: