L'interface Map et ses implémentations (HashMap, MultiHashMap, ...) dans Vegas

Marc Alcaraz

Suite des articles consacrés aux types abstraits de données dans Vegas avec l'interface Map et ses diverses implémentations.

- Titre : L'interface Map et ses implémentations (HashMap, MultiHashMap, ...) dans Vegas

- Auteur : Marc Alcaraz

- Parution : 21 novembre 2007

- Licence : Copyright ® 2006-2007 Marc Alcaraz. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Généralités

- I-A. Les méthodes de tests

- I-B. Les méthodes d'ajout et de modification

- I-C. Les méthodes de suppression

- I-D. Les méthodes de duplication

- I-E. Les méthodes de récupération

- I-F. Les méthodes d'informations

- II. Détail des différentes implémentations de l'interface Map.

- II-A. La classe HashMap

- II-B. La classe ArrayMap

- II-C. La classe SortedArrayMap

- II-D. La classe TypedMap

- II-E. La classe MultiHashMap

- II-F. La classe MultiHashSet

- III. Conclusion

I - Généralités

L'interface Map de VEGAS est largement inspirée d'une interface du framework des collections JAVA. Cette interface définit des classes très pratiques dans la mise en place des modèles d'une RIA car elle permet d'enregistrer tout type d'objets en l'associant à une clé de tout type. Il est ainsi possible d'associer pour n'importe quel objet (la clé) de votre application un ou plusieurs autres (la valeur ) .

L'interface Map est implémentée par des Collections qui associent une clé à une valeur. Les clés sont uniques et de n'importe quel type. Il est possible de retrouver plusieurs fois la même valeur pour des clés différentes et les valeurs sont également de n'importe quel type.

L'interface Map contient plusieurs ensembles de méthodes de base :

I-A - Les méthodes de tests

- isEmpty():Boolean : retourne true si la collection ne contient aucune entrée.

- containsKey(key):Boolean : retourne true si la collection contient une paire clé/valeur correspondant à la clé spécifiée.

- containsValue(value):Boolean : retourne true si la collection contient une ou plusieurs clés pointant la valeur spécifiée.

I-B - Les méthodes d'ajout et de modification

- put(key, value) : associe la valeur spécifiée à la clé donnée au sein de la collection. Si la clé existe déjà dans la Map seul la valeur est remplacée et l'ancienne valeur est retournée.

- putAll(m:Map) : copie toutes les paires clé/valeur de la Map spécifiée au sein de l'objet Map.

I-C - Les méthodes de suppression

- clear() : supprime toutes les paires clé/valeur de la collection.

- remove(key) : supprime la paire clé/valeur correspondant à la clé spécifiée.

I-D - Les méthodes de duplication

- clone() : retourne une nouvelle Map contenant les mêmes paires clé/valeur que l'original.

I-E - Les méthodes de récupération

- get(key) : retourne la valeur correspondant à la clé spécifiée.

- getKeys():Array : retourne une liste (Array) de toutes les clés de la Map.

- getValues():Array : retourne une liste (Array) de toutes les valeurs de la Map.

- iterator():Iterator : retourne un Iterator permettant d'énumérer le contenu de la Map (valeur et clé via un MapIterator)

- keyIterator():Iterator : retourne un Iterator permettant d'énumérer seulement les clés de la Map.

I-F - Les méthodes d'informations

- size():Number : retourne le nombre d'entrées (paires clé/valeur) de la Map.

- toString():String : retourne la représentation chaine de caractère de la Map.

VEGAS contient plusieurs implémentations de l'interface Map dans le package vegas.data.map.* mais aussi dans le package vegas.data.set.* :

- vegas.data.map.HashMap

- vegas.data.map.ArrayMap

- vegas.data.map.SortedArrayMap

- vegas.data.map.MultiHashMap

- vegas.data.map.TypedMap

- vegas.data.set.MultiHashSet

En AS2 et AS3 les implémentations des classes de type Map sont les mêmes mais en AS3 avec l'apparition de la classe flash.utils.Dictionary qui propose en natif un objet permettant de créer une collection dynamique contenant des clés et des valeurs j'ai pu adapter ma classe HashMap en la basant sur cette classe AS3. Il faut donc vous méfier de l'implémentation de la classe HashMap en AS3. La classe ArrayMap AS3 correspond au final à la classe HashMap AS2 et SSAS.

II - Détail des différentes implémentations de l'interface Map.

II-A - La classe HashMap

Cette classe est une implémentation directe de l'interface Map. Je me suis basé sur l'implémentation JAVA de cette classe pour définir les méthodes de cette classe.

