As long as I can remember, I've always liked numbers. The ways they behave and relate has always captured my interest. While I was in college, there were always fun mathematical problems to solve running around the corridors and I was one, amongst a bunch of people, who got entertained by trying to solve them. At some point, this simple problem showed up:
Fill a 3 by 3 square with the numbers from 1 to 9 so that the sum of the numbers on each row, column and both diagonals are the same.
After a little trial and error, the solution is easy to achieve:
| 4 | 9 | 2 |
| 3 | 5 | 7 |
| 8 | 1 | 6 |
And the sum of the numbers on each row, column and both diagonals is 15.
And, as usual, solving just the problem at hand isn't enough. I embarked in the task of trying to solve the same problem for a 5 by 5 square. After a little bit more trial and error than the first time, the solution presented itself:
| 11 | 24 | 7 | 20 | 3 |
| 4 | 12 | 25 | 8 | 16 |
| 17 | 5 | 13 | 21 | 9 |
| 10 | 18 | 1 | 14 | 22 |
| 23 | 6 | 19 | 2 | 15 |
Where the sum of the numbers on each row, column and both diagonals is 65.
At this point, I thought it must be likely that all odd-sided squares have a similar solution and, consequently, there must be a generic way to solve this problem for all. Following that conviction, I started looking for patterns and some stand out to the "naked eye", more or less rotations of the solution.
| 4 | 9 | 2 |
| 3 | 5 | 7 |
| 8 | 1 | 6 |
| 11 | 24 | 7 | 20 | 3 |
| 4 | 12 | 25 | 8 | 16 |
| 17 | 5 | 13 | 21 | 9 |
| 10 | 18 | 1 | 14 | 22 |
| 23 | 6 | 19 | 2 | 15 |
- In the middle of the square goes the median (the middle number): 5 in 3x3; 13 in 5x5
- Bellow the median goes the first number, i.e., 1
- Over the median goes the last number: 9 in 3x3; 25 in 5x5
- Top-left to bottom-right diagonal is in sequence, having the median, in the middle:
- 4, 5 and 6 in 3x3
- 11, 12, 13, 14 and 15 in 5x5
- Since the top-left to bottom-right diagonal numbers are known and sequential, it's easy to calculate
the expected value of the sum of each row, column and both diagonals:
ƒ( side) = side * ( side 2 + 1) / 2
Where ( side 2 + 1) / 2 finds the median that then, is multiplied by the side . The differences of the other numbers to the median are cancelled, e.g., in the 5 by 5 square where the median is 13, 12 cancels 14 and 11 cancels 15.
Having this starting point, I took the task of solving the 7 by 7 square.
| 22 | 47 | 16 | 41 | 10 | 35 | 4 |
| 5 | 23 | 48 | 17 | 42 | 11 | 29 |
| 30 | 6 | 24 | 49 | 18 | 36 | 12 |
| 13 | 31 | 7 | 25 | 43 | 19 | 37 |
| 38 | 14 | 32 | 1 | 26 | 44 | 20 |
| 21 | 39 | 8 | 33 | 2 | 27 | 45 |
| 46 | 15 | 40 | 9 | 34 | 3 | 28 |
Where the sum of the numbers on each row, column and both diagonals is 175.
Some more scrutiny and some more patterns became evident, like the sequences of numbers in diagonals parallel to the middle top-left to bottom-right diagonal:
| 22 | 47 | 16 | 41 | 10 | 35 | 4 |
| 5 | 23 | 48 | 17 | 42 | 11 | 29 |
| 30 | 6 | 24 | 49 | 18 | 36 | 12 |
| 13 | 31 | 7 | 25 | 43 | 19 | 37 |
| 38 | 14 | 32 | 1 | 26 | 44 | 20 |
| 21 | 39 | 8 | 33 | 2 | 27 | 45 |
| 46 | 15 | 40 | 9 | 34 | 3 | 28 |
Or the gap of the size of the side, in this case 7, on the diagonals of the opposite direction:
| 22 | 47 | 16 | 41 | 10 | 35 | 4 |
| 5 | 23 | 48 | 17 | 42 | 11 | 29 |
| 30 | 6 | 24 | 49 | 18 | 36 | 12 |
| 13 | 31 | 7 | 25 | 43 | 19 | 37 |
| 38 | 14 | 32 | 1 | 26 | 44 | 20 |
| 21 | 39 | 8 | 33 | 2 | 27 | 45 |
| 46 | 15 | 40 | 9 | 34 | 3 | 28 |
These got me trying out things like continuing these sequences outside the square itself and, eventually, I got here:
| 1 | ||||||||||||
| 8 | 2 | |||||||||||
| 15 | 9 | 3 | ||||||||||
| 22 | 47 | 16 | 41 | 10 | 35 | 4 | ||||||
| 29 | 5 | 23 | 48 | 17 | 42 | 11 | 29 | 5 | ||||
| 36 | 30 | 6 | 24 | 49 | 18 | 36 | 12 | 6 | ||||
| 43 | 37 | 13 | 31 | 7 | 25 | 43 | 19 | 37 | 13 | 7 | ||
| 44 | 38 | 14 | 32 | 1 | 26 | 44 | 20 | 14 | ||||
| 45 | 21 | 39 | 8 | 33 | 2 | 27 | 45 | 21 | ||||
| 46 | 15 | 40 | 9 | 34 | 3 | 28 | ||||||
| 47 | 41 | 35 | ||||||||||
| 48 | 42 | |||||||||||
| 49 |
And, removing the clutter:
| 1 | ||||||||||||
| 8 | 2 | |||||||||||
| 15 | 9 | 3 | ||||||||||
| 22 | 16 | 10 | 4 | |||||||||
| 29 | 23 | 17 | 11 | 5 | ||||||||
| 36 | 30 | 24 | 18 | 12 | 6 | |||||||
| 43 | 37 | 31 | 25 | 19 | 13 | 7 | ||||||
| 44 | 38 | 32 | 26 | 20 | 14 | |||||||
| 45 | 39 | 33 | 27 | 21 | ||||||||
| 46 | 40 | 34 | 28 | |||||||||
| 47 | 41 | 35 | ||||||||||
| 48 | 42 | |||||||||||
| 49 |
It doesn't take long from here to realize the numbers outside the square go into it moving the side value, in this case 7, into it:
| 1 | ||||||||||||
| 8 | 2 | |||||||||||
| 15 | 9 | 3 | ||||||||||
| 22 | 16 | 10 | 4 | |||||||||
| 29 | 23 | 17 | 11 | 5 | ||||||||
| 36 | 30 | 24 | 18 | 12 | 6 | |||||||
| 43 | 37 | 31 | 25 | 43 | 19 | 13 | 7 | |||||
| 44 | 38 | 32 | 1 | 26 | 20 | 14 | ||||||
| 45 | 21 | 39 | 33 | 27 | 21 | |||||||
| 46 | 40 | 34 | 28 | |||||||||
| 47 | 41 | 35 | ||||||||||
| 48 | 42 | |||||||||||
| 49 |
And that's all there is to it.
I think, the largest side value I tested on paper, in my college days, was 19. Mostly, because it's not easy to find squared paper with such a big matrix. But I'm pretty confident this method works for every odd-sided square.
As a computer program
Since then, I've started working as a software developer and I always had the goal of, one day, implementing this method as a computer program. And, as I like to explore various programming languages, I try to code this give the language a go. The code lives here. Or in each language:
- JavaScript
- C
- C++
- Python
- Ruby
- Rust
- Lua
- Kotlin
- F#
- Go
- C#
- Java
- Perl
- PowerShell
- PHP
- VB.Net
- TypeScript
- Erlang
The purpose is to follow the same approach, e.g., same components, on every language but respecting the "normal" usage of each language and its features. The program consists of the following files that represent separate modules (or packages), and their contents:
> .xx is a placeholder for the file extension
> casing might change, according to language customs
-
main(.xx):
-
Main - Program entry point, i.e., where the program starts:
- Detects if there are command line arguments that provide matrix side and, if not, calls for user input to be requested
- Calls for matrix calculation
- Calls for matrix display
-
ParseSideInput - function that uses a regular expression to check if input is just digits and, if so, parses the input to an integer.
-
ErrorOut - function that displays an error message and exits program.
-
-
consoleUtil(.xx) - Helper module containing:
-
Prompt - function that receives a question to ask the user and returns the user input.
-
DisplayMatrixResult - function that receives the calculated matrix and displays it calculating paddings so that columns are aligned.
-
-
results(.xx) - Contains result object definitions:
-
ParseSideResult - The result of parsing side input return with either the parsed integer or an error message.
-
MatrixResult - The result of matrix calculation containing the calculated matrix, the expected sum (of each row, column and both diagonals) and the input side.
-
-
matrixCalculator(.xx) - The module where the matrix calculation is made, containing:
-
CreateEmtpyMatrix - A (private where available) function that creates an empty (with 0 in all positions) matrix of the given side in both dimentions.
-
CurrentHolder - A (private where available) state definition (class, type) that serves as a helper for matrix calculation.
-
Calculate - 'The' function that, receiving the side as parameter, calculates the intended matrix.
-
TestResult - A (private where available) function that tests if the result matrix is correct, i.e., that the sum of each row, column and both diagonals is equal to the expected sum.
-