Work in progress.
A Pen by Keegan Brown on CodePen.
Work in progress.
A Pen by Keegan Brown on CodePen.
<div class="left"> | |
<h1>Input</h1> | |
<div id="input"></div> | |
<hr /> | |
<h1>Distance Table</h1> | |
<div id="distance"></div> | |
<hr/> | |
<div class="left"> | |
<h1>Lowest Solution:</h1> | |
<p id="lowestSolution"></p> | |
</div> | |
<div class="right"> | |
<h1>Permutations: ~<span id="numPerms"></span></h1> | |
<hr/> | |
<h1 id="possiblePerms"></h1> | |
</div> | |
</div> | |
<div class="right"> | |
<h1>Chart</h1> | |
<canvas id="chart"></canvas> | |
<h2>Batch Lowest: <span id="lastSolve"></span> | Lowest Solve: <span id="lowestSolve"></span></h2> | |
<hr> | |
<a href="#" onclick="location.href = (location.href)">refresh</a> | |
</div> |
// requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel | |
// MIT license | |
(function(){var lastTime=0;var vendors=["ms","moz","webkit","o"];for(var x=0;x<vendors.length&&!window.requestAnimationFrame;++x){window.requestAnimationFrame=window[vendors[x]+"RequestAnimationFrame"];window.cancelAnimationFrame=window[vendors[x]+"CancelAnimationFrame"]||window[vendors[x]+"CancelRequestAnimationFrame"]}if(!window.requestAnimationFrame)window.requestAnimationFrame=function(callback,element){var currTime=(new Date).getTime();var timeToCall=Math.max(0,16-(currTime-lastTime));var id= | |
window.setTimeout(function(){callback(currTime+timeToCall)},timeToCall);lastTime=currTime+timeToCall;return id};if(!window.cancelAnimationFrame)window.cancelAnimationFrame=function(id){clearTimeout(id)}})(); | |
var input = document.getElementById("input"); | |
var distance = document.getElementById("distance"); | |
var chart = document.getElementById("chart"); | |
var lowestSolveEle = document.getElementById("lowestSolve"); | |
var lastSolveEle = document.getElementById("lastSolve"); | |
var lowestSolve = 999999999; | |
var lowestSolveData = null; | |
var data = generateData(10, 100); | |
data = data.sort(sortCities); | |
var distancedata = []; | |
function generateData(numCities, distmax) { | |
var outArr = []; | |
for ( var i = 0; i < numCities; i++ ) { | |
outArr.push( | |
padLeadingZeros( Math.round(Math.random()*distmax), 4 )+","+ padLeadingZeros( Math.round(Math.random()*distmax), 4 ) | |
); | |
} | |
return outArr; | |
} | |
function padLeadingZeros(num, size) { | |
var s = "000000000" + num; | |
return s.substr(s.length-size); | |
} | |
function getDistance ( x1, y1, x2, y2 ) { | |
var a = parseFloat(x2, 10) - parseFloat(x1, 10); | |
var b = parseFloat(y2, 10) - parseFloat(y1, 10); | |
return Math.round( Math.sqrt((a*a)+(b*b)) ); | |
} | |
function outputCities (data) { | |
var out = []; | |
out.push("<ol>"); | |
for ( var i in data ) { | |
out.push( "<li>" + data[i] + "</li>" ); | |
} | |
out.push("</ol>"); | |
return out.join(" \n "); | |
} | |
function outputDistanceTable ( data ) { | |
var out = []; | |
out.push("<table>"); | |
for ( var i in data ) { | |
out.push( "<tr>" ); | |
distancedata.push([]); | |
for ( var j in data ) { | |
var city1 = data[i].split(","); | |
var city2 = data[j].split(","); | |
out.push( '<td class="'+getCityClass(data[i])+' '+getCityClass(data[j])+'">' + getDistance( city1[0], city1[1], city2[0], city2[1] ) + "</td>" ); | |
distancedata[i][j] = getDistance( city1[0], city1[1], city2[0], city2[1] ); | |
} | |
out.push( "</tr>" ); | |
} | |
out.push("</table"); | |
return out.join(" \n "); | |
} | |
function getCityClass (city) { | |
return "city_"+city.replace(",","_"); | |
} | |
function getCityObj (city) { | |
var cityArr = city.split(","); | |
return { x: parseFloat(cityArr[0], 10), y: parseFloat(cityArr[1], 10) } | |
} | |
function factorial(n) { | |
if (n <= 1) return 1; | |
return n*factorial(n-1); | |
} | |
function drawCities (canvas) { | |
document.getElementById("possiblePerms").innerHTML = "Total Possible Permutations: "+factorial(data.length); | |
var scale = 5; | |
canvas.height=100*scale; | |
canvas.width=100*scale; | |
var ctx = canvas.getContext("2d"); | |
ctx.fillStyle = "#000000"; | |
var city = []; | |
var cityProp = {x:0, y:0}; | |
for ( var i in data ) { | |
city = data[i].split(","); | |
cityProp = {x:parseFloat(city[0],10), y:parseFloat(city[1],10)} | |
ctx.fillRect((cityProp.x*scale)-5,(cityProp.y*scale)-5,10,10); | |
} | |
} | |
var solveCertainty = 0.01; | |
function drawSolution (canvas, data, lineWidth, hue) { | |
var scale = 5; | |
var thisSolve = 0; | |
var ctx = canvas.getContext("2d"); | |
var newData = (data.join("|")).split("|"); | |
var cityObj = {x:0,y:0}; | |
var firstCity = getCityObj( newData[0] ); | |
var lastCity = getCityObj( newData.pop() ); | |
ctx.moveTo( lastCity.x*scale, lastCity.y*scale ); | |
ctx.beginPath(); | |
ctx.lineTo( lastCity.x*scale, lastCity.y*scale ); | |
var fullpath = true; | |
while ( !!newData.length ) { | |
cityObj = getCityObj( newData.pop() ); | |
ctx.lineTo( cityObj.x*scale, cityObj.y*scale ); | |
thisSolve += getDistance( cityObj.x, cityObj.y, lastCity.x, lastCity.y ); | |
if ( thisSolve > lowestSolve ) { | |
fullpath = false; | |
newData = []; | |
} | |
lastCity = cityObj; | |
} | |
if ( fullpath ) { | |
ctx.lineTo( firstCity.x*scale, firstCity.y*scale ); | |
thisSolve += getDistance( firstCity.x, firstCity.y, lastCity.x, lastCity.y ); | |
if ( thisSolve > lowestSolve ) { | |
fullpath = false; | |
} | |
} | |
writeLowestSolve( thisSolve, (data.join("|")).split("|") ); | |
if ( fullpath ) { | |
ctx.lineWidth = lineWidth; | |
ctx.strokeStyle = "hsla("+hue+",70%,60%,"+solveCertainty+")"; | |
ctx.stroke(); | |
} | |
ctx.closePath(); | |
return [ thisSolve, (data.join("|")).split("|") ]; | |
} | |
function writeLowestSolve ( newSolve, newData ) { | |
if ( newSolve < lowestSolve ) { | |
solveCertainty += 0.01; | |
lowestSolveEle.innerHTML = newSolve; | |
lowestSolve = newSolve; | |
lowestSolveData = newData; | |
document.getElementById("lowestSolution").innerHTML = newData.join(" \<br\/\>"); | |
} | |
} | |
function writeLastSolve ( newSolve, newData ) { | |
lastSolveEle.innerHTML = newSolve; | |
} | |
function permValue ( input ) { | |
var _a = ( input ).split(","); | |
return parseInt(_a[0]) + parseInt(_a[1]); | |
} | |
function nextPermutation(permArr) { | |
var i = permArr.length - 1; | |
while (i > 0 && permValue( permArr[i - 1] ) >= permValue( permArr[i] ) ) { | |
i--; | |
} | |
if (i == 0) { | |
return false; | |
} | |
var j = permArr.length - 1; | |
while ( permValue( permArr[j] ) <= permValue( permArr[i - 1] ) ) { | |
j--; | |
} | |
var temp = permArr[i - 1]; | |
permArr[i - 1] = permArr[j]; | |
permArr[j] = temp; | |
j = permArr.length - 1; | |
while (i < j) { | |
temp = permArr[i]; | |
permArr[i] = permArr[j]; | |
permArr[j] = temp; | |
i++; | |
j--; | |
} | |
return true; | |
} | |
function sortCities (a,b) { | |
if ( permValue(a) < permValue(b) ) return -1; | |
if ( permValue(a) > permValue(b) ) return 1; | |
return 0; | |
} | |
var batches = 0; | |
function nextOnRaf () { | |
batches++; | |
var batch = 2000; | |
var numbatch = batch; | |
var continueBatch = true; | |
var tempSolve; | |
while ( batch-- ) { | |
continueBatch = nextPermutation(data); | |
if ( continueBatch ) { | |
//console.log(data); | |
var _temp = drawSolution(chart,data,2,10); | |
if ( !tempSolve || ( !!tempSolve && _temp[0] < tempSolve[0] ) ) { | |
tempSolve = _temp; | |
} | |
} else { | |
console.log( "DONE! Last Solve: ", tempSolve ); | |
batch = 0; | |
} | |
} | |
document.getElementById("numPerms").innerHTML = (batches*numbatch)+" <br/> ["+batches+"•"+numbatch+"]"; | |
if (!!tempSolve) { | |
writeLastSolve(tempSolve[0], tempSolve[1]); | |
} | |
if ( continueBatch ) { | |
//console.log(batches); | |
requestAnimationFrame( nextOnRaf ); | |
} else { | |
solveCertainty = 1; | |
drawSolution(chart,lowestSolveData,5,128); | |
console.log( "End Run!" ); | |
} | |
} | |
input.innerHTML = outputCities( data ); | |
distance.innerHTML = outputDistanceTable( data ); | |
drawCities(chart); | |
requestAnimationFrame( nextOnRaf ); |
body { | |
font-family: monospace; | |
} | |
table td { | |
text-align: right; | |
padding: 5px; | |
border: 1px solid #DDD; | |
} | |
#chart { | |
height: 500px; | |
width: 500px; | |
display: block; | |
border: 1px solid #000; | |
} | |
.left, .right { | |
width: 50%; | |
margin: 0; | |
padding: 0; | |
float: left; | |
border: 1px solid #DDD; | |
box-sizing: border-box; | |
} |
A Pen by Keegan Brown | |
--------------------- | |
A [Pen](http://codepen.io/keeganbrown/pen/lLhjt) by [Keegan Brown](http://codepen.io/keeganbrown) on [CodePen](http://codepen.io/). | |
[License](http://codepen.io/keeganbrown/pen/lLhjt/license). |