Pourquoi un HashMap devrait-il être utilisé (dans les fonctions) pour déterminer la valeur à renvoyer (pour une clé) quand une construction if else peut faire le travail en un temps meilleur?

9

Alors que je travaillais récemment dans une grande entreprise, j'ai remarqué que les programmeurs y suivaient ce style de codage:

Supposons que j'ai une fonction qui renvoie 12 si l'entrée est A, 21 si l'entrée est B et 45 si l'entrée est C.

Je peux donc écrire la signature de la fonction comme suit:

int foo(String s){
    if(s.equals("A"))      return 12;
    else if(s.equals("B")) return 21;
    else if(s.equals("C")) return 45;
    else throw new RuntimeException("Invalid input to function foo");
}

Mais lors de l'examen du code, on m'a demandé de modifier la fonction comme suit:

int foo(String s){
    HashMap<String, Integer> map = new HashMap<String, Integer>();
    map.put("A", 12);
    map.put("B", 21);
    map.put("C", 45);
    return map.get(s);
}

Je ne peux pas me convaincre pourquoi le deuxième code est meilleur que le premier. Le deuxième code prendrait certainement plus de temps à s'exécuter.

La seule raison d'utiliser le deuxième code peut être qu'il offre une meilleure lisibilité. Mais si la fonction est appelée plusieurs fois, la deuxième fonction ne ralentira-t-elle pas le temps d'exécution de l'utilitaire qui l'appelle?

Que penses-tu de cela?

Nikunj Banka
la source
4
Pour trois valeurs, une carte semble exagérée ( switchsemble plus appropriée que if-else). Mais à un moment donné, cela devient problématique. Le principal avantage de l'utilisation d'une carte est que vous pouvez la charger à partir d'un fichier ou d'une table, etc. Si vous codez en dur l'entrée sur la carte, je ne vois pas beaucoup de valeur sur un commutateur.
JimmyJames

Réponses:

16

