/**
* Padvinder!
* Uit een verzameling punten die verbonden zijn met lijn,
* vindt het kortste pad tussen 2 punten.
*/
/**
* Alle padvinder-meuk zit in dit object
*/
var Padvinder = {
/**
* @var Array Punten alle punten
*/
Punten: [],
/**
* @var Array Lijnen alle lijnen
*/
Lijnen: [],
/**
* Callback voor het sorteren van 'n padvinder-object op lengte, kortste eerst
*/
kortsteEerst: function(a, b){
return a.lengte - b.lengte;
},
/**
* Punten zijn nodig om lijnen af te bakenen
*
* @param String naam de naam van dit Punt
*/
Punt: function(naam){
/**
* @var Number id waar dit punt in de Punten array te vinden is
*/
this.id;
/**
* @var Array lijnen alle lijnen aan dit punt, voor gebruiksvriendelijkheid
*/
this.lijnen = [];
/**
* @var String naam puur voor aestetische redenen
*/
this.naam;
/**
* Laat het weten dat er (nog) een lijn aan dit punt verbonden is
*
* @param Lijn lijn
*/
this.voegLijnToe = function(lijn){
// Zorg ervoor dat geen Lijn er 2x in zit
for(var x = 0; x < this.lijnen.length; x++){
if(this.lijnen[x] == lijn) return this.lijnen[x];
}
// Nu weten we dat deze Lijn uniek aan dit Punt zit
this.lijnen.push(lijn);
// Sorteer de lijnen op volgorde van lengte, kortste eerst
this.lijnen = this.lijnen.sort(Padvinder.kortsteEerst);
}
/**
* Laat het bekend zijn dat de Lijn niet langer is
* dit impliceert dus ook dat de Lijn eerst van het Punt
* wordt gehaald en daarna pas echt weggegooid wordt
*
* @param Lijn lijn
*/
this.verwijderLijn = function(lijn){
for(var x = 0; x < this.lijnen.length; x++){
if(this.lijnen[x] == lijn) this.lijnen = this.lijnen.splice(x, 1);
}
// Sorteer de lijnen op volgorde van lengte, kortste eerst
this.lijnen = this.lijnen.sort(Padvinder.kortsteEerst);
}
/**
* Voor log
*/
this.toString = function(){
return "Punt: "+this.naam;
}
if(typeof(naam) != "undefined"){
this.naam = naam;
}
},
/**
* Een lijn wordt gedefiniƫerd door twee punten
*
* @param String naam de naam van deze Lijn
* @param Punt puntA het ene uiteinde van de Lijn
* @param Punt puntB het andere uiteinde
* @param Number lengte de kosten
*/
Lijn: function(naam, puntA, puntB, lengte){
/**
* @var Number id waar deze Lijn in de Lijnen array te vinden is
*/
this.id;
/**
* @var Punt puntA het ene uiteinde van deze lijn
*/
this.puntA;
/**
* @var Punt puntB het ander uiteinde
*/
this.puntB;
/**
* @var Number lengte de kosten van deze lijn
*/
this.lengte;
/**
* @var String naam puur voor aestetische redenen
*/
this.naam;
/**
* Gegeven het ene punt, retourneer het andere
*
* @param Punt enePunt het ene punt
*/
this.anderePunt = function(enePunt){
if(this.puntA == enePunt){
return this.puntB;
}
return this.puntA;
}
/**
* Voor log
*/
this.toString = function(){
return "Lijn #"+this.naam+":"+this.lengte;
}
if(typeof(naam) != "undefined"){
this.naam = naam;
}
if(typeof(puntA) != "undefined"){
this.puntA = puntA;
// Nu kunnen we deze Lijn ook toevoegen aan dit Punt
this.puntA.voegLijnToe(this);
}
if(typeof(puntB) != "undefined"){
this.puntB = puntB;
// Nu kunnen we deze Lijn ook toevoegen aan dit Punt
this.puntB.voegLijnToe(this);
}
if(typeof(lengte) != "undefined"){
this.lengte = lengte;
}
},
/**
* Een Route beschrijft een aantal Punten en Lijnen die aan elkaar zitten
*/
Route: function(naam){
/**
* @var Array lijnen de lijnen in deze route
*/
this.pad = [];
/**
* @var Number lengte de gezamelijke lengte van de lijnen
*/
this.lengte = 0;
/**
* @var Punt puntA het ene uiteinde van deze Route
*/
this.puntA;
/**
* @var Punt puntB het andere uiteinde
*/
this.puntB;
this.naam;
/**
* Een Lijn aan deze Route toevoegen
*
* @param Lijn de lijn toe te voegen
*/
this.voegLijnToe = function(lijn, puntB){
if(this.pad.length == 0){ // Eerste lijn
this.pad.push(lijn.anderePunt(puntB));
this.puntA = this.pad[0];
this.pad.push(lijn);
this.puntB = puntB;
this.pad.push(puntB);
}else{
if(lijn.anderePunt(puntB) != this.pad[this.pad.length-1]) throw new Error("Route niet consequent");
this.pad.push(lijn);
this.puntB = puntB;
this.pad.push(puntB);
}
this.lengte += lijn.lengte;
}
/**
* Gegeven het ene punt, retourneer het andere
*
* @param Punt enePunt het ene punt
*/
this.anderePunt = function(enePunt){
if(this.puntA == enePunt){
return this.puntB;
}
if(this.puntB != enePunt) throw new Error("Punt ["+enePunt.id+"] zit niet aan deze lijn");
return this.puntA;
}
/**
* Maak een nieuwe Route gebaseerd op deze
*
* @return Route een kopie van deze
*/
this.kloon = function(){
var nieuwe = new Padvinder.Route();
nieuwe.pad = this.pad.slice(0); // Wel'n kopie maken
nieuwe.lengte = this.lengte;
nieuwe.puntA = this.puntA;
nieuwe.puntB = this.puntB;
return nieuwe;
}
/**
* Controleer of deze Route nog wel consequent is
*/
this.check = function(){
// Check beginpunt
if(this.pad[0] != this.puntA) throw new Error("Eerste element is niet puntA");
for(var x = 1; x < this.pad; x++){
// Elk even element is 'n Lijn, elk oneven 'n Punt
if(x % 2){
if(!(this.pad[x] instanceof Lijn)) throw new Error("Element ["+x+"] is even, maar geen Lijn");
// Kijk of het vorige element, 'n Punt, ook verbonden is aan deze lijn
if(this.pad[x].puntA != this.pad[x-1] && this.pad[x].puntB != this.pad[x-1])
throw new Error("Vorige Punt niet verbonden met deze Lijn ["+x+"]");
}else{
if(!(this.pad[x] instanceof Punt)) throw new Error("Element ["+x+"] is oneven, maar geen Punt");
// Kijk of het vorige element, 'n Lijn, ook aan dit Punt zit
if(this.pad[x].lijnen.indexOf(this.pad[x-1]) == -1) throw new Error("Vorige Lijn niet verbonden met dit Punt ["+x+"]");
}
}
// Check 't eindpunt
if(this.pad.last() != this.puntB) throw new Error("Laatste element is niet puntB");
}
/**
* Voor log
*/
this.toString = function(){
var str = this.pad[0].naam+" ";
for(var x = 1; x < this.pad.length; x++){
if(x % 2){
str += "--"+this.pad[x].naam+":"+this.pad[x].lengte+"-- ";
}else{
str += ""+this.pad[x].naam+" ";
}
}
return str+":: "+this.lengte;
}
if(typeof(naam) != "undefined"){
this.naam = naam;
}
},
/**
* Voegt een punt toe aan de Punten array.
* Dit is de enige juiste manier om een Punt te maken,
* anders is'ie niet bruikbaar.
*
* @param String naam de naam van het Punt (Punt.naam)
*/
voegPuntToe: function(naam){
var puntje = new Padvinder.Punt(naam);
puntje.id = Padvinder.Punten.push(puntje)-1;
return puntje;
},
/**
* Haalt een Punt weg
* Dit haalt ook alle Lijnen die aan het Punt zitten weg
*
* @param Punt punt
*/
verwijderPunt: function(punt){
// Eerst alle lijnen weghalen
for(var x = 0; x < punt.lijnen.length; x++){
Padvinder.verwijderLijn(punt.lijnen[x]);
}
Padvinder.Punten = Padvinder.Punten.splice(punt.id, 1);
punt = null;
},
/**
* Voegt een lijn toe aan de Lijnen array
* Dit is de enige juiste manier om een Lijn te maken,
* anders is'ie niet bruikbaar.
*
* @param String naam de naam van de Lijn (Lijn.naam)
* @param Punt puntA het ene uiteinde van de Lijn (Lijn.puntA)
* @param Punt puntB het andere uiteinde (Lijn.puntB)
* @param Number lengte de lengte (Lijn.lengte)
*/
voegLijnToe: function(naam, puntA, puntB, lengte){
for(var x = 0; x < Padvinder.Lijnen.length; x++){
var dezeLijn = Padvinder.Lijnen[x];
if((dezeLijn.puntA == puntA && dezeLijn.puntB == puntB) || (dezeLijn.puntA == puntB && dezeLijn.puntB == puntA)){
throw new Error("Er is al een Lijn tussen Punt ["+puntA.id+"] en Punt ["+puntB.id+"]");
return;
}
}
var lijntje = new Padvinder.Lijn(naam, puntA, puntB, lengte);
lijntje.id = Padvinder.Lijnen.push(lijntje)-1;
return lijntje;
},
/**
* Verwijdert een Lijn
*
* @param Lijn lijn
*/
verwijderLijn: function(lijn){
// Zorg dat de Punt'en het ook weten
lijn.puntA.verwijderLijn(lijn);
lijn.puntB.verwijderLijn(lijn);
Padvinder.Lijnen = Padvinder.Lijnen.splice(lijn.id, 1);
lijn = null;
},
/**
* De echte, ware, padvinder
*
* @param Punt start het begin-Punt
* @param Punt eind waar we heen moeten
* @return Array 'n lijst met Routes, gesorteerd op lengte, oplopend
*/
KortstePad: function(start, eind){
var routes = [], dezeLijn, dezeRoute, kortste, nieuweRoute, x, y;
for(x = 0; x < start.lijnen.length; x++){
dezeLijn = start.lijnen[x];
// Elke lijn is een nieuwe Route
dezeRoute = new Padvinder.Route();
routes.push(dezeRoute);
dezeRoute.voegLijnToe(dezeLijn, dezeLijn.anderePunt(start));
}
/*
De routes array is nu gevuld met alle punten die 1 punt verder dan het startpunt zijn
Nu gaan we langs die Routes en breiden we ze uit met de Lijnen en Punten die aan die punten liggen
*/
x = 0;
kortste = null; // De tot-dan-toe kortste Route die al klaar is
while(x < routes.length){
dezeRoute = routes[x];
// Deze route is klaar
if(dezeRoute.puntB == eind){
// Nieuwe kortste Route
if(dezeRoute.lengte < kortste || kortste === null){
kortste = dezeRoute.lengte;
}
x++;
continue;
}
// Als deze Route (nu al) langer is dan de kortste, sla 'm dan over
if(kortste !== null && dezeRoute.lengte > kortste){
x++;
continue;
}
/*
Ga langs alle lijnen en maak nieuwe Routes van alle lijnen die niet teruglopen naar een Punt waar we al geweest zijn
*/
for(y = 0; y < dezeRoute.puntB.lijnen.length; y++){
dezeLijn = dezeRoute.puntB.lijnen[y];
// Deze lijn gaat terug
if(dezeRoute.pad.indexOf(dezeLijn.anderePunt(dezeRoute.puntB)) != -1){
continue;
}
nieuweRoute = dezeRoute.kloon();
nieuweRoute.voegLijnToe(dezeLijn, dezeLijn.anderePunt(dezeRoute.puntB));
routes.push(nieuweRoute);
}
x++;
} // Einde while-lus
// Haal nu alle lege elementen eruit en sorteer 'm op de lengte
routes = routes.filter(function(elm){ return (elm != null && elm.puntB == eind); }).sort(Padvinder.kortsteEerst);
return routes;
}
};