theread.me/_posts/2017-10-19-tiling-shapes-proofs.md
2017-11-19 14:44:53 +03:30

369 lines
14 KiB
Markdown

---
layout: post
title: "Mathematical Induction for proving tiling methods"
date: 2017-10-19
permalink: mathematical-induction-proving-tiling-methods/
categories: math
math: true
author: Mahdi
---
On my way towards self-taught data science, I've stumbled upon the need to be proficient with mathematical proofs, so I picked up the amazing [How To Prove It: A Structured Approach](https://www.amazon.com/How-Prove-It-Structured-Approach/dp/0521675995) by Daniel J. Velleman; and I've been fascinated by mathematical proofs since then.
One of the uses for [Mathematical Induction](https://en.wikipedia.org/wiki/Mathematical_induction) which I've found to be pretty cool is proving methods for tiling shapes.
Here is an example:
Suppose $n$ is a positive integer. An equilateral triangle is cut into $4^n$ congruent equilateral triangles, and one corner is removed. Show that the remaining area can be covered by trapezoidal tiles like this: <img src='/img/tiling/trapezoidal.jpg' class='inline' width='30'/>
<canvas id='tiling-triangle' width='200' height='200' class='centered'></canvas>
{% include caption.html text='An example of n = 2. The dark tile is removed.' %}
Just to be clear, by _cover_ we mean covering without any overlaps, so you can't have two overlapping trapezoidal tiles.
As with any mathematical induction solution, we start with the base case, which is $n = 1$, in that case the triangle looks like this:
<canvas id='base-case' width='100' height='100' class='centered'></canvas>
In this case, it's obvious that we can cover the leftover area using trapezoidal tiles consisting of three equilateral triangles (the second row), so:
----
Base case: $n = 1$ then the triangle has $4^1 = 4$ tiles, and by removing the top-most tile, the second row can be covered using a single trapezoidal tile.
----
Now for the induction step, we have to somehow show that after adding 1 to $n$, that is, by multiplying the tiles by $4$, we can still cover the triangle by trapezoidal tiles. To show this, we start by assuming we have a triangle with $4^n$ tiles, which we know can be covered by trapezoidal tiles, then we add the new tiles and show that they, too, can be covered by trapezoidal tiles.
----
Induction Step: Suppose we have a triangle split into $4^n$ tiles, and we know by induction hypothesis that it can be covered by trapezoidal tiles.
Now suppose we have another triangle with $4^{n+1}$ tiles, that means, $4$ times as many triangles as our original triangle. We can then group the new bigger triangle into 4 congruent triangles, one of which we know can be split into trapezoidal tiles by removing one of it's tiles.
For the three left triangles, we can find a neighbouring corner and assume the tile on that corner to be removed, and then cover the rest by trapezoidal tiles. Afterwards, since we had three such corners, and they are neighbouring corners, we can cover these three corners with one trapezoidal tile, thus completing the triangle.
----
Now that's a mouthful, in simpler terms, we are following these steps (for $n = 2$):
<canvas id='n-2' width='200' height='200' class='centered'></canvas>
First, we split the whole triangle into 4 smaller groups:
<canvas id='n-2-grouped' width='200' height='200' class='centered'></canvas>
Then, we know that one of the triangles can be covered by trapezoidal tiles if we remove one of it's tiles, that's the induction hypothesis (the case for $4^n$ which is proved in the base case):
<canvas id='n-2-grouped-removed' width='200' height='200' class='centered'></canvas>
Leaving the top triangle behind, we now find neighbouring corners among the three left triangles, and we assume the tiles in those corners to be removed (they are not actually removed as we are constrained to remove only one tile):
<canvas id='n-2-grouped-neighbours' width='200' height='200' class='centered'></canvas>
Now we can cover the rest of these triangles by a single trapezoidal tile, similar to the case of $n = 1$.
Afterwards, we see that the three neighbouring tiles form a trapezoidal tile, therefore we can now put the last piece there to complete the tiles.
<canvas id='final' width='200' height='200' class='centered'></canvas>
This procedure can be applied recursively on larger values of $n$ as well, so this concludes a proof of tiling an equilateral triangle divided into $4^n$ equilateral triangles using trapezoidal tiles after removing a single piece.
<script>
(function() {
var tilingTriangle = document.getElementById('tiling-triangle');
var c = tilingTriangle.getContext('2d');
function TriangleCanvas(id) {
this.element = document.getElementById(id);
this.context = this.element.getContext('2d');
}
function modifyColor(c, p) {
var e = document.createElement('i');
e.style.background = c;
var r = getComputedStyle(e).backgroundColor.slice(4, -1).split(',').map(parseFloat);
return 'rgb(' + [r[0] + p, r[1] + p, r[2] + p].join(',') + ')';
}
function dedup(list) {
return list.reduce(function(newList, item) {
if (!newList.some(function(a) { return deepCompare(a, item) })) {
return newList.concat(item);
}
return newList;
}, []);
}
function deepCompare () {
var i, l, leftChain, rightChain;
function compare2Objects (x, y) {
var p;
// remember that NaN === NaN returns false
// and isNaN(undefined) returns true
if (isNaN(x) && isNaN(y) && typeof x === 'number' && typeof y === 'number') {
return true;
}
// Compare primitives and functions.
// Check if both arguments link to the same object.
// Especially useful on the step where we compare prototypes
if (x === y) {
return true;
}
// Works in case when functions are created in constructor.
// Comparing dates is a common scenario. Another built-ins?
// We can even handle functions passed across iframes
if ((typeof x === 'function' && typeof y === 'function') ||
(x instanceof Date && y instanceof Date) ||
(x instanceof RegExp && y instanceof RegExp) ||
(x instanceof String && y instanceof String) ||
(x instanceof Number && y instanceof Number)) {
return x.toString() === y.toString();
}
// At last checking prototypes as good as we can
if (!(x instanceof Object && y instanceof Object)) {
return false;
}
if (x.isPrototypeOf(y) || y.isPrototypeOf(x)) {
return false;
}
if (x.constructor !== y.constructor) {
return false;
}
if (x.prototype !== y.prototype) {
return false;
}
// Check for infinitive linking loops
if (leftChain.indexOf(x) > -1 || rightChain.indexOf(y) > -1) {
return false;
}
// Quick checking of one object being a subset of another.
// todo: cache the structure of arguments[0] for performance
for (p in y) {
if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
return false;
}
else if (typeof y[p] !== typeof x[p]) {
return false;
}
}
for (p in x) {
if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
return false;
}
else if (typeof y[p] !== typeof x[p]) {
return false;
}
switch (typeof (x[p])) {
case 'object':
case 'function':
leftChain.push(x);
rightChain.push(y);
if (!compare2Objects (x[p], y[p])) {
return false;
}
leftChain.pop();
rightChain.pop();
break;
default:
if (x[p] !== y[p]) {
return false;
}
break;
}
}
return true;
}
if (arguments.length < 1) {
return true; //Die silently? Don't know how to handle such case, please help...
// throw "Need two or more arguments to compare";
}
for (i = 1, l = arguments.length; i < l; i++) {
leftChain = []; //Todo: this can be cached
rightChain = [];
if (!compare2Objects(arguments[0], arguments[i])) {
return false;
}
}
return true;
}
TriangleCanvas.prototype.drawTriangle = function(x, y, size, fill, reversed) {
var c = this.context;
var corners = [{
x: x,
y: y,
}, {
x: x - size / 2,
y: y + size,
}, {
x: x + size / 2,
y: y + size,
}];
if (reversed) {
corners = [{
x: x + size / 2,
y: y,
}, {
x: x - size / 2,
y: y,
}, {
x: x,
y: y + size
}];
}
if (fill) {
this.drawShape(corners, modifyColor(fill, 20), fill);
} else {
this.drawShape(corners);
}
}
TriangleCanvas.prototype.drawTrapezoid = function(tiles, size, strokeStyle, fillStyle) {
var corners = tiles
.map(function(o) {
return { x: o[0] * size, y: o[1] * size };
});
console.log(corners);
this.drawShape(corners, strokeStyle, fillStyle);
}
TriangleCanvas.prototype.drawShape = function(corners, strokeStyle, fillStyle) {
var c = this.context;
c.beginPath();
c.moveTo(corners[0].x, corners[0].y);
corners.slice(1).concat([corners[0]]).forEach(function(object, index) {
c.lineTo(object.x, object.y);
});
c.closePath();
if (fillStyle) {
c.fillStyle = fillStyle;
c.fill();
}
if (strokeStyle) c.strokeStyle = strokeStyle;
c.stroke();
}
TriangleCanvas.prototype.drawSplittedTriangle = function(x, y, size, split, blocks, rest) {
var c = this.context;
var rows = Math.sqrt(split);
var rowHeight = size / rows;
var triangleSize = size / rows;
for (var i = 0; i < rows * 2; i += 2) {
var row = Math.floor(i / 2);
for (var j = 0; j < i + 1; j++) {
var position = {
x: x + triangleSize * j / 2 - (row * triangleSize / 2),
y: y + row * rowHeight,
};
var block = blocks.reduce(function(c, a) {
return (a[0] === row && a[1] === j) ? (a[2] || 'black') : c;
}, null);
this.drawTriangle(position.x, position.y, triangleSize, block || rest, j % 2 == 1);
}
}
}
var c1 = new TriangleCanvas('tiling-triangle');
c1.drawSplittedTriangle(100, 0, 200, Math.pow(4, 2), [[0, 0, '#435062']], '#92afd7');
var c2 = new TriangleCanvas('base-case');
c2.drawSplittedTriangle(50, 0, 100, 4, [[0, 0, '#435062']], '#92afd7');
var c3 = new TriangleCanvas('n-2');
c3.drawSplittedTriangle(100, 0, 200, Math.pow(4, 2), [[0, 0, '#435062']], '#92afd7');
var c4 = new TriangleCanvas('n-2-grouped');
var groups = [
[0, 0, '#809bce'], [1, 0, '#809bce'], [1, 1, '#809bce'], [1, 2, '#809bce'],
[2, 0, '#95b8d1'], [3, 0, '#95b8d1'], [3, 1, '#95b8d1'], [3, 2, '#95b8d1'],
[2, 1, '#b8e0d2'], [2, 2, '#b8e0d2'], [2, 3, '#b8e0d2'], [3, 3, '#b8e0d2'],
[2, 4, '#d6eadf'], [3, 4, '#d6eadf'], [3, 5, '#d6eadf'], [3, 6, '#d6eadf'],
];
c4.drawSplittedTriangle(100, 0, 200, Math.pow(4, 2), groups);
var c5 = new TriangleCanvas('n-2-grouped-removed');
var groups = [
[0, 0, 'rgb(90, 90, 90)'], [1, 0, '#809bce'], [1, 1, '#809bce'], [1, 2, '#809bce'],
[2, 0, '#95b8d1'], [3, 0, '#95b8d1'], [3, 1, '#95b8d1'], [3, 2, '#95b8d1'],
[2, 1, '#b8e0d2'], [2, 2, '#b8e0d2'], [2, 3, '#b8e0d2'], [3, 3, '#b8e0d2'],
[2, 4, '#d6eadf'], [3, 4, '#d6eadf'], [3, 5, '#d6eadf'], [3, 6, '#d6eadf'],
];
c5.drawSplittedTriangle(100, 0, 200, Math.pow(4, 2), groups);
c5.context.strokeStyle = '#ff7777';
c5.context.lineWidth = 3;
c5.drawTriangle(100, 0, 100);
var c6 = new TriangleCanvas('n-2-grouped-neighbours');
var groups = [
[0, 0, '#e7ecf6'], [1, 0, '#e7ecf6'], [1, 1, '#e7ecf6'], [1, 2, '#e7ecf6'],
[2, 0, '#95b8d1'], [3, 0, '#95b8d1'], [3, 1, '#95b8d1'], [3, 2, '#6d8699'],
[2, 1, '#b8e0d2'], [2, 2, '#b8e0d2'], [2, 3, '#b8e0d2'], [3, 3, '#657b73'],
[2, 4, '#d6eadf'], [3, 4, '#89958e'], [3, 5, '#d6eadf'], [3, 6, '#d6eadf'],
];
c6.drawSplittedTriangle(100, 0, 200, Math.pow(4, 2), groups);
var trapezoids = [
[[1, 2], [3, 2], [2.5, 3], [1.5, 3]],
[[1, 2], [1.5, 3], [1, 4], [0, 4]],
[[3, 2], [4, 4], [3, 4], [2.5, 3]],
];
c6.drawTrapezoid(trapezoids[0], 200 / 4, '#ff7777');
c6.drawTrapezoid(trapezoids[1], 200 / 4, '#ff7777');
c6.drawTrapezoid(trapezoids[2], 200 / 4, '#ff7777');
var c7 = new TriangleCanvas('final');
var groups = [
[0, 0, '#435062'], [1, 0, '#92afd7'], [1, 1, '#92afd7'], [1, 2, '#92afd7'],
[2, 0, '#95b8d1'], [3, 0, '#95b8d1'], [3, 1, '#95b8d1'], [3, 2, '#95b8d1'],
[2, 1, '#b8e0d2'], [2, 2, '#b8e0d2'], [2, 3, '#b8e0d2'], [3, 3, '#b8e0d2'],
[2, 4, '#d6eadf'], [3, 4, '#d6eadf'], [3, 5, '#d6eadf'], [3, 6, '#d6eadf'],
];
c7.drawSplittedTriangle(100, 0, 200, Math.pow(4, 2), groups);
var trapezoids = [
[[1.5, 1], [2.5, 1], [3, 2], [1, 2]],
[[1, 2], [3, 2], [2.5, 3], [1.5, 3]],
[[1, 2], [1.5, 3], [1, 4], [0, 4]],
[[3, 2], [4, 4], [3, 4], [2.5, 3]],
[[1.5, 3], [1, 4], [3, 4], [2.5, 3]],
];
trapezoids.forEach(function(el) {
c7.drawTrapezoid(el, 200 / 4, '#ff7777');
});
}());
</script>