Le but est de déplacer la création de la table de hachage en dehors de la fonction et de le faire une fois (ou juste moins de fois qu'autrement).

private static final Map<String, Integer> map;
static{
    Map<String, Integer> temp = new HashMap<String, Integer>();
    temp.put("A", 12);
    temp.put("B", 21);
    temp.put("C", 45);
    map = Collections.unmodifiableMap(temp);//make immutable
}

int foo(String s){
    if(!map.containsKey(s))
        throw new RuntimeException("Invalid input to function foo");

    return map.get(s);
}

Cependant, java a depuis java7 pu avoir des chaînes (finales) dans les commutateurs:

int foo(String s){
    switch(s){
    case "A":
        return 12;
    case "B": 
        return 21;
    case "C": 
        return 45;
    default: throw new RuntimeException("Invalid input to function foo");
}
monstre à cliquet
la source
1
Je ne vois pas comment cela répond à la question des PO, donc -1 là-bas. Mais +1 pour avoir suggéré un changement.
user949300
Il montre comment implémenter correctement le style de codage pour donner un sens et éventuellement améliorer les performances. Cela n'a toujours pas de sens pour 3 choix, mais le code d'origine était probablement beaucoup plus long.
Florian F
12

Dans votre deuxième exemple, le Mapdoit être un membre statique privé pour éviter la surcharge d'initialisation redondante.

Pour de grandes quantités de valeurs, la carte fonctionnera mieux. En utilisant une table de hachage, on peut rechercher la réponse en temps constant. La construction multiple-if doit comparer l'entrée à chacune des possibilités jusqu'à ce qu'elle trouve la bonne réponse.

En d'autres termes, la recherche de carte est O (1) tandis que les ifs sont O (n) où n est le nombre d'entrées possibles.

La création de carte est O (n), mais n'est effectuée qu'une seule fois s'il s'agit d'un état constant statique. Pour une recherche effectuée fréquemment, la carte surclassera les ifinstructions à long terme, au prix d'un peu plus de temps au démarrage du programme (ou de la classe chargée, selon la langue).

Cela dit, la carte n'est pas toujours le bon outil pour ce travail. C'est bien quand il y a beaucoup de valeurs, ou les valeurs doivent être configurables via un fichier texte, une entrée utilisateur ou une base de données (auquel cas la carte agit comme un cache).


la source
Oui, pour de grandes quantités de valeurs, la carte fonctionnera mieux. Mais la quantité de valeurs est fixe et elle est de trois.
RemcoGerlich
la création de la carte est O (N) seule la recherche en elle est O (1).
Pieter B
Bon point, j'ai clarifié ma réponse.
La carte nécessite également un auto-unboxing qui affecte très légèrement les performances.
user949300
@ user949300 en Java, oui, et le code dans la question semble être Java. Cependant, il n'est étiqueté avec aucun langage et l'approche fonctionne sur plusieurs langages (y compris C # et C ++, aucun des deux ne nécessitant de boxe).
3

Il y a deux vitesses dans le logiciel: le temps qu'il faut pour écrire / lire / déboguer le code; et le temps qu'il faut pour exécuter le code.

Si vous pouvez me convaincre (et vos réviseurs de code) que la fonction de hachage est en effet plus lente que le if / then / else (après refactorisation pour faire un hashmap statique) ET vous pouvez me convaincre / réviseurs que son appelé suffisamment de temps pour faire un réel différence, puis allez-y et remplacez le hashmap par le if / else.

Sinon, le code de hachage est éminemment lisible; et (probablement) sans bug; vous pouvez le déterminer rapidement simplement en le regardant. Vous ne pouvez pas vraiment dire la même chose au sujet du if / else sans vraiment l'étudier; la différence est encore plus exagérée lorsqu'il existe des centaines d'options.

Robert Hanson
la source
3
Eh bien, cette objection tombe en morceaux quand on compare avec un interrupteur à la place ...
Deduplicator
Se désagrège également si vous écrivez les instructions if sur une seule ligne.
gnasher729
2
En plaçant la création de la table de hachage ailleurs, vous ne faites que rendre plus difficile la compréhension de ce qui se passe réellement. Vous devez voir ces touches et valeurs pour savoir quel est l'effet réel de la fonction.
RemcoGerlich
2

Je préfère grandement la réponse de style HashMap.

Il y a une métrique pour cela

Il existe une métrique de qualité de code appelée Complexité cyclomatique . Cette métrique compte essentiellement le nombre de chemins différents à travers le code ( comment calculer la complexité cyclomatique ).

Pour chaque chemin d'exécution possible, une méthode devient de plus en plus difficile à comprendre ET à tester pleinement l'exactitude.

Cela revient au fait que "contrôler les mots clés" comme: ifs, elses, whiles, etc ... utilise des tests booléens qui peuvent être erronés. L'utilisation répétée de "contrôle des mots clés" produit un code fragile.

Des avantages supplémentaires

De plus, «l'approche basée sur la carte» encourage les développeurs à considérer les paires entrée-sortie comme un ensemble de données qui peut être extrait, réutilisé, manipulé au moment de l'exécution, testé et vérifié. Par exemple, ci-dessous, j'ai réécrit "foo" afin que nous ne soyons pas définitivement enfermés dans "A-> 12, B-> 21, C-> 45":

int foo(String s){
    HashMap<String, Integer> map = getCurrentMapping();
    return map.get(s);
}

rachet_freak mentionne ce type de refactor dans sa réponse, il plaide pour la vitesse et la réutilisation, je plaide pour la flexibilité d'exécution (bien que l'utilisation d'une collection immuable puisse avoir d'énormes avantages selon la situation)

Ivan
la source
1
Ajouter cette flexibilité d'exécution est soit une excellente idée tournée vers l'avenir, soit une sur-génération inutile qui rend beaucoup plus difficile de comprendre ce qui se passe. :-).
user949300
@JimmyJames Le lien fonctionne pour moi: C'est: leepoint.net/principles_and_practices/complexity/…
Ivan
1
@ user949300 Le fait est que les données clé / valeur soutenant la méthode "foo" sont un concept distinct qui pourrait mériter une certaine forme de clarté. La quantité de code que vous souhaitez écrire pour isoler la carte dépendra en grande partie du nombre d'éléments que la carte contient et de la fréquence à laquelle ces éléments devront peut-être changer.
Ivan
1
@ user949300 Une partie de la raison pour laquelle je suggère de retirer la chaîne if-else est que j'ai vu des chaînes if-else exister en groupes. Un peu comme des cafards, s'il y a une méthode basée sur une chaîne if-else, il y a probablement une autre méthode ailleurs dans la base de code avec une chaîne if-else similaire / identique. L'extraction de la carte est payante si nous supposons qu'il pourrait y avoir d'autres méthodes qui utilisent une structure logique similaire de recherche / commutation.
Ivan
Moi aussi, j'ai vu des blocs if / else en double, presque identiques, éparpillés partout. Bien mis
Jon Chesterfield
1

Les données sont meilleures que le code. Pas moins parce qu'il est trop tentant d'ajouter une autre branche au code, mais il est difficile de se tromper en ajoutant une ligne à une table. Cette question en est un petit exemple. Vous écrivez une table de recherche. Soit écrire une implémentation, complète avec la logique conditionnelle et la documentation, ou écrire la table puis la rechercher.

Une table de données est toujours une meilleure représentation de certaines données que le code, l'optimisation modulo passe. La difficulté d'expression de la table peut dépendre du langage - je ne connais pas Java, mais j'espère qu'elle peut implémenter une table de recherche plus simplement que l'exemple de l'OP.

Il s'agit d'une table de recherche en python. Si cela est considéré comme un conflit invitant, veuillez considérer que la question n'est pas balisée java, le refactoring est indépendant du langage et que la plupart des gens ne connaissent pas java.

def foo(s):
    return {
               "A" : 12,
               "B" : 21,
               "C" : 45,
           }[s]

L'idée de restructurer le code pour réduire le temps d'exécution a du mérite, mais je préfère de loin utiliser un compilateur qui hisse la configuration courante plutôt que de le faire moi-même.

Jon Chesterfield
la source
-1 pas une réponse à la question.
Pieter B
Dans quel sens? J'aurais demandé à l'auteur de remplacer la chaîne if else par une carte en partant du principe que les données et le code devraient être séparés. C'est une raison claire pour laquelle le deuxième code est meilleur que le premier, bien que tous les appels map.put soient malheureux.
Jon Chesterfield
2
@JonChesterfield Cette réponse est essentiellement "utiliser une meilleure langue", ce qui est rarement utile.
walpen
@walpen fair point. Ivan a fait à peu près la même observation via Java, donc je n'avais pas clairement besoin de passer en python. Je vais voir si je peux nettoyer un peu
Jon Chesterfield
Dans certains codes java plus anciens, j'avais besoin de quelques cartes pour quelque chose de très similaire, j'ai donc écrit un petit utilitaire pour convertir des tableaux N-dimensionnels de tableaux 2D en une carte. Un peu hacky mais fonctionnait bien. Cette réponse souligne la puissance de Python / JS pour prendre en charge la notation JSONy si simplement et facilement.
user949300