diff --git a/code/patterns/patterns.rb b/code/patterns/patterns.rb new file mode 100644 index 0000000..1a173a8 --- /dev/null +++ b/code/patterns/patterns.rb @@ -0,0 +1,68 @@ +require 'victor' + +def sum_digits(n) + while n > 9 + n = n.to_s.chars.map(&:to_i).sum + end + n +end + +def step(x, y, n, dir) + case dir + when :top + return [x,y+n,:right] + when :right + return [x+n,y,:bottom] + when :bottom + return [x,y-n,:left] + when :left + return [x-n,y,:top] + end +end + +def run_number(number) + counter = 1 + x, y, dir = 0, 0, :top + line_stack = [[0,0]] + + loop do + x, y, dir = step(x,y, sum_digits(counter*number), dir) + line_stack << [x,y] + counter += 1 + break if x == 0 && y == 0 + end + return make_svg(line_stack) +end + +def make_svg(line_stack) + line_length = 20 + xs = line_stack.map { |c| c[0] } + ys = line_stack.map { |c| c[1] } + + x_offset = -xs.min + y_offset = -ys.min + svg_coords = ->(p) { + nx, ny = p + [(nx+x_offset)*line_length + line_length/2, (ny+y_offset)*line_length + line_length/2] + } + + max_width = (xs.max - xs.min).abs * line_length + line_length + max_height = (ys.max - ys.min).abs * line_length + line_length + svg = Victor::SVG.new width: max_width, height: max_height + + style = { stroke: 'black', stroke_width: 5 } + svg.build do + line_stack.each_cons(2) do |pair| + p1, p2 = pair + x1, y1 = svg_coords.call(p1) + x2, y2 = svg_coords.call(p2) + line x1: x1, y1: y1, x2: x2, y2: y2, style: style + circle cx: x2, cy: y2, r: line_length/6, style: style, fill: 'black' + end + end + return svg +end + +(1..9).each do |i| + run_number(i).save "pattern_#{i}" +end diff --git a/content/blog/modulo_patterns/index.md b/content/blog/modulo_patterns/index.md new file mode 100644 index 0000000..8fb4307 --- /dev/null +++ b/content/blog/modulo_patterns/index.md @@ -0,0 +1,318 @@ +--- +title: Digit Sum Patterns and Modular Arithmetic +date: 2021-12-30T15:42:40-08:00 +tags: ["Ruby", "Mathematics"] +draft: true +--- + +When I was in elementary school, our class was briefly visited by our school's headmaster. +He was there for a demonstration, probably intended to get us to practice our multiplication tables. +_"Pick a number"_, he said, _"And I'll teach you how to draw a pattern from it."_ + +The procedure was rather simple: + +1. Pick a number between 2 and 8 (inclusive). +2. Start generating multiples of this number. If you picked 8, + your multiples would be 8, 16, 24, and so on. +3. If a multiple is more than one digit long, sum its digits. For instance, for 16, write 1+6=7. + If the digits add up to a number that's still more than 1 digit long, add up the digits of _that_ + number (and so on). +4. Start drawing on a grid. For each resulting number, draw that many squares in one direction, + and then "turn". Using 8 as our example, we could draw 8 up, 7 to the right, 6 down, 5 to the left, + and so on. +5. As soon as you come back to where you started (_"And that will always happen"_, said my headmaster), + you're done. You should have drawn a pretty pattern! + +Sticking with our example of 8, the pattern you'd end up with would be something like this: + +{{< figure src="pattern_8.svg" caption="Pattern generated by the number 8." class="tiny" alt="Pattern generated by the number 8." >}} + +Before we go any further, let's observe that it's not too hard to write code to do this. +For instance, the "add digits" algorithm can be naively +written by turning the number into a string (`17` becomes `"17"`), splitting that string into +characters (`"17"` becomes `["1", "7"]`), turning each of these character back into numbers +(the array becomes `[1, 7]`) and then computing the sum of the array, leaving `8`. + +{{< codelines "Ruby" "patterns/patterns.rb" 3 8 >}} + +We may now encode the "drawing" logic. At any point, there's a "direction" we're going - which +I'll denote by the Ruby symbols `:top`, `:bottom`, `:left`, and `:right`. Each step, we take +the current `x`/`y` coordinates (our position on the grid), and shift them by `n` in a particular +direction `dir`. We also return the new direction alongside the new coordinates. + +{{< codelines "Ruby" "patterns/patterns.rb" 10 21 >}} + +The top-level algorithm is captured by the following code, which produces a list of +coordinates in the order that you'd visit them. + +{{< codelines "Ruby" "patterns/patterns.rb" 23 35 >}} + +I will omit the code for generating SVGs from the body of the article -- you can always find the complete +source code in this blog's Git repo (or by clicking the link in the code block above). Let's run the code on a few other numbers. Here's one for 4, for instance: + +{{< figure src="pattern_4.svg" caption="Pattern generated by the number 4." class="tiny" alt="Pattern generated by the number 4." >}} + +And one more for 2, which I don't find as pretty. + +{{< figure src="pattern_2.svg" caption="Pattern generated by the number 2." class="tiny" alt="Pattern generated by the number 2." >}} + +It really does always work out! Young me was amazed, though I would often run out of space on my +grid paper to complete the pattern, or miscount the length of my lines partway in. It was only +recently that I started thinking about _why_ it works, and I think I figured it out. Let's take a look! + +### Is a number divisible by 3? +You might find the whole "add up the digits of a number" thing familiar, and for good reason: +it's one way to check if a number is divisible by 3. The quick summary of this result is, + +> If the sum of the digits of a number is divisible by 3, then so is the whole number. + +For example, the sum of the digits of 72 is 9, which is divisible by 3; 72 itself is correspondingly +also divisible by 3, since 24*3=72. On the other hand, the sum of the digits of 82 is 10, which +is _not_ divisible by 3; 82 isn't divisible by 3 either (it's one more than 81, which _is_ divisible by 3). + +Why does _this_ work? Let's talk remainders. + +If a number doesn't cleanly divide another (we're sticking to integers here), +what's left behind is the remainder. For instance, dividing 7 by 3 leaves us with a remainder 1. +On the other hand, if the remainder is zero, then that means that our dividend is divisible by the +divisor (what a mouthful). In mathematics, we typically use +\\(a|b\\) to say \\(a\\) divides \\(b\\), or, as we have seen above, that the remainder of dividing +\\(b\\) by \\(a\\) is zero. + +Working with remainders actually comes up pretty commonly in discrete math. A well-known +example I'm aware of is the [RSA algorithm](https://en.wikipedia.org/wiki/RSA_(cryptosystem)), +which works with remainders resulting from dividing by a product of two large prime numbers. +But what's a good way to write, in numbers and symbols, the claim that "\\(a\\) divides \\(b\\) +with remainder \\(r\\)"? Well, we know that dividing yields a quotient (possibly zero) and a remainder +(also possibly zero). Let's call the quotient \\(k\\). +{{< sidenote "right" "r-less-note" "Then, we know that when dividing \(b\) by \(a\) we have:" >}} +It's important to point out that for the equation in question to represent division +with quotient \(k\) and remainder \(r\), it must be that \(r\) is less than \(a\). +Otherwise, you could write \(r = s + a\) for some \(s\), and end up with +{{< latex >}} + \begin{aligned} + & b = ka + r \\ + \Rightarrow\ & b = ka + (s + a) \\ + \Rightarrow\ & b = (k+1)a + s + \end{aligned} +{{< /latex >}} + +In plain English, if \(r\) is bigger than \(a\) after you've divided, you haven't +taken out "as much \(a\) from your dividend as you could", and the actual quotient is +larger than \(k\). +{{< /sidenote >}} + +{{< latex >}} + \begin{aligned} + & b = ka + r \\ + \Rightarrow\ & b-r = ka \\ + \end{aligned} +{{< /latex >}} + +We only _really_ care about the remainder here, not the quotient, since it's the remainder +that determines if something is divisible or not. From the form of the second equation, we can +deduce that \\(b-r\\) is divisible by \\(a\\) (it's literally equal to \\(a\\) times \\(k\\), +so it must be divisible). Thus, we can write: + +{{< latex >}} + (b-r)|a +{{< /latex >}} + +There's another notation for this type of statement, though. To say that the difference between +two numbers is divisible by a third number, we write: + +{{< latex >}} + b \equiv r\ (\text{mod}\ a) +{{< /latex >}} + +Some things that _seem_ like they would work from this "equation-like" notation do, indeed, work. +For instance, we can "add two equations": + +{{< latex >}} +\textbf{if}\ a \equiv b\ (\text{mod}\ k)\ \textbf{and}\ c \equiv d, (\text{mod}\ k),\ \textbf{then}\ +a+c \equiv b+d\ (\text{mod}\ k). +{{< /latex >}} +Multiplying both sides by the same number (call it \\(n\\)) also works: + +{{< latex >}} +\textbf{if}\ a \equiv b\ (\text{mod}\ k),\ \textbf{then}\ na \equiv nb\ (\text{mod}\ k). +{{< /latex >}} + +Ok, that's a lot of notation and other _stuff_. Let's talk specifics. Of particular interest +is the number 10, since our number system is _base ten_ (the value of a digit is multiplied by 10 +for every place it moves to the left). The remainder of 10 when dividing by 3 is 1. Thus, +we have: + +{{< latex >}} + 10 \equiv 1\ (\text{mod}\ 3) +{{< /latex >}} + +From this, we can deduce that multiplying by 10, when it comes to remainders from dividing by 3, +is the same as multiplying by 1. We can clearly see this by multiplying both sides by \\(n\\). +In our notation: + +{{< latex >}} + 10n \equiv n\ (\text{mod}\ 3) +{{< /latex >}} + +But wait, there's more. Take any power of ten, be it a hundred, a thousand, or a million. +Multiplying by that number is _also_ equivalent to multiplying by 1! + +{{< latex >}} + 10^kn = 10\times10\times...\times 10n \equiv n\ (\text{mod}\ 3) +{{< /latex >}} + +We can put this to good use. Let's take a large number that's divisible by 3. This number +will be made of multiple digits, like \\(d_2d_1d_0\\). Note that I do __not__ mean multiplication +here, but specifically that each \\(d_i\\) is a number between 0 and 9 in a particular place +in the number -- it's a digit. Now, we can write: + +{{< latex >}} +\begin{aligned} + 0 &\equiv d_2d_1d_0 \\ + & = 100d_2 + 10d_1 + d_0 \\ + & \equiv d_2 + d_1 + d_0 +\end{aligned} +{{< /latex >}} + +We have just found that \\(d_2+d_1+d_0 \\equiv 0\\ (\\text{mod}\ 3)\\), or that the sum of the digits +is also divisible by 3. The logic we use works in the other direction, too: if the sum of the digits +is divisible, then so is the actual number. + +There's only one property of the number 3 we used for this reasoning: that \\(10 \\equiv 1\\ (\\text{mod}\\ 3)\\). But it so happens that there's another number that has this property: 9. This means +that to check if a number is divisible by _nine_, we can also check if the sum of the digits is +divisible by 9. Try it on 18, 27, 81, and 198. + +Here's the main takeaway: __summing the digits in the way described by my headmaster is +the same as figuring out the remainder of the number from dividing by 9__. Well, almost. +The difference is the case of 9 itself: the __remainder__ here is 0, but we actually use 9 +to draw our line. We can actually try just using 0. Here's the updated `sum_digits` code: + +```Ruby +def sum_digits(n) + n % 9 +end +``` + +The results are similarly cool: + +{{< figure src="pattern_8_mod.svg" caption="Pattern generated by the number 8." class="tiny" alt="Pattern generated by the number 8 by just using remainders." >}} +{{< figure src="pattern_4_mod.svg" caption="Pattern generated by the number 4." class="tiny" alt="Pattern generated by the number 4 by just using remainders." >}} +{{< figure src="pattern_2_mod.svg" caption="Pattern generated by the number 2." class="tiny" alt="Pattern generated by the number 2 by just using remainders." >}} + +### Sequences of Remainders +So now we know what the digit-summing algorithm is really doing. But that algorithm isn't all there +is to it! We're repeatedly applying this algorithm over and over to multiples of another number. How +does this work, and why does it always loop around? Why don't we ever spiral further and further +from the center? + +First, let's take a closer look at our sequence of multiples. Suppose we're working with multiples +of some number \\(n\\). Let's write \\(a_i\\) for the \\(i\\)th multiple. Then, we end up with: + +{{< latex >}} +\begin{aligned} + a_1 &= n \\ + a_2 &= 2n \\ + a_3 &= 3n \\ + a_4 &= 4n \\ + ... \\ + a_i &= in +\end{aligned} +{{< /latex >}} + +This is actually called an [arithmetic sequence](https://mathworld.wolfram.com/ArithmeticProgression.html); +for each multiple, the number increases by \\(n\\). + +Here's a first seemingly trivial point: at some time, the remainder of \\(a_i\\) will repeat. +There are only so many remainders when dividing by nine: specifically, the only possible remainders +are the numbers 0 through 8. We can invoke the [pigeonhole principle](https://en.wikipedia.org/wiki/Pigeonhole_principle) and say that after 9 multiples, we will have to have looped. Another way +of seeing this is as follows: + +{{< latex >}} + \begin{aligned} + & 9 \equiv 0\ (\text{mod}\ 9) \\ + \Rightarrow\ & 9n \equiv 0\ (\text{mod}\ 9) \\ + \Rightarrow\ & 10n \equiv n\ (\text{mod}\ 9) \\ + \end{aligned} +{{< /latex >}} + +The 10th multiple is equivalent to n, and will thus have the same remainder. The looping may +happen earlier: the simplest case is if we pick 9 as our \\(n\\), in which case the remainder +will always be 0. + +Repeating remainders alone do not guarantee that we will return to the center. The repeating sequence 1,2,3,4 +will certainly cause a spiral. The reason is that, if we start facing "up", we will always move up 1 +and down 3 after four steps, leaving us 2 steps below where we started. Next, the cycle will repeat, +and since turning four times leaves us facing "up" again, we'll end up getting _further_ down. + +From this, we can devise a simple condition to prevent spiraling -- the _length_ of the sequence before +it repeats _cannot be a multiple of 4_. This way, whenever the cycle restarts, it will do so in a +different direction: backwards, turned once to the left, or turned once to the right. Clearly repeating +the sequence backwards is guaranteed to take us back to the start. The same is true for the left and right-turn sequences, +since after two iterations they will _also_ leave us facing backwards. + +Okay, so we want to avoid cycles with lengths divisible by four. What does it mean for a cycle to be of length _k_? It effectively means the following: + +{{< latex >}} + \begin{aligned} + & a_{k+1} \equiv a_1\ (\text{mod}\ 9) \\ + \Rightarrow\ & (k+1)n \equiv n\ (\text{mod}\ 9) \\ + \Rightarrow\ & kn \equiv 0\ (\text{mod}\ 9) \\ + \end{aligned} +{{< /latex >}} + +If we could divide both sides by \\(k\\), we could go one more step: + +{{< latex >}} + n \equiv 0\ (\text{mod}\ 9) \\ +{{< /latex >}} + +That is, \\(n\\) would be divisible by 9! This would contradict our choice of \\(n\\) to be +between 2 and 8. What went wrong? Turns out, it's that last step: we can't always divide by \\(k\\). +Some values of \\(k\\) are special, and it's only _those_ values that can serve as cycle lengths +without causing a contradiction. So, what are they? + +They're values that have a common factor with 9. There are many numbers that have a common +factor with 9; 3, 6, 9, 12, and so on. However, those can't all serve as cycle lengths: as we said, +cycles can't get longer than 9. This leaves us with 3, 6, and 9 as _possible_ cycle lengths, +none of which are divisible by 4. We've eliminated the possibility of spirals! + +{{< todo >}} +This doesn't get to the bottom of it all. +{{< /todo >}} + +### Generalizing to Arbitrary Divisors +The trick was easily executable on paper because there's an easy way to compute the remainder of a number +when dividing by 9 (adding up the digits). However, we have a computer, and we don't need to fall back on such +cool-but-complicated techniques. To replicate our original behavior, we can just write: + +``` +def sum_digits(n) + x = n % 9 + x == 0 ? 9 : x +end +``` + +But now, we can change the `9` to something else. Any number we pick, so long as it isn't +{{< sidenote "right" "div-4-note" "divisible by 4," >}} +"Wait", you might be thinking, "I thought you said that 4 can't have a common factor with the divisor, +and that means any even numbers are out, too."
+
+Good observation. Although the path-not-divisible-by-four condition is certainly sufficient, it is not +necessary. There seems to be another, less restrictive, condition at play here: even numbers work fine. I haven't +figured out what it is, but we might as well make use of it. +{{< /sidenote >}} will work. I'll pick primes for good measure. Here are a few good ones from using 13 +(which corresponds to summing digits of base-14 numbers): + +{{< figure src="pattern_8_13.svg" caption="Pattern generated by the number 8 in base 14." class="tiny" alt="Pattern generated by the number 8 by summing digits." >}} +{{< figure src="pattern_4_13.svg" caption="Pattern generated by the number 4 in base 14." class="tiny" alt="Pattern generated by the number 4 by summing digits." >}} + +Here are a few from dividing by 17 (base-18 numbers). + +{{< figure src="pattern_5_17.svg" caption="Pattern generated by the number 5 in base 18." class="tiny" alt="Pattern generated by the number 5 by summing digits." >}} + +Finally, base-30: + +{{< figure src="pattern_2_29.svg" caption="Pattern generated by the number 2 in base 30." class="tiny" alt="Pattern generated by the number 2 by summing digits." >}} + +{{< figure src="pattern_6_29.svg" caption="Pattern generated by the number 6 in base 30." class="tiny" alt="Pattern generated by the number 6 by summing digits." >}} diff --git a/content/blog/modulo_patterns/pattern_2.svg b/content/blog/modulo_patterns/pattern_2.svg new file mode 100644 index 0000000..0897552 --- /dev/null +++ b/content/blog/modulo_patterns/pattern_2.svg @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/content/blog/modulo_patterns/pattern_2_29.svg b/content/blog/modulo_patterns/pattern_2_29.svg new file mode 100644 index 0000000..b23ecc6 --- /dev/null +++ b/content/blog/modulo_patterns/pattern_2_29.svg @@ -0,0 +1,242 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/content/blog/modulo_patterns/pattern_2_mod.svg b/content/blog/modulo_patterns/pattern_2_mod.svg new file mode 100644 index 0000000..8dd0cb3 --- /dev/null +++ b/content/blog/modulo_patterns/pattern_2_mod.svg @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/content/blog/modulo_patterns/pattern_4.svg b/content/blog/modulo_patterns/pattern_4.svg new file mode 100644 index 0000000..caec9d6 --- /dev/null +++ b/content/blog/modulo_patterns/pattern_4.svg @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/content/blog/modulo_patterns/pattern_4_13.svg b/content/blog/modulo_patterns/pattern_4_13.svg new file mode 100644 index 0000000..99dfa89 --- /dev/null +++ b/content/blog/modulo_patterns/pattern_4_13.svg @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/content/blog/modulo_patterns/pattern_4_mod.svg b/content/blog/modulo_patterns/pattern_4_mod.svg new file mode 100644 index 0000000..c38e01d --- /dev/null +++ b/content/blog/modulo_patterns/pattern_4_mod.svg @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/content/blog/modulo_patterns/pattern_5_17.svg b/content/blog/modulo_patterns/pattern_5_17.svg new file mode 100644 index 0000000..b248e02 --- /dev/null +++ b/content/blog/modulo_patterns/pattern_5_17.svg @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/content/blog/modulo_patterns/pattern_6_29.svg b/content/blog/modulo_patterns/pattern_6_29.svg new file mode 100644 index 0000000..4926431 --- /dev/null +++ b/content/blog/modulo_patterns/pattern_6_29.svg @@ -0,0 +1,242 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/content/blog/modulo_patterns/pattern_8.svg b/content/blog/modulo_patterns/pattern_8.svg new file mode 100644 index 0000000..158c27f --- /dev/null +++ b/content/blog/modulo_patterns/pattern_8.svg @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/content/blog/modulo_patterns/pattern_8_13.svg b/content/blog/modulo_patterns/pattern_8_13.svg new file mode 100644 index 0000000..c6c9b33 --- /dev/null +++ b/content/blog/modulo_patterns/pattern_8_13.svg @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/content/blog/modulo_patterns/pattern_8_mod.svg b/content/blog/modulo_patterns/pattern_8_mod.svg new file mode 100644 index 0000000..eb46c71 --- /dev/null +++ b/content/blog/modulo_patterns/pattern_8_mod.svg @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file