En principe la classe HashMap n'assure pas l'ordre des paires clé/valeur dans la collection. En AS3, cette classe utilise en interne la classe flash.utils.Dictionary par composition et utilise un proxy (voir classe flash.utils.Proxy AS3). En AS2 et SSAS par contre, j'utilise 2 Arrays par composition pour stocker les paires clé/valeur dans la Map.

Maintenant voyons un exemple d'utilisation :

import vegas.data.Map ;
import vegas.data.map.HashMap;
import vegas.data.iterator.Iterator;

var map:Map = new HashMap() ;

trace("put key1 -> value0 : " + map.put("key1", "value0") ) ;
trace("put key1 -> value1 : " + map.put("key1", "value1") ) ;
trace("put key2 -> value2 : " + map.put("key2", "value2") ) ;

trace("map : " + map) ;
trace("map toSource : " + map.toSource()) ;

trace("> iterator") ;
var it:Iterator = map.iterator() ;
while(it.hasNext())
{
var v = it.next() ;
var k = it.key() ;
trace( "   " + k + " : " + v ) ;
}

trace("remove key1 : " + map.remove("key1")) ;
trace("size : " + map.size()) ;

map.clear() ;

trace("isEmpty : " + map.isEmpty()) ;

II-B - La classe ArrayMap

Cette classe dans toutes ses versions (AS2, AS3 et SSAS) utilise 2 Arrays pour stocker ses entrées (paires clé/valeur). Cette classe ressemble beaucoup à une HashMap mais propose 2 méthodes supplémentaires :

- setKeyAt(index:Number, key) : Modifie ma valeur d'une clé dans la Map à une position donnée sans modifier la paire clé/valeur.

- setValueAt(index:Number, value) : Modifier une valeur dans la Map à une position donnée (index).

En AS2 et SSAS la classe ArrayMap hérite de la classe HashMap.

II-C - La classe SortedArrayMap

Cette classe propose toutes les fonctionnalités d'une ArrayMap avec la particularité qu'il est possible de trier le contenu de la Map suivant plusieurs critères. De par sa nature, une ArrayMap est basée sur des collections de type Array, il est donc possible de trier une SortedArrayMap :

- En ciblant précisément si l'on veut trier la Map en utilisant un tri par clés ou par valeurs.

- En utilisant la méthode sort(); un objet de type IComparator qui permet de personnaliser sa méthode de tri en créant une fonction compare() qui renvoie -1,0 ou 1 selon les valeurs et l'ordre de chaque élément dans la liste des clés ou des valeurs de la Map.

- En choisissant à tout moment un niveau d'options de tri basé sur l'implémentation des options de la classe Array avec un filtrage binaire des critères de tri : Array.DESCENDING, Array.NUMERIC, etc.

- En utilisant la méthode avancée sortOn() qui permet de trier la liste des clés ou des valeurs en ciblant une propriété en particulier dans la liste des clés ou la liste des valeurs à condition que les clés ou les valeurs soient des objets contenant des attributs triables. Cette méthode utilise en interne la méthode Array.sortOn().

Example 1 : Utilisation de la méthode sort()

import vegas.data.map.SortedArrayMap ;
import vegas.util.comparators.NumberComparator ;
import vegas.util.comparators.StringComparator ;

var map:SortedArrayMap = new SortedArrayMap( [0] , ["item0"] ) ;

map.put( 1 , "item8" ) ;
map.put( 3 , "item7" ) ;
map.put( 2 , "item6" ) ;
map.put( 5 , "item5" ) ;
map.put( 4 , "item4" ) ;
map.put( 7 , "item3" ) ;
map.put( 6 , "item2" ) ;
map.put( 8 , "item1" ) ;

trace("----- original Map") ;

trace( map ) ;

trace("----- sort by key with sort() method") ;

map.comparator = new NumberComparator() ;

map.options = SortedArrayMap.NUMERIC | SortedArrayMap.DESCENDING ;
map.sort() ;
trace( map ) ;

map.options = SortedArrayMap.NUMERIC ;
map.sort() ;
trace( map ) ;

trace("----- sort by value with sort() method") ;

map.comparator = new StringComparator() ;

map.sortBy = SortedArrayMap.VALUE ;

map.options = SortedArrayMap.DESCENDING ;
map.sort() ;
trace( map ) ;

map.options = SortedArrayMap.NONE ;
map.sort() ;
trace( map ) ;

Example 2 : Utilisation de la méthode sortOn()

import vegas.data.iterator.Iterator ;
import vegas.data.Map ;
import vegas.data.map.SortedArrayMap ;

var map:SortedArrayMap = new SortedArrayMap() ;

map.put( { id:5 } , { name:'name4' } ) ;
map.put( { id:1 } , { name:'name1' } ) ;
map.put( { id:3 } , { name:'name5' } ) ;
map.put( { id:2 } , { name:'name2' } ) ;
map.put( { id:4 } , { name:'name3' } ) ;

var debug:Function = function( map:Map ):void
{

var vit:Iterator = map.iterator() ;
var kit:Iterator = map.keyIterator() ;

var str:String = "{" ;

while( vit.hasNext() )
{

var value = vit.next() ;
var key   = kit.next() ;

str += key.id + ":" + value.name ;

if (vit.hasNext())
{
str += "," ;
}
}

str += "}" ;

trace(str) ;
}

trace("----- original Map") ;

debug( map ) ; // {5:name4,1:name1,3:name5,2:name2,4:name3}

trace("----- sort by key with sort() method") ;

map.sortBy = SortedArrayMap.KEY ; // default

map.options = SortedArrayMap.NUMERIC | SortedArrayMap.DESCENDING ;
map.sortOn("id") ;
debug( map ) ; // {5:name4,4:name3,3:name5,2:name2,1:name1}

map.options = SortedArrayMap.NUMERIC  ;
map.sortOn("id") ;
debug( map ) ; // {1:name1,2:name2,3:name5,4:name3,5:name4}

trace("----- sort by value with sort() method") ;

map.sortBy = SortedArrayMap.VALUE ;

map.options = SortedArrayMap.DESCENDING ;
map.sortOn("name") ;
debug( map ) ; // {3:name5,5:name4,4:name3,2:name2,1:name1}

map.options = SortedArrayMap.NONE ;
map.sortOn("name") ;
debug( map ) ; // {1:name1,2:name2,4:name3,5:name4,3:name5}

II-D - La classe TypedMap

Cette classe permet d'encapsuler une Map pour sécuriser l'insertion des éléments en testant à chaque utilisation des méthodes put() et putAll() toutes les paires clé/valeur et ainsi en forçant le typage unique des valeurs insérées dans la Map. Cette classe implémente l'interface ITypeable et hérite directement de la classe abstraite AbstractTypeable.

II-E - La classe MultiHashMap

La classe MultiHashMap est une classe inspirée de l'implémentation des collections du framework JAKARTA. Cette classe implémente l'interface MultiMap qui permet d'insérer pour une même clé dans la Map plusieurs valeurs. En fait une MultiHashMap utilise une valeur de type Collection pour chaque paire clé/valeur et il est possible d'insérer autant de valeurs que l'on veut pour une clé en particulier.

Cette classe fonctionne un peu différemment que les Maps classiques.

import vegas.data.Collection ;
import vegas.data.iterator.Iterator ;
import vegas.data.map.HashMap ;
import vegas.data.Map ;
import vegas.data.MultiMap ;
import vegas.data.map.MultiHashMap ;

var map1:HashMap = new HashMap() ;
map1.put("key1", "valueD1") ;
map1.put("key2", "valueD2") ;
trace ("> map1 : " + map1) ;
trace ("> map1 containsKey 'key1' : " + map1.containsKey("key1")) ;

trace ("--- use a map argument in constructor") ;

var map:MultiHashMap = new MultiHashMap(map1) ;
map.put("key1", "valueA1") ;
map.put("key1", "valueA2") ;
map.put("key1", "valueA3") ;
map.put("key2", "valueA2") ;
map.put("key2", "valueB1") ;
map.put("key2", "valueB2") ;
map.put("key3", "valueC1") ;
map.put("key3", "valueC2") ;

trace ("init map : " + map) ;
trace ("r--- toSource MultiMap") ;

trace("map toSource : " + map.toSource()) ;

trace ("r--- put values in MultiMap") ;

trace ("key1 >> " + map.get("key1")) ;

trace ("key2 >> " + map.get("key2")) ;

trace ("key3 >> " + map.get("key3")) ;

trace ("r--- toString MultiMap") ;

trace (map) ;

map.remove("key1", "valueA2") ;

trace ("r--- remove a value in key1 >> " + map.get("key1")) ;

trace ("r--- use iterator") ;

var it:Iterator = map.iterator() ;
while(it.hasNext())
{
trace ("\t :: " + it.next()) ;
}

trace ("r--- use a key iterator : key1") ;
var it:Iterator = map.iterator("key1") ;
while(it.hasNext())
{
trace ("\t :: " + it.next()) ;
}

trace ("r--- putCollection key2 in key1") ;
map.putCollection("key1", map.get("key2")) ;
trace ("key1 >> " + map.get("key1")) ;

trace ("r--- different size") ;
trace ("map size : " + map.size()) ;
trace ("map totalSize : " + map.totalSize()) ;

trace ("r--- clone MultiMap") ;
var clone:MultiMap = map.clone() ;
clone.remove("key1") ;

