Skip to content

Instantly share code, notes, and snippets.

@g-k
Created October 29, 2012 00:01
Show Gist options
  • Save g-k/3970533 to your computer and use it in GitHub Desktop.
Save g-k/3970533 to your computer and use it in GitHub Desktop.
Graphs for Kaprekar's Routine
<!DOCTYPE html>
<html>
<head>
<title>Kaprekar's Routine</title>
<link type="text/css" rel="stylesheet" href="style.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/2.10.0/d3.v2.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.3.1/underscore-min.js"></script>
<script src="kaprekate.js"></script>
<script src="renderSequence.js"></script>
<script src="renderLineChart.js"></script>
<script src="k.js"></script>
</head>
<body>
<h1>Kaprekar's Routine</h1>
<noscript>Please enable Javascript.</noscript>
<div id="description">
<p>From the <a href="http://en.wikipedia.org/wiki/6174_(number)">Wikipedia Article</a>:
</p>
<ol>
<li>Take any number. (Leading zeros are allowed.)
<li>Arrange the digits in ascending order.
<li>Arrange the digits in descending order to get two numbers with the same number of digits (Add leading zeros if necessary).
<li>Subtract the smaller number from the bigger number.
<li>Repeat.
</ol>
</div>
<div id="input">
<p>Starting value:
<input id="seed" class="number" type="number" value="900" autofocus>
</div>
<div id="graph"></div>
</body>
</html>
var sequences = [];
var sequence;
var length = 3;
var radix = 10;
var max = Math.pow(radix, length);
for (var j = 0; j < max; j++) {
j = j.toString();
while (j.length - length) { j = '0'+j; }
sequence = kaprekarSequence(j, kaprekated);
sequences.push({
seed: j,
sequence: sequence,
lastValue: +sequence[sequence.length-1],
length: sequence.length
});
}
var addSvg = function (id, height, width) {
return d3.select(id)
.append('svg')
.attr('width', width)
.attr('height', height);
};
var init = function init() {
var seedEl = document.getElementById('seed');
var margin = { top: 20, right: 10, bottom: 20, left: 60 };
var width = 600;
var height = 800;
var svg = addSvg('#graph', height, width);
var sequence = svg.append('g')
.attr('transform', 'translate('+margin.left+',20)');
var sequenceLength = svg.append('g')
.attr('transform', 'translate('+margin.left+',60)');
var sequenceLengthOptions = {
width: 500,
height: 300,
yLabel: "Sequence Length",
yDataKey: 'length',
yTickScale: 1,
data: sequences,
selection: sequenceLength
};
renderLineChart(sequenceLengthOptions);
var sequenceEnd = svg.append('g')
.attr('transform',
'translate('+margin.left+','+(120+sequenceLengthOptions.height)+')');
var sequenceEndOptions = {
width: 500,
height: 300,
yLabel: "Sequence End or Repeat Value",
yDataKey: 'lastValue',
yTickScale: 50,
data: sequences,
selection: sequenceEnd
};
renderLineChart(sequenceEndOptions);
var run = function run(event) {
var result;
if (event.keyCode !== 13) return; // Must press enter
result = kaprekarSequence(seedEl.value, kaprekated);
renderSequence(result, sequence);
};
seedEl.addEventListener('keydown', run, false);
};
document.addEventListener('DOMContentLoaded', init, false);
var memoize = function (func, hasher, memo) {
// _.memoize but allow passing in a memo object
memo || (memo = {});
hasher || (hasher = _.identity);
return function () {
var key = hasher.apply(this, arguments);
return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
};
};
var arrayToNumber = function (a) {
// convert an array of digits to a number
var n = 0;
var place_value = 1;
var place = a.length;
while (place--) {
n += a[place] * place_value;
place_value *= 10;
}
return n;
};
var kaprekate = function(seed) {
// runs a step of kaprekar's routine on seed string and returns a string
var n,
i,
smallToLarge, // array of digits from small to large
largeToSmall; // from large to small
n = seed;
i = seed.length;
// smallToLarge = [parseInt(digit) for each (digit in n)].sort();
smallToLarge = [];
while (i--) { smallToLarge.unshift(n[i]); }
// sort() and reverse() work in-place
// so slice to make a second deep copy in memory
smallToLarge = smallToLarge.sort(); // sort as strings
largeToSmall = smallToLarge.slice().reverse();
n = arrayToNumber(largeToSmall) - arrayToNumber(smallToLarge);
n = n.toString();
// prepend zeros to maintain length
i = seed.length;
while (n.length - i) { n = '0'+n; }
return n;
};
var kaprekated = {};
var memoizedKaprekate = memoize(kaprekate, null, kaprekated);
var kaprekarSequence = function (seed, memo) {
var prev = seed;
var next;
var sequence = [];
var seen = {};
sequence.push(prev); // Include seed
while (!_.has(seen, prev)) {
next = memoizedKaprekate(prev);
seen[prev] = next;
sequence.push(next);
prev = next;
}
return sequence;
};
var renderLineChart = function (options) {
var data = options.data;
var selection = options.selection;
var height = options.height;
var width = options.width;
var x = d3.scale.linear()
.range([0, width])
.domain([parseInt(data[0].seed, 10),
parseInt(data[data.length-1].seed, 10)]);
var y = d3.scale.linear()
.range([height, 0])
.domain([0, _.max(_.pluck(data, options.yDataKey))+options.yTickScale]);
var line = d3.svg.line()
.x(function (d) {
return x(parseInt(d.seed, 10)); })
.y(function (d) { return y(d[options.yDataKey]); });
selection.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
selection.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
selection.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", -40)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text(options.yLabel);
};
var renderSequence = function (sequence, selection) {
var nodeSeparation = 50;
var nodeRadius = 4.5;
var newNodes, nodes, links;
// Redraw everything -> empty update selection
var dataKey = function (d, i) { return Math.random(); };
var linkPath = function (d, i) {
if (i > 0) {
return [
'M', (nodeSeparation*(i-1)+nodeRadius), 0,
'L', (nodeSeparation*i-nodeRadius), 0
].join(' ');
}
return null;
};
var delayByIndex = function (delay) {
return function (d, i) { return delay*i; };
};
var translateByIndex = function (d, i) {
return 'translate('+(nodeSeparation*i)+',0)';
};
var fadeOut = function (selection) {
// CSS exit class handles transition
return selection.attr('class', 'exit').remove();
};
links = selection.selectAll('.link')
.data(sequence, dataKey);
links.enter()
.append('path')
.attr('class', 'link')
.style('opacity', 0)
.transition()
.delay(delayByIndex(200))
.duration(400)
.attr('d', linkPath)
.style('opacity', 1);
links.exit().call(fadeOut);
nodes = selection.selectAll('.node')
.data(sequence, dataKey);
newNodes = nodes.enter()
.append('g')
.attr('class', 'node')
.attr('transform', translateByIndex);
newNodes.append('text')
.attr("dx", 8).attr("dy", 10)
.style('opacity', 0)
.transition()
.duration(400) // Match CSS transition duration
.delay(delayByIndex(205))
.text(function(d) { return d.toString(); })
.style('opacity', 1);
newNodes.append('circle')
.transition()
.delay(delayByIndex(200))
.duration(400)
.attr('r', nodeRadius);
nodes.exit().call(fadeOut);
};
/* From another block or d3 example*/
body {
font: 14px Helvetica Neue;
text-rendering: optimizeLegibility;
margin-top: 1em;
overflow-y: scroll;
}
svg {
font: 10px sans-serif;
border: gray 1px solid;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 1.5px;
}
.exit {
transition-property opacity;
opacity: 0;
transition-duration: 0.4s;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}
/* From: http://bl.ocks.org/3902569 */
.line {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}
.axis path, .axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.y.axis path, .x.axis path {
display: none;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment