Algorithmes fonctionnels en javascript sous la forme de cours et d'exercices. Utilisation de js dans un contexte de liste, de map, list_it. Inspiré de caml. A lire à l'envers...

samedi 3 octobre 2009

Les expressions régulières.

Expressions régulières.

Une petite digression sur les expressions régulières, aussi appelées expressions rationnelles ou regex..
Les expressions régulières correspondent à un mini langage bien spécifique qui est le langage des automates. Ici automate  est utilisé au sens mathématique  du terme. Dans un "automate fini déterministe", un mot u est reconnu si il fait passer l'automate d'un état initial à son état final.
Les expressions régulières sont utilisées en programmation pour reconnaître des motifs particuliers. Ainsi  les mots "unix" et "linux" sont reconnus par un automate dont le langage est  "*n.x" . "*n.x" est une expression régulière.

Construction d'expressions régulières:

en ligne: var regex = /test/igm ;
à l'aide d'un constructeur: var regex = new RegExp("test","igm");

En pratique 6 méthodes javascript permettent d'utiliser les expressions régulières

- 2 sont des méthodes de l'objet Regexp exec et test:
var regex = /t(r+)/ig ; // objet RegExp
regex.exec("Do it or not but there is no try"/* optionnel: , ig */);
regex.test("Try it or not");//true or false
- 4 sont des méthodes de l'objet String: match, search, replace, split.
str.match(regex);
str.search(regex);// retourne un entier
str.replace(regex,replacement ); // replacement un entier ou une fonction..
str.split(regex);// renvoi une liste.

En pratique de nombreux sites expliquent la formation d'une expression régulière, c'est un outil très puissant en programmation car il permet de selectionner rapidement et avec précision des éléments donnés dans un texte.

Ce qui est un peu plus intéressant est la notion d'alphabet et d'automate qui est à la base des moteurs d'expressions régulières. Bien sûr, il est probable que les implémentations de javascript utilisent une bibliothèque C d'expressions régulières.

Automate fini déterministe:


Un AFD définit sur un alphabet A de la manière suivante:
{Q, q0 , F, delta}
Q : est l'ensemble fini des états de l'automate.
q0: est l'état initial de l'automate.
F: est l'ensemble des états finals. F est inclus dans Q .
delta : est une fonction de transition de Q*A -> Q, telle que delta(q,c)-> q'
ou encore qui permet de passer d'un état q à un état q' lors de la lecture du caractère c.

Programmation d'un automate fini déterministe en javascript:



//Objet AFD, si on se contente de numéroter les états de l'automate:
var alpha ={ initial, final, transitions }
//où initial est un entier,
//final est la liste des états finaux,
//transitions est une liste de tableaux associatifs

Exemple de lecture d'un mot .

function calcul(alpha, q, mot){
var n= mot.length;
function avancer(q,i){
if(i==n)return q ;
if(typeof alpha.transitions[i])[mot[i]] =="undefined" ) throw "blocage";
avancer( (alpha.transitions[i])[mot[i]] , i+1);
}
}
//exemple automate à 3 états, 0->1->2
// alphabet a,b
var alpha ={
initial: 0,
finals: [2],
[{a:1, b:0},{a:0,b:2},{a:1, :b:2}]
}


Exercice: Tracez le schéma de cet automate et trouvez les mots qu'il reconnait en sachant que sont état final est l'état 2 et l'état initial l'état 0.

Réponse:
/b*a(aa)*b+(ab+)*/


lundi 16 février 2009

Fonctions en javascript: re map, reduce, foreach, compose

Finalement, on remet maintenant une petite couche sur les fonctions en javascript.
On a vu que les fonctions sont des éléments de "première classe" en javascript. On peut revoir maintenant les principales fonctions utilisées en programmation fonctionnelle, et la façon de les nommer suivant les langages:
Les fonctions sont largement inspirés de l'excellent: "Eloquent Javascript"
Le but de ce chapître est de se familiariser avec la terminologie habituelle des librairies javascripts en particulier:
  • map
  • each ou forEach
  • reduce
  • compose
  • filter
  • any
  • every


La fonctionnelle map:


La fonction map, prend en argument une liste et une fonction et à chaque élément de cette liste, applique la fonction. Elle retourne une nouvelle liste on peut
donc résumer la fonction ainsi:
map : liste fn -> liste .
Un exemple:

map = function(fun ,liste){
var resultat=[] ;
for(var i=0 ; i< liste.length ; i++) resultat.push(fun(liste[i]) ;
return resultat ;
}
// application
map( function(x){ return x+1 ;} , [1,3,5,7,9]) ; //[2,4,6,7,10]

Firefox possède la fonction map en standard, comme une des propriétés de l'objet Array .

// Firefox :
var incremente = function(n){ return n+1 ;} ;
[2, 4, 6, 8].map(incremente) ; // [3,5,7,9]
// IE ... n'existe pas .

Pour doter n'importe quel navigateur de cette fonction il faut utiliser le prototype sur l'objet Array , en effet:
[1,3].constructor == Array ;
donc pour bénéficier de Array.map ; il faut écrire la fonction suivante:

Array.prototype.map = function(fun){
var resultat =[] ;
for(var i=0; i< liste.length ; i++)
resultat.push(fun(liste[i]) ;

return resultat ;
}
// Certain poussent le bouchon et rajoutent des variables optionnelles:
Array.prototype.map = function(fun , i , debut, fin , increment ){
var resultat =[] ;
var debut= debut || 0 ;
var fin= fin||liste.length ;
var increment = increment || 1 ;
for(var i=debut; i< fin ; i+= increment){
resultat.push(fun(liste[i],i) ;
}

return resultat ;
}

Les fous de sécurité rajouteront même un try/catch sur la ligne resultat.

La fonctionnelle do_list, foreach


On a vu que map renvoi un tableau, parfois ce n'est pas nécessaire. On peut écrire ainsi:

var do_list = function(fun, liste){
for(var i=0 ; i< liste.length; i ++){
fun( liste[i] ) ;
}
return ;
}
// mootools utilise cette fonction :
forEach: function(fn, bind){
for (var i = 0, l = this.length; i < l; i++) fn.call(bind, this[i], i, this);
}


La plupart des librairies javascript utilisent d'autres appellation pour do_list, foreach, each.

La fonction reduce, list_it ou it_list :


Nous avions vu la fonction de caml list_it et la fonction it_list, cette fonction applique une fonction binaire (prenant deux arguments) à tous les éléments successifs d'une liste:

function reduce(liste, base, f){
foreach(function(element){ base= f(element,base); } ,liste );
return base;
}
//Exemple:
function add(x,y){ return x+y;}
reduce([1,4,4,3] , 0 , add); //12

En général l'élement de base est l'élément neutre pour l'opération, ici 0 pour l'addition.
Mais attention ! reduce n'est pas une fonction commutative ! l'ordre d'appartition des opérateurs est important si la fonction de base n'est pas commutative (exemple la soustraction)
Autre exemple:

function lt(x,y){ return x < y && x || y ;};
reduce([4,4,3,1] , Infinity , lt);//3


Partial ou curryfication:



function partial(func) {
var fixedArgs = asArray(arguments, 1);
return function(){
return func.apply(null, fixedArgs.concat(asArray(arguments)));
};
}
// souvenez vous arguments n'est pas un vrai tableau
// et il faut le transformer en tableau
function asArray(quasiArray, start) {
var result = [];
for (var i = (start || 0); i < quasiArray.length; i++)
result.push(quasiArray[i]);
return result;
}
//exemple
add2 = partial(add,2);
add2(3); //5


Composée de fonction, "f rond g":


Pour composer la fonction (f o g)(arguments) on peut écrire:
 
function compose(f,g){
return function(){ f( g.apply(null,arguments) ); };
}
// Un exemple simple:
fog = compose( alert , add );
fog(1,3); // alert(4);

Fonction filter:


Filtrer une liste suivant un prédicat:

// exemple:
function filter(liste, predicat){
return reduce(liste,[], function(e,t){ return predicat(e)? t.push(e)&& t: t;}) ;
}
filter([1,3,4,5], function(x){ return x>3;}); // [4,5]

Fonction any


On cherche ici s'il y a un élément de la liste qui répond à un prédicat, la signature est du type liste * function -> boolean

function any(liste, predicat){
return reduce(liste, false, function(e, bool){ return predicat(e)||bool ;});
}
//exemple
any([1,3,4,5], function(x){ return x>1;}); // true;
//qui montre que notre fonction mérite un peu d'optimisation car
// même si le premier résultat est juste, elle boucle jusqu'au bout.
// La solution est d'utiliser une exception:
function any(liste, predicat){
try{
return reduce(liste,
false,
function(e, bool){
if(predicat(e)) throw "found" ; return false ;});
}catch(e){ return e=="found" ;}
}

any([1,3,4,5], function(x){ return x>1;});

L'inverse de any. every


Ici même chose en sens inverse tout doit répondre au prédicat:

function every(liste, predicat){
try{
return reduce(liste,
true,
function(e, bool){
if(!predicat(e)) throw "nfound" ; return true ;});
}catch(e){ if(e=="nfound")return false; else throw e ; }
}


De nombreuses variantes des mêmes fonctions existent, en fait 3 "fonctionnelles" permettent de reconstituer l'ensemble map, foreach et reduce.
Les différentes librairies, prototypes, jquery utilisent largement ces bibliothèques.
Enfin il existe certaines librairies qui transforme les expressions du type x -> x*x en fonction.. pas mal non ?

samedi 24 janvier 2009

Objets et javascript

Comment définit on un objet en javascript ?

Pour continuer, un petit guide de survie sur les objets en javascript était nécessaire. Pour bien comprendre les objets il faut utiliser la console firebug et tester à outrance les morceaux de code. Il faut comprendre, que sur une page web, on est en permanence dans un sous objet qui est le contexte d'exécution: l'objet window. Un peu comme un électron tourne autour d'un atome placé dans un manège qui tourne sur la terre qui tourne autour du soleil.. etc...Il existe beaucoup de dogmes sur la programmation objet, qui passe pour beaucoup de programmeur comme le nirvana à atteindre... Certes, la programmation objet présente de nombreux avantages, elle facilite la modularité et la ré-utilisation de fragments de programmes; pour autant elle ne garantie pas la bonne marche du programme. Et surtout, un conseil, fuyez les dogmes en matière de programmation .. faire comme ci/ faire comme cela, la programmation est un art, pas une série de recettes de cuisine à suivre !

Un objet est une collection de paires 'propriété: valeurs', chacune de ces valeurs peut prendre elle même la forme d'un objet ou d'une fonction ou d'un des types javascript. La définition d'un objet en javascript répond à la notion d'arbre récursif.

Les propriétés de cet objet peuvent être soient numérotées comme pour les tableaux, qui ont pour constructeur Array(), soient nommées et stockées sous la forme d'un "tableau associatif" ( monTableau['mon_element'] ) et donc avoir un constructeur du type Object(). Dans les deux cas, la fonction typeof retourne le type "object".

Lorsqu'on se trouve dans un contexte d'objets les fonctions prennent le nom de "méthodes", nous utiliserons donc indifféremment les mots méthodes et fonctions.

Contrairement aux objets java ou C++ qui sont immuables une fois créés, on peut à tout moment modifier un objet et lui rajouter ou retirer des propriétés (donc des fonctions ou méthodes). Certains évitent de parler de classe en javascript car cette notion est différente de celle des langages procéduraux classiques.

Créations d'objets vides en javascript

Il existe ne nombreuses façons de créer un objet vide en javascript :
  • En écriture directe au standard JSON en écrivant var o ={}
  • Strictement équivalente à var o = new Object()
  • Soit en créant une fonction, puis en créant une instance de l'objet avec le mot clé "new". Dans ce cas, la fonction créatrice est appelée "constructeur".
Chaque objet en javascript possède une propriété cachée: "constructor" qui permet de déceler comment l'objet a été construit.


// Création d'un objet vide:
var monObjet={} ; // monObject.constructor // Object()
var monObjet= new Object(); // identique

// Création à partir d'une fonction anonyme :
var points = function(){} ; // pour le moment points est une fonction anonyme
typeof points ; // "function" ; Classe est une variable avec fonction anonyme.
points.constructor; // Function(), noter le F majuscule.
var p = new point() ; // maintenant c'est un object
typeof p ; //"object"
typeof p.constructor; // "function" .. pas étonnant.
// p.constructor et points sont identiques


// Création à partir d'une fonction nommée:
function Points(){} ; // Points est une fonction nommée.
Points.constructor ; // Function()
var p= new Points() ; // objet
p.constructor ; // Points()




Créations d'objets 'pleins'


Plusieurs méthodes sont utilisées pour créer un objet:
  • La notation JSON, qui créée un objet unique de toute pièce en angais :"object litterals"
  • L'utilisation du préfixe new appliqué à une fonction anonyme ou nommée, qui devient alors le constructeur de l'objet,l'utilisation du mot-clé "this" permet de préciser la portée des variables (publique ou privée). Dans ce cas on a l'habitude d'écrire la fonction constructice avec la première lettre en majuscule.
  • Enfin on peut combiner plusieurs techniques



//La notation JSON :permet de créer un objet unique.
monAuto ={
vehicule: 'voiture',
moteur :{ carburant: 'essence', puissance: 4} ,
couleur: 'rouge',
bruit: function(){ return "beq-beq-beq-beq"}
}

monAuto.constructor; // Object()

// On accède ainsi aux propriétés avec la notation point.
console.log("Mon auto est de couleur "+ monAuto.couleur );

// Création d'un objet avec une fonction et le mot clé this:
// la fonction est un constructeur d'objets .
var Moteurs = function(carburant, puissance){
this.carburant= carburant; // variable publique.
this.puissance= puissance;//variable publique.
var etat= "off"; // variable privee, inaccessible de l'extérieur.
// creation de méthodes publiques à l'aide de "this":
this.demarrer= function(){ etat="on" ;} ;
this.arreter = function(){ etat="off";} ;
this.getEtat = function(){ return etat ;};

}
Moteurs.constructor; // Function()
// le constructeur est une fonction anonyme.

m = new Moteurs("essence", "4") ; // nouvelle instance de Moteurs
m.constructor // function() // avec un f minuscule.
m.demarrer();
n = new Moteurs("essence", "4") ;
n.getEtat(); //"off"
m.getEtat(); //"on"



Ajouts de propriétés à un ou plusieurs objets javascript et utilisation du prototype:


On voit plus haut deux objets définis de façon différente, le premier, est un objet unique. On aurait pu également le définir en ajoutant progressivement des éléments supplémentaires sous forme d'un tableau associatif ou avec la notation point, mais attention, dans ce cas l'objet modifié est unique il est mutant par rapport aux autres objets construits de la même façon.

function station(l){return alert("servez-moi "+ l +" litres d' "+ this.carburant) }
monAuto = new Object() ;
monAuto.vehicule = 'voiture'; // équivalent de monAuto['vehicule']
monAuto.carburant = "essence";
monAuto.demarrage= function(){ return "beq-beq-beq-beq" };
monAuto.station = station ; // on lui assigne la fonction station()

monAuto.station(20) ;// Exécution à l'aide de l'opérateur ()


Le prototype permet d'éviter la mutation individuelle de l'objet et la transformer en mutation de classe. Si on applique à un objet,-- à condition qu'il soit créé par une fonction -- une méthode qui n'est pas définie dans son tableau associatif d'origine, elle le cherchera dans son prototype.
Il est donc facile d'utiliser le prototype pour étendre une classe d'objets déjà instanciés. (Ce qui est ahurissant pour une classe en java par exemple).


var Auto= function(){} ; // c'est une fonction
//Un peu d'introspection:
typeof Auto.constructor ; // "function"
typeof Auto.prototype; // "object" : il existe donc un prototype
// lié à la fonction Auto

Auto.prototype = {
etat: 'off',
demarre: function(){ this.etat='on'; } ,
arreter: function(){ this.etat='off' } ,
getEtat: function(){ return this.etat ; }
};


serge = new Auto() ; // new permet de créer une classe d'objets.
seb = new Auto() ;
serge.demarre();
serge.getEtat(); //'off', il 'a pas démarré.
seb.getEtat(); //'on' il a démarré.
serge.etat ; // 'off' la variable est publique;

Auto.etat ; // "undefined" !
Auto.prototype.etat; // 'off'

// Ainsi la création d'une fonction, a pour conséquence immédiate la création d'un
// objet vide "prototype".
// Cet objet prototype, va servir à créer des objets différents
// créés grâce à l'opérateur new en préfixe de la fonction.
// mais ayant les mêmes méthodes de classes.


Enumération des propriétés d'un objet


Pour inspecter un objet, javascript fournit l'instruction for ... in qui permet d'inspecter certaines propriétés d'un objet, qui n'est autre qu'un tableau associatif d'objets ou de variables.

var objet = new Auto() ;
for(var propriete in objet ){
console.log( "Propriété visible: "+ propriete +": " + objet[propriete] ) ;
}

le prototype

Relations entre l'objet prototype et la fonction constructor:

Il existe des propriétés "cachées" c'est à dire non énumérable lors de l'appel de for...in.
  • Lorsque l'on construit un objet à l'aide de "new", javascript lui adjoint une propriété cachée ['constructor'] du type "function" qui indique le fonction qui l'a créé.
  • Chaque fonction possède elle même une propriété cachée "prototype" de type objet, vide au départ, que l'on peut obtenir sous la forme maFonction['prototype']
  • Mais à son tour, chaque objet prototype possède une propriété cachée constructor, objet.prototype['constructor'] qui pointe vers la fonction qui l'à créée

Le prototype est donc un "objet", vide au départ, qui servira a étendre la classe d'objets créés par la "fonction" constructeur de l'objet.

Il existe ainsi une chaîne de prototypes, que l'on peut découvrir à l'aide du constructeur de l'objet:
Attention: le prototype est une source de confusion ... accrochez vous bien et essayez de suivre pour comprendre le prototype ... :


function Point(x,y){ this.x= x; this.y=y};
// une fonction peut construire un objet :

var p= new Point(3,4) ;// on construit l'objet p avec Point()

p.constructor ; // c'est Point(x,y) du type "function"
//car le mot clé new crée la propriété p['constructor']= Point(x,y)


p.constructor.prototype ;//donc identique à Point.prototype : c'est un objet vide.

Point.prototype.description = function(){
return ("x= "+this.x+", y= " + this.y) ;
}
p.description() ; //"x= 3, y= 4"

// Maintenant, l'objet p est doté de la méthode description
// grâce à p.constructor, car si on fait p.description(), javascript recherchera
// dans p.constructor.prototype puis dans , p.constructor.constructor.prototype
// ... jusqu'à épuisement de la chaine de constructeurs
// La méthode description peut être considérée comme une méthode publique:
// tout élément construit
// à l'aide de Point bénéficie de description.


p.constructor.prototype.constructor; // Point(x,y) ça rend fou non ?
// équivaut à Point.prototype.constructor ;
// logique car si on peut écrire :
// var lePrototype = Point['prototype'] // type objet ,
// lePrototype['constructor'] est une propriété de tout prototype
// ici elle vaut Point(x,y)

p.constructor.constructor ;
// Function() : objet Function
// Equivaut à Point.constructor
// Construit à partir de
p.constructor. constructor. prototype;// function() : constructeur de Function
//Avec d'autre objets:
"hé".constructor ; // String() ;
"hé".constructor.prototype ;// [] c'est un objet array.
// On peut donc créer des nouvelles méthodes pour la
// classe String grâce à son prototype.

On retiendra donc:
  • Le prototype est toujours un objet.
  • Le constructor est toujours une fonction.
  • Tout objet possède une propriété 'constructor'
  • Tout constructeur possède un prototype
  • Tout prototype possède une propriété 'constructor' qui le relie à son constructeur

Methodes d'introspection

Il est donc difficile d'inspecter un objet javascript, voici quelques fonctions d'introspection
  • Nous avons vu, for...in
  • typeof ...
  • Son constructeur: instanceof : monTableau instanceof Array

La fameuse Bibliothèque "Prototype.js" utilise la méthode extends pour simuler un héritage:

Object.prototype.extends(destination, source){
for(property in source){
destination[property]=source[property] ;
}
}
Personnellement, j'ai du mal à modifier les objets de base de javascript...mais c'est juste mon opinion.

Utilisation des clôtures,(en anglais closure) pour limiter la portée des variables


Une troisième méthode pour créer un objet est de créer une fonction qui retourne une autre fonction, dans ce cas les variables définies avant le "return" ne sont accessibles que par la fonction retournée, ces variables sont dans une "cloture" ou "closure":

var moteur = function(puissance){
var etat= "off" ; // notre variable privée dans une cloture (closure).
return{ // on retourne un objet JSON qui fait référence à "etat".
puissance : puissance, // variable publique
demarrer :function(){etat="on" ;},
arreter : function(){ etat="off";} ,
status: function(){ return etat ;}
};
}
m = moteur( "4") ;
m.demarrer();
n = moteur("5") ;
n.status(); //"off"
m.status(); //"on"

//
Ici, on crée une fonction qui initialise d'abord tout ce qui est privé puis renvoie une autre fonction. Tout ce qui a été initialisé est dans une clôture (de l'anglais closure), c'est à dire à portée exclusivement de l'objet renvoyé .
La variable "puissance" est modifiable de l'extérieur, c'est donc une variable publique. Alors que la variable etat, n'est plus accessible. Elle est clôturée dans sa fonction mère.
On a donc ainsi obtenu un nouveau type d'objets aux mêmes caractéristiques et au même comportement. Mais attention, ici pas de mot clé new donc pas de prototype associé à
moteur (Arrêtez moi si je me trompe ).

Reste le problème des variables statiques...


Les variables statiques sont des variables qui maintiennent une valeur constante quelquesoit la classe utilisée, un exemple fréquent est dans une classe qui crée des fenêtres, il existe souvent une variable static qui compte le nombre total de fenêtres. Ce compte est disponible et identique pour tous les objets de cette classe.

Javascript ne comporte pas de déclaration de variable statique, cependant, on peut utiliser le fait que toutes les fonctions sont des objets pour simuler une telle variable. Nous allons créer une variable membre de la fonction, qui sera retenue à chaque appel de cet objet.


function a() {
var closure = "set-me" ;
// noter le var qui sous entend que le "scope" ou la portée
// de cette variable est locale: il s'agit d'une variable en cloture.

// On crée maintenant une variable globale: a.static
// Mais pour la première fois il faut l'initialiser.. sinon on l'initialise à chaque
// appel de a.
if ( typeof a.static == 'undefined' ) {
a.static = 0;
}
a.static++ ; // notre variable sert de compteur.
// On renvoie un objet avec une référence privée: closure et une référence statique: static.
return {
getStatic : function(){ return a.static ;} ,
setClosure : function(n){ return closure= n ;},
getClosure: function(n){ return closure ; }

}
};
a(); // static=1
a(); // static=2
a() ; //static=3
var object1 = a() ; //static = 4
object1.setClosure("hello ") ; // object1.closure vaut "hello "
var object2 = a() ; //static= 5
object2.setClosure("world") ; // object2.closure vaut "world"
object2.getStatic() + " " + object2.getStatic()+ " "+ a.static ;
//Résultat : 5 5 5 : la meme valeur pour tous!
object1.getClosure() + object2.getClosure() ; // "hello world"



On a vu ici quelques éléments de programmation orienté objet. Nous n'avons pas tout abordé en particulier, l'héritage, les fonctions apply et call qui peuvent emprunter des méthodes à d'autres objets, nous n'avons pas vu l'isolement du code source dans une seule variable unique...

Arbres, parcours du DOM

Javascript pour manipuler les arbres.

Un arbre possède des noeuds et des feuilles on peut également parler de noeuds internes ou externes.
On peut définir la structure d’arbre de façon récursive, toute feuille est un arbre, et tout arbre est constitué de feuille, ou d'arbres.

On peut définir un arbre binaire en javascript


Un arbre binaire est constitué de deux branches droite et gauche avec pour chaque noeud une valeur, les éléments terminaux peuvent correspondre à une feuille.
Voici un exemple d'arbre binaire c

function noeud(valeur, gauche, droite){}
function feuille(valeur){} ;
// Exemple:
var arbre = noeud(1,
feuille("a"), noeud(6,
noeud(4,feuille("c"), feuille("d")) , feuille("b"))

)






Nous allons utiliser le DOM comme exemple de structure arborescente.

Le DOM comme exemple.


Le "Document Object Model" ou DOM définit la structure logique d'un document XML et la manière d'y accéder et de le manipuler. Ainsi un document HTML bien formé est une arborescence d'éléments ou noeuds (Nodes) ayant comme racine principale le "document".

Javascript permet de manipuler les éléments du DOM. Le point d'entrée dans le DOM d'une page web est l'objet window.document , cet objet peut être en général, simplement écrit "document" en javascript car le langage interagit à l'intérieur de l'objet window, qui devient sous entendu, lorsqu'on crée une variable dans une page web.

Les fonctions principales du DOM :


Plusieurs fonctions existent au sein de l'api DOM pour manipuler l'arbre du DOM, soit n un noeud:
  • n.nodeName est le nom du noeud
  • nodeType est le type du noeud
    1. 1: Element
    2. 2: Attribut
    3. 3 type texte
  • childNodes: tableau correspondant aux enfants.
  • firstChild: 1er enfant à gauche de l'arbre
  • nextSibling: noeud suivant
  • getAttribute, setAttribute
  • parentNode


Exemple de fonctions


Compter le nombre de noeuds d'une page:

function compte( n){
if(typeof(n) !== 'object') return 0 ;
var n=n.firstChild;
var total = 1 ;
while(n) {
total += compte(n) ;
n = n.nextSibling ;
}
return total;
};


Compter en utilisant childNodes et map :

function compte(n){
if(typeof(n) !== 'object') return 0 ;
return 1 + n.childNodes.map(compte) ;

}


Pour obtenir la liste des noeuds:

var nodes = [] ;
function allChildren(n){
if(typeof(n) !== 'object') return [] ;
var n=n.firstChild;
while(n) {
if(n.nodeType==1) nodes.push(n);
nodes.concat(compte(n)) ;
n = n.nextSibling ;
}
return nodes;
};
allChildren(document.body);


samedi 3 janvier 2009

Algorithme de tris en javascript: Sélection, fusion, insertion

Javascript dans sa forme actuelle ne convient pas pour les algorithmes évolués, notamment il ne faut pas perdre de vue habituellement les tableaux dans une page web sont de faibles importances, et dans ce cas les évaluations mathématiques habituellement asymptotiques en O(n) ne sont pas appropriées.

tri par sélection :


C'est le tri le plus simple, on effectue une double boucle. Une externe toujours triée et une interne. La boucle externe de 0 à i-1 est triée : c'est "l'invariant" de l'algorithme. Dans la boucle interne, on cherche le maximum(minimum) du sous tableau de i à la fin du tableau.
Pour cela, on pose comme hypothèse, qu'en i se trouve le maximum (minimum) du sous tableau, et on parcours ce sous-tableau de i+1 jusqu'à la fin en comparant chaque élément avec l'élément en i, et éventuellement on permute deux éléments lorsque la comparaison contredit notre hypothèse.
Quand on a parcouru tout le sous tableau interne, on est certain qu'en i se trouve le maximum(minimum) du sous tableau de i à la fin du tableau.
On peut alors recommencer à chercher le prochain maximum du sous tableau interne de mais cette fois i+1 à la fin du tableau.

On peut utiliser f, une "fonction de valuation" qui renvoie true ou false suivant que les valeurs à comparer sont plus grandes ou plus petites. (une fonction est une variable en javascript)



// notre fonction de valuation f renvoie true ou false:
var f = function(a,b){
return a>b ;
}

function tri(l,f){
for(var i= 0 ; i< l.length; i++){
// le tableau est trié de 0 à i-1
// La boucle interne recherche le maximum
// de i+1 à la fin du tableau.
for(var j=i+1; j< l.length; j++){
if(f( l[j], l[i]) ){
var temp = l[j];
l[j]=l[i];
l[i]=temp;
}
}
}
return l ;
}
tri([1,4,3,7],function(a,b){ return a>b ;});


Note sur la fonction swap:
Firefox permet de swapper (permuter) ainsi:
                    [l[i] ,l[j]] = [l[j],l[i]] ;// swapper.

Mais pas IE!!


Le tri par insertion


Dans ce tri, une première boucle parcourt le tableau de gauche à droite, tous les éléments à gauche du tableau sont triés. Une deuxième boucle insère dans le tableaux de gauche les éléments, de la même manière qu'un joueur de cartes insère une nouvelle carte dans un jeux trié de gauche à droite. En décalant les autres cartes vers la droite pour insérer la carte courante au bon endroit.

Une autre image, plus simple est celle d'une rangée de livre sur une étagère, on considère que le premier livre à gauche est en place, si le deuxième livre est en place, on passe au 3ème, sinon on décale à gauche le premier livre pour insérer en première position le livre que l'on a en main et ainsi de suite...
L'"invariant" est que la partie gauche du tableau est toujours triée.


var tri_insert = function(t , f){
for(var i=1 ; i < t.length ; i++){
var temp= t[i] ;
var j = i-1 ;
while( f(temp, t[j]) && j>=0){
t[j+1]= t[j]
j-- ;
};
t[j+1]=temp ;

}
return t;
}

tri_insert([5,3,6,2,1] , function(a, b){ return a < b;})


Ce tri est probablement l'un des plus simple et efficace, sa vitesse d'exécution est en général proportionnelle au carré de la taille du tableau (on peut noter O(n2). Mais, si le tableau est trié, le tri s'effectue dans un temps proportionnel à la longueur du tableau. Si le tableau est trié en sens inverse, c'est le cas le pire, le tri s'effectue en un temps proportionnel au carré de la longueur du tableau.

Tri fusion en javascript


Nous avons vu créé naïvement deux tris, qui s'effectuent habituellement, en un temps proportionnel au carré de la taille du tableau. Comment faire plus rapide ?
Si notre tableau contient n éléments on peut aller beaucoup plus vite et effectuer ce tri à une vitesse de nlog(n), il existe de nombreuses variantes de tris. On va voir ici le tri-fusion.
Le tri-fusion utilise le paradigme de Machiavel : "diviser pour régner" , "divide and conquer", ou encore plus modestement fait appel au couple division-récursion.

Maintenant accrochez vous : faire un tri fusion en javascript :
- Une première fonction "divise" divise votre tableau en 2 tableaux.
- Une deuxième fonction "fusion" fusionne deux tableaux en un seul tableau, mais en choisissant entre les deux derniers (ou premiers) éléments de chaque tableau celui qui est le plus petit (ou plus grand).
A l'image d'un professeur qui distribue les copies à partir de deux tas de copies placées devant lui. Il ne voit que la dernière copie de chaque tas, choisit entre deux copies celle dont la note est la plus forte (ou faible).
- Enfin l'étape finale est de faire cela récursivement:
  • On divise en deux chaque tableau, sauf si sa taille est inférieur ou égale à 1
  • On tri chacun des deux sous tableaux.

  • Enfin, on fusionne les deux tableaux triés que l'on renvoi en résultat.




//Tri fusion sur liste:

function tri_fusion(liste){
var divise= function(l){
if(l.length === 0) return [[],[]] ;
if(l.length ===1 ) return [ l , []] ;
var last = l.pop();
var lastlast = l.pop();
var [g,d]= divise(l) ;
g.push(last) ; d.push(lastlast) ;
return [g,d];
}
//divise([1,3,4,6]); //[[3, 6], [1, 4]]
///// Attention aux tableaux de tableaux:
var fusion = function(a,b){
if(a.length===0 && b.length===0) return [] ;
if(a.length===0) return b;
if(b.length===0) return a;
var [p,q]= [a.pop(),b.pop()];
if(p < q){
b.push(q);
var temp= fusion(a,b);
temp.push(p);
return temp;
}else{
a.push(p);
var temp= fusion(a,b);
temp.push(q);
return temp ;}
}
//fusion([3], [5]); //

var tri = function(l){
if(l.length <=1 ) return l ;
var [g,d] = divise(l) ;
return fusion (tri(g),tri(d));
}
//tri(liste);
}//tri([2,3,5,0,10,10,9]);

function m(n){
var r=new Array(n);
for(var i=0; i< n;i++)
r[i] = Math.round(Math.random()*1000000) ;
return r;
}
tri_fusion(m(10000)); // too much recursion !





On le voit que le tri fusion n'est pas adapté à la structure actuelle, du javascript, qui est un langage interprété par un autre langage et qui ne convient pas au contexte habituel des pages web.
C'est cependant un tri efficace qu'il faut connaître.
Il existe des tas d'autres algorithmes de tri en O(n log n), l'autre très connu est le tri-rapide ou "quick-sort" qui s'effectue plus dans un contexte de tableau que de liste.

Simplifions nous la vie: la fonction sort


En pratique, il est plus simple d'utiliser la fonction "sort" déjà présente dans javascript: Array.sort.
Il est ainsi possible de trier une liste en lui appliquant la méthode sort(f). Cette méthode prend en argument, une fonction de valuation elle même à deux arguments qui retourne un résultat positif ou négatif suivant la façon dont vous voulez trier.
Par exemple :

  • var mafonction= fonction(a,b){ return a-b ;};

  • montableau.sort(mafonction) ;



N'oubliez pas qu'une fonction en javascript est un type au même titre qu'une string...




var liste=[4,5,9,0,1];
// on donne en argument une fonction anonyme:
liste.sort(function(a,b){ return a-b } ); //[0, 1, 4, 5, 9]
liste.sort(function(a,b){ return b-a } ); //[9, 5, 4, 1, 0]


// Trier en ordre alphabétique...
var liste=['i',' ','l','o','v','e',' ', 'y','o','u'];
liste.sort(function(a,b){ return a.charCodeAt(0)- b.charCodeAt(0) } );
//[" ", " ", "e", "i", "l", "o", "o", "u", "v", "y"]

//Pour tout mélanger on lui donne Math.random !!
liste.sort(function(a,b){ return Math.random()-0.5 } );//[4, 0, 5, 9, 1]



Ainsi, en pratique on utilisera sort et une fonction de valuation à deux arguments qui permettra un tri parfait, voire de mélanger tout comme ci dessus avec notre fonction
anonyme retournant un nombre aléatoire entre -0.5 et + 0.5.