trace("clone : " + clone) ;
trace ("clone size : " + clone.totalSize()) ;
trace ("map size : " + map.totalSize()) ;

trace ("r--- valueIterator") ;
var it:Iterator = map.valueIterator() ;
while(it.hasNext())
{
trace("\t> " + it.next()) ;
}

II-F - La classe MultiHashSet

La classe vegas.data.set.MultiHashSet est par contre une classe originale que j'ai dû mettre en place par rapport à certaines applications. Cette classe fonctionne exactement comme une MultiHashMap à la seule différence près que la Collection utilisée pour stocker les valeurs d'une clé est de type Set.

Une Set est une collection qui ne permet pas les doublons. Il est donc impossible d'insérer deux valeurs identiques dans la MultiMap pour une même clé mais il est possible d'insérer une même valeur pour différentes clés de la Map.

import vegas.data.Collection ;
import vegas.data.collections.SimpleCollection ;
import vegas.data.set.MultiHashSet ;

var s:MultiHashSet = new MultiHashSet() ;

trace("----- Test put()") ;

trace("insert key1:valueA1 : " + s.put("key1", "valueA1")) ;
trace("insert key1:valueA2 : " + s.put("key1", "valueA2"))
trace("insert key1:valueA2 : " + s.put("key1", "valueA2")) ;
trace("insert key1:valueA3 : " + s.put("key1", "valueA3")) ;
trace("insert key2:valueA2 : " + s.put("key2", "valueA2")) ;
trace("insert key2:valueB1 : " + s.put("key2", "valueB1")) ;
trace("insert key2:valueB2 : " + s.put("key2", "valueB2")) ;

trace("size : " + s.size()) ;
trace("totalSize : " + s.totalSize()) ;

trace("---- Test toArray") ;

var ar:Array = s.toArray() ;
trace("s.toArray : " + ar) ;

trace("----- Test contains") ;

trace("contains valueA1 : " + s.contains("valueA1") ) ;
trace("contains valueA1 in key1 : " + s.contains("key1", "valueA1") ) ;
trace("contains valueA1 in key2 : " + s.contains("key2", "valueA1") ) ;

trace("---- Test remove(key, value)") ;

trace("remove key1:valueA2 : " + s.remove("key1", "valueA2")) ;
trace("insert key1:valueA2 : " + s.put("key1", "valueA2")) ;
trace("insert key1:valueA2 : " + s.put("key1", "valueA2")) ;

trace("---- Test remove(key)") ;

trace("remove key2 : " + s.remove("key2")) ;
trace("size : " + s.size()) ;

trace("---- Test putCollection(key, co:Collection)") ;
var co:Collection = new SimpleCollection(["valueA1", "valueA4", "valueA1"]) ;
s.putCollection("key1", co) ;
trace("s.toString : " + s) ;

Remarque : En AS3 j'ai dû modifier le nom du package vegas.data.set (AS2 et SSAS) car le mot set est mal interprété par le compilateur du Flex SDK. J'ai donc décidé en AS3 de nommer ce répertoire vegas.data.sets.

III - Conclusion

L'interface Map est une structure de données très importante. J'utilise dans ma production personnelle énormément les classes qui implémentent cette interface. Je vous conseille vivement en complément de ce tutoriel d'essayer d'implémenter dans vos applications le design pattern MVC en utilisant ma nouvelle implémentation basée sur l'extension AndromedA de VEGAS.

Pour le moment l'extension AndromedA est encore en chantier mais les packages andromeda.controller, andromeda.model sont déjà bien avancés et stables. Je n'ai pas encore eu le temps de vous faire des tutoriaux complets sur le sujet (ils sont en cours) mais en attendant la sortie des tutoriaux, je vous conseille de vous pencher sur mon framework opensource documentaire AST'r qui me sert de base pour mes tutoriaux et pour créer des sortes de templates de code basés sur VEGAS.

Dans AST'r, vous avez dans les sources du projet, un répertoire d'exemples qui contient une petite application "eGallery" qui montre comment créer une petite application AS2 qui permet charger un modèle des données provenant d'une base de donnée MYSQL via AMFPHP ou des données contenues dans un fichier contenant du texte au format eden en local. Ce modèle permet ensuite d'afficher une sorte de petite galerie d'images.

L'objectif de l'exemple "eGallery" est multiple mais permet au passage de bien démontrer l'utilisation du modèle MVC de AndromedA avec la classe andromed.model.map.MapModel avec un cablage du modèle sur le flux évènementiel global de l'application via un FrontController. Je reviendrai sur ces spécificités du langage très bientôt j'espère.

A noter, pour finir, que je suis en train de documenter AST'r en anglais sur le wiki du projet sur Google Code, je vous conseille donc de faire un tour de temps en temps sur ce wiki pour en savoir plus.