Partage
  • Partager sur Facebook
  • Partager sur Twitter

Implémentation comparateur avancé

Sujet résolu
    8 février 2012 à 11:31:20

    Salut à tous

    Avant toute chose, je précise que dans mon cas je ne peux utiliser que la version 1.4 de Java et ne peut donc pas utiliser certaines fonctions des versions ultérieures qui me seraient utiles (généricité, réflexion...).

    Je vais d'abord expliquer ce que je cherche à faire, mes avancés et mes problèmes.
    Dans le cadre d'une application web, à plusieurs endroits l'utilisateur doit faire une sélection dans un menu déroulant. Les champs dans ces menus déroulants apparaissent dans n'importe quel ordre. Je doit faire en sorte que ces champs soient triés dans l'ordre alphabétique usuel.

    Dans tout ces cas, les informations affichées proviennent de List d'objets personnalisés dont l'un des champs (string) est celui affiché.
    Je prends pour exemple une classe Toto :

    public class Toto{
    
       public Toto()
       {
       }
    
       private String totoArg = "toto";
    
       public String getTotoArg()
       {
           return totoArg;
       }
    }
    


    Je veux maintenant classer une liste d'objets Toto selon le champs totoArg. J'utilise la méthode sort et le comparateur natif, ce qui donne :

    List totoList; //Liste forméé d'objets Toto
    
    Collections.sort(totoList, new Comparator() {
    		    public int compare(Object o1, Object o2) {
    		    	
    		    	Toto t1 = (Toto) o1;
    		    	Toto t2 = (Toto) o2;
    		    	
    		    	String s1 = t1.getTotoArg();
    		    	String s2 = t2.getTotoArg();
    	 
    		    	return s1.compareTo(s2);
    		    }
    		});
    


    Le problème est qu'avec cette méthode, le tri n'est pas correcte si le champs totoArg comporte des caractères numériques : si je suppose que les champs totoArg comporte les valeurs successives "toto1", "toto2", ..., "toto42",... le tri donnera :

    "toto1"
    "toto10"
    "toto11"
    "toto12"
    ...
    "toto19"
    "toto2"
    "toto20"
    ...


    Après recherche, j'ai trouvé un comparateur qui me permet d'avoir le tri que je veux. Je crée donc une classe NaturalOrderComparator :

    public class NaturalOrderComparator implements Comparator {
    	
    	int compareRight(String a, String b)
        {
            int bias = 0;
            int ia = 0;
            int ib = 0;
    
            // The longest run of digits wins. That aside, the greatest
            // value wins, but we can't know that it will until we've scanned
            // both numbers to know that they have the same magnitude, so we
            // remember it in BIAS.
            for (;; ia++, ib++)
            {
                char ca = charAt(a, ia);
                char cb = charAt(b, ib);
    
                if (!Character.isDigit(ca) && !Character.isDigit(cb))
                {
                    return bias;
                }
                else if (!Character.isDigit(ca))
                {
                    return -1;
                }
                else if (!Character.isDigit(cb))
                {
                    return +1;
                }
                else if (ca < cb)
                {
                    if (bias == 0)
                    {
                        bias = -1;
                    }
                }
                else if (ca > cb)
                {
                    if (bias == 0)
                        bias = +1;
                }
                else if (ca == 0 && cb == 0)
                {
                    return bias;
                }
            }
        }
    	
    	
        public int compare(Object o1, Object o2)
        {
            /*je cast ici les objets o1 et o2 dans le type qui m'intéresse pour pouvoir
            utiliser la méthode de récupération de champs getTotoArg*/
        	Toto p1 = (Toto) o1;
        	Toto p2 = (Toto) o2;
        	
        	String a = p1.getTotoArg();
        	String b = p2.getTotoArg();
            
            int ia = 0, ib = 0;
            int nza = 0, nzb = 0;
            char ca, cb;
            int result;
    
            while (true)
            {
                // only count the number of zeroes leading the last number compared
                nza = nzb = 0;
    
                ca = charAt(a, ia);
                cb = charAt(b, ib);
    
                // skip over leading spaces or zeros
                while (Character.isSpaceChar(ca) || ca == '0')
                {
                    if (ca == '0')
                    {
                        nza++;
                    }
                    else
                    {
                        // only count consecutive zeroes
                        nza = 0;
                    }
    
                    ca = charAt(a, ++ia);
                }
    
                while (Character.isSpaceChar(cb) || cb == '0')
                {
                    if (cb == '0')
                    {
                        nzb++;
                    }
                    else
                    {
                        // only count consecutive zeroes
                        nzb = 0;
                    }
    
                    cb = charAt(b, ++ib);
                }
    
                // process run of digits
                if (Character.isDigit(ca) && Character.isDigit(cb))
                {
                    if ((result = compareRight(a.substring(ia), b.substring(ib))) != 0)
                    {
                        return result;
                    }
                }
    
                if (ca == 0 && cb == 0)
                {
                    // The strings compare the same. Perhaps the caller
                    // will want to call strcmp to break the tie.
                    return nza - nzb;
                }
    
                if (ca < cb)
                {
                    return -1;
                }
                else if (ca > cb)
                {
                    return +1;
                }
    
                ++ia;
                ++ib;
            }
        }
    
        static char charAt(String s, int i)
        {
            if (i >= s.length())
            {
                return 0;
            }
            else
            {
                return s.charAt(i);
            }
        }
    
    	    
    }
    


    Puis en instanciant mon comparateur :

    List totoList; //Liste forméé d'objets Toto
    
    Collections.sort(totoList, new NaturalOrderComparator());
    


    Ca marche parfaitement.

    Le problème maintenant est que ma classe NaturalOrderComparator n'est pas du tout réutilisable pour faire d'autre tri (par exemple sur une liste d'objets Titi) puisque je rentre en dur dans la définition de la classe le type à caster (Toto) et la méthode get à utiliser.

    Ma question est donc : est-il possible de rendre variable le type à caster et la méthode get à utiliser ??

    J'ai éventuellement penser à utiliser le pattern factory qui à l'aide de la méthode isInstanceOf() me permet de déterminer le type d'objet dans la liste et instancie ensuite le comparateur adéquat (par exemple NaturalOrderComparatorToto, NaturalOrderComparatorTiti, NaturalOrderComparatorTutu...) qui aura dans sa définition le méthode get à utiliser.
    Cependant cette méthode reste toujours peu flexible à mes yeux puisqu'à chaque nouveau tri il faut créer une nouvelle classe comparateur et modifier le factory.

    De plus je ne peux a priori pas modifier mon comparateur puisque dans la version 1.4 de Java, le comparateur nécessite absolument la méthode compare qui prends 2 Objets en arguments (je n'ai pas l'impression qu'il soit possible de mettre par exemple des String en arguments de cette méthode).

    Je souhaite donc savoir si l'un d'entre vous aurait une méthode, un truc, une astuce... qui donnerait quelque chose de plus souple que le pattern factory.

    J'espère que j'ai réussi à exposer mon problème clairement. Désolé si le post est un peu long (je m'en rends compte en tapant cette dernière ligne :-° ) mais j'ai préféré expliqué mon raisonnement.

    Merci d'avance à tous

    poupitz
    • Partager sur Facebook
    • Partager sur Twitter
      8 février 2012 à 14:45:57

      Salut, pour rendre un comparator genererique, il y plusieurs problematique:
      la 1ere c'est le Type a comparer, a la limite ceci peut être géré dynamiquement en passant Class en paramètre de ton constructeur de comparateur, ou en utilisant une interface sur tes objets a comparer.
      Après il y a le type d'ordre que tu souhaite, trier par ordre alphabetique, numeric?
      Et enfin il y la methode appelé pour comparer les objects.
      D'après cette dernière difficulté je dirais que le plus simple(ou pas! :p ) c'est d'utiliser des interface pour que tes objets a comparer soit accessible par ma même méthode.
      • Partager sur Facebook
      • Partager sur Twitter
        14 février 2012 à 10:29:40

        Salut ninlock
        Merci de m'avoir répondu. Malheureusement il n'est pas envisageable pour moi de modifier l'implémentation des objets que je dois trier comme tu le suggères car ce serait trop de modifications vu la taille du projet (900Mo de source, plusieurs centaines de classe) donc je dois les prendre comme ils sont.
        J'ai finalement choisi de mettre en place un constructeur dans mon comparateur qui prend un String comme paramètre. Ce String permet d'identifier quel est le type d'objet utilisé. J'ai donc implémenté différents cas selon le type à caster et la méthode get à utiliser (choix à l'aide d'un bête if). Je trouve que ça reste un peu rigide comme méthode mais je n'ai pas trouvé mieux.
        • Partager sur Facebook
        • Partager sur Twitter
          14 février 2012 à 13:15:09

          Citation : poupitz

          Je trouve que ça reste un peu rigide comme méthode mais je n'ai pas trouvé mieux.


          Si ça peut te consoler, en travaillant avec Java 1.4 et sans pouvoir toucher aux objets... Tu trouveras difficilement mieux tellement les leviers d'action sont restreints.
          Je t'aurais bien dit dit de hiérarchiser tes objets dans différentes interfaces (par exemple une interface NaturalStringOrderComparable avec une unique méthode getComparableString() qui renverrait titi ou tutu suivant l'implémentation de la classe) mais si tu ne peux pas toucher aux classes existantes...


          Ce que tu dis à la fin de ton message (rendre la classe totalement générique en passant le nom de la propriété à comparer), c'est faisable par introspection... Mécanique introduite en Java 5 il me semble (l'API Reflect).

          Bon courage en tout cas.
          • Partager sur Facebook
          • Partager sur Twitter

          Implémentation comparateur avancé

          × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
          × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
          • Editeur
          • Markdown