in Maths, Python, Tutorial

Recreational Maths in Python

This post is for all the developers and mathematicians out there that are curious to explore and visualize the bizarre properties of numbers. Although Maths plays an important role in today’s technology, many people likes to abuse it for recreational purposes. Part of the appeal of Recreational Maths lies in the challenge to discover something new. Despite what many believe, finding mathematical patterns is very easy; it’s discovering something useful that is incredibly challenging. If you’re up for such a challenge, this tutorial will teach you how to use Python to calculate some of the most infamous numerical sequences.

Part 0. Unlimited precision

If you are familiar with Python, it is not uncommon to discover native features which would be challenging to replicate in other languages. One of the most intriguing is the way Python handles long integers. While standard ints have a 32bit precision, longs can store arbitrarily big integers. This feature is native in Python, and does not requires any additional piece of code: when an int becomes too large, it is automatically cast to long. Compared to Java or C#, Python is perfect to work with very long numbers. The best hidden features of Python have been discussed in the tutorial The Top 5 Hidden Features of Python.

Part 1. Working with digits

Many numerical sequences which are classified as recreational Maths work by manipulating the digits of a number. Converting a number in the list of its digits is surprisingly easy in Python:

def int_to_list (n):
	return map(int,str(n))

The number is firstly converted into a string using str(n); it is then passed into map which converts each character of the string into an integer.

>>> int_to_list (1234567890);
[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]

Narcissistic numbers

Let’s use this to calculate Narcissistic numbers (OEIS A005188); a number of d digits is narcissistic if it’s equal to the sum of the d^{th} power of its digits. For instance, 153 is narcissistic since 153=1^3+5^3+3^3. These numbers are rather useless and they are “very suitable for puzzle columns and likely to amuse amateurs, but there is nothing in them which appeals to the mathematician” (G. H. Hardy, 1993).

The following code checks whether a number is narcissistic or not:

def is_narcissistic (n):
	d = int_to_list(n)
	e = 0
	for x in d:
		e = e + x ** len(d)
	return e == n

The for loop can be replaced by a map function, which calculates the d^{th} power of each digit:

def is_narcissistic (n):
	d = int_to_list(n)
	e = sum(	map(lambda x : x**len(d), d)	)
	return e == n

To calculate the sequence you can simply leave this piece of code running:

n = 1
while True:
	if is_narcissistic(n):
		print n,
	n = n+1

Kaprekar numbers

A slightly more interesting sequence is the one made of Kaprekar numbers (OEIS A006886). Consider an n-digit number. Square it and add the right n digits to the left n or n-1 digits. If the resultant sum is equal to the original number, then it is a Keprekar number. 9 satisfies this property, since 9^2=81 and 8+1= 9 itself. Similarly 297^2=88209 and 88+209=297.

What makes it more interesting is that it requires to split a number into digits and then to join them. Once again, Python offers a very elegant way of doing this:

def list_to_int(ns):
	return int(''.join(map(str, ns)))

What we have to do now is to split the left and right side of the digits into two separate lists. To get the left n elements of a list d in Python one can write d[:-n]; for the right n elements d[-n:] can be used instead.

def is_kaprekar (n):
	nn = len(	int_to_list(n)	)	# Digits original numbers
	d = int_to_list(n**2)			# Square
	d_l = d[:-nn]	# Left side
	d_r = d[-nn:]	# Right side

	# Right term can't be zero (by convention)
	if d_r == 0:
		return False

	return n == list_to_int(d_l) + list_to_int(d_r)

Step 2. Working with sequences

Some numbers requires a more complex approach. This is the case for happy numbers (OEIS A007770). Finding if a number is happy or sad (not happy) requires an iterative computation. Let’s start with the happification, a process in which you take the digits of a number, square and sum them up:

    \[happy\left( 13\right)=1^2+3^2=10\]

A number is happy if repeating this process leads to 1. In this example, 13 is indeed happy, since happy\left( 10\right)=1. An interesting property of the happification is that numbers either end up at 1, or they get stuck in a loop made of 41637588914542 and 20. Numberphile has covered this in a video in which Matt Parker coined the term melancoil.

The function is_happy determines whether a number is happy or not; it keeps happifying the number until it gets 1 or a loop:

def is_happy (n, l=[]):
	seen_numbers.append(n)

	# End of tree
	if n == 1:
		return True

	ns = int_to_list(n)
	s = sum(map(lambda x : x**2, ns))

	# We are looping!
	if s in seen_numbers:
		l.append(s)
		return False

	# Recursive step
	return is_happy(s, l)

It’s important to remember that despite Python’s ability to work with arbitrarily large numbers, the function is_happy cannot. This is because Python has a recursion depth limit. You can extend it using:

import sys
sys.setrecursionlimit(10000)

Another alternative is to avoid recursion altogether:

def is_happy(n):
    seen_numbers = set()
    while n > 1 and (n not in seen_numbers):
        seen_numbers.add(n)
        ns = int_to_list(n)
        n = sum( map(lambda x: x**2, ns) )
    return n == 1

Since we are storing the numbers in a list, the function is again limited by the amount of memory available. A further improvement is to remove any memory requirement by hard-coding the melancoil numbers in the function:

def is_happy(n):
    melancoil = [4, 16, 37, 58, 89, 145, 42, 20]
    while n > 1 and (n not in melancoil):
        ns = int_to_list(n)
        n = sum( map(lambda x: x**2, ns) )
    return n == 1

The above function works only because all the sad numbers eventually end up in the melancoil loop.

Step 3. Visualising graphs

The happification process naturally links numbers to each other. The result is a network graph which can be hard to see within the Python console. The best way to show the relationships between them is to visualise them with a graph. This is discussed in the tutorial Interactive Graphs in the Browser. You can see the result in the interactive graph below:

You can find a high resolution version (2000x2000px, first 1000 numbers) here: PNG, SVG, HTML.

Step 4. Parallel computation

If the sequence you are trying to calculate works on very big numbers, it might take longer and longer to run. Calculating several numbers in parallel can help speed up the process, especially if you have multiple cores. There are several ways to implement parallel computation in Python; this example relies on the joblib package:

from joblib import Parallel, delayed

if __name__ == '__main__':
	tasks = 4
	with Parallel(n_jobs=tasks) as parallel:
		n = 1
		while True:
			# Calculate
			results = parallel(delayed(function_to_calculate)(n+i)	for i in range(tasks))

			# Print
			for i, result in enumerate(results):
				print n+i, "\t", result

			n = n +tasks

Line 5 opens a with section which creates four parallel processes. Every process has overhead, so it’s important to use a reasonable number; using too many processes can actually slow down your calculations. Within the parallel block, the computation is done with the function parallel, which is fed four tasks. The function delayed allows the user to specify the name of the function that will be executed separately. Evaluating results forces the program to wait until all of the processes are completed.

It is important to notice that in order for this code to work, everything has to be inside a __main__ section; this is because when the processes start they will execute the current file again.

Conclusion

This tutorial has shown how Python can be used effectively to calculate numerical sequences of arbitrary length. Recreational Maths is a very intriguing field, which can sometimes produce beautiful things such as Conway’s Game of Life. I strongly encourage you to explore the bizarre properties of numbers, and see if you can find something new.

If you are interested in this topic, you might also find the following tutorials interesting:

💖 Support this blog

This website exists thanks to the contribution of patrons on Patreon. If you think these posts have either helped or inspired you, please consider supporting this blog.

Patreon Patreon_button
Twitter_logo

YouTube_logo
📧 Stay updated

You will be notified when a new tutorial is released!

📝 Licensing

You are free to use, adapt and build upon this tutorial for your own projects (even commercially) as long as you credit me.

You are not allowed to redistribute the content of this tutorial on other platforms, especially the parts that are only available on Patreon.

If the knowledge you have gained had a significant impact on your project, a mention in the credit would be very appreciated. ❤️🧔🏻

Write a Comment

Comment

  1. What’s the point of linking to the previous article (“5 hidden features of Python”) when it’s password-protected?

  2. First time I’m using python and long fan of Numberphile. Thanks for this tutorial. I did have some issues getting the code to run (Python 3.9). Some fixes I made to get it to run:

    #This method should return a list and not a map, hence the name.
    def int_to_list(n):
    res = list(map(int, str(n)))
    return res

    #In the recursive is_happy method, the seen_numbers variable just appears without being declared anywhere. For people just starting with programming this is probably a show-stopper. So I just declared it globally.
    x = int(input())
    seen_number = []
    is_happy(x)

    #So my entire program looks like this:
    import sys

    sys.setrecursionlimit(10000)

    ”’
    Takes each digit from the supplied integer and enters them
    into the returned list.
    ”’
    def int_to_list(n):
    res = list(map(int, str(n)))
    return res

    ”’
    Takes a supplied list of digits and returns these digits as
    a integer.
    ”’
    def list_to_int(ns):
    res = int(”.join(map(str, ns)))
    return res

    ”’
    Test whether the supplied integer is a Kaprekar number.
    ”’
    def is_kaprekar(n):
    list_len = len(int_to_list(n))
    digit_squared = int_to_list(n ** 2) # Square
    squared_l = digit_squared[:-list_len] # Left side
    squared_r = digit_squared[-list_len:] # Right side
    # Right term can’t be zero (by convention)
    if squared_r == 0:
    return False
    return n == list_to_int(squared_l) + list_to_int(squared_r)

    ”’
    Test whether the supplied integer is a Happy number.
    ”’
    def is_happy(n, l=[]):
    seen_numbers.append(n)
    # End of tree
    if n == 1:
    return True
    nr_as_digits = int_to_list(n)
    nr_squared = sum(map(lambda x: x ** 2, nr_as_digits))
    # We are looping!
    if nr_squared in seen_numbers:
    l.append(nr_squared)
    return False
    # Recursive step
    return is_happy(nr_squared, l)

    ”’
    Main()
    ”’
    input_nr = int(input())
    seen_numbers = []
    if is_happy(input_nr):
    print(f”{input_nr} is a happy number”)
    else:
    print(f”{input_nr} is NOT a happy number”)

Webmentions

  • Interactive Graphs in the Browser - Alan Zucconi May 2, 2021

    […] Recreational Maths in Python […]

  • Links of the Week, W46 2015 | One More Game-Dev and Programming Blog May 2, 2021

    […] Recreational Maths in Python. […]