Warum sollte eine HashMap (in Funktionen) verwendet werden, um zu bestimmen, welcher Wert (für einen Schlüssel) zurückgegeben werden soll, wenn ein if else-Konstrukt die Aufgabe in besserer Zeit erledigen kann?

9

Als ich kürzlich in einer großen Firma arbeitete, bemerkte ich, dass die Programmierer dort diesem Codierungsstil folgten:

Angenommen, ich habe eine Funktion, die 12 zurückgibt, wenn der Eingang A ist, 21, wenn der Eingang B ist, und 45, wenn der Eingang C ist.

So kann ich die Funktionssignatur schreiben als:

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");
}

Bei der Codeüberprüfung wurde ich jedoch gebeten, die Funktion wie folgt zu ändern:

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);
}

Ich kann mich nicht davon überzeugen, warum der zweite Code besser ist als der erste. Die Ausführung des zweiten Codes würde definitiv mehr Zeit in Anspruch nehmen.

Der einzige Grund für die Verwendung des zweiten Codes kann sein, dass er eine bessere Lesbarkeit bietet. Wenn die Funktion jedoch mehrmals aufgerufen wird, verlangsamt die zweite Funktion dann nicht die Laufzeit des Dienstprogramms, das sie aufruft?

Was denkst du darüber?

Nikunj Banka
quelle
4
Für drei Werte scheint eine Karte übertrieben zu sein ( switchscheint angemessener zu sein als if-else). Aber irgendwann wird es problematisch. Der Hauptvorteil der Verwendung einer Karte besteht darin, dass Sie sie aus einer Datei, einer Tabelle usw. laden können. Wenn Sie die Eingabe in die Karte fest codieren, wird über einen Switch nicht viel Wert angezeigt.
JimmyJames

Antworten:

16

Der Punkt ist, die Erstellung der Hashmap außerhalb der Funktion zu verschieben und einmal (oder nur seltener als sonst) auszuführen.

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);
}

Allerdings konnte Java7 seitdem (endgültige) Zeichenfolgen in Switches haben:

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");
}
Ratschenfreak
quelle
1
Ich sehe nicht, wie dies OPs Frage beantwortet, also -1 dort. Aber +1 für den Vorschlag eines Schalters.
user949300
Es zeigt, wie der Codierungsstil richtig implementiert wird, um tatsächlich Sinn zu machen und möglicherweise die Leistung zu verbessern. Es macht immer noch keinen Sinn für 3 Auswahlmöglichkeiten, aber der ursprüngliche Code war wahrscheinlich viel länger.
Florian F
12

In Ihrem zweiten Beispiel Mapsollte das ein privates statisches Mitglied sein, um redundanten Initialisierungsaufwand zu vermeiden.

Bei großen Wertemengen ist die Leistung der Karte besser. Mit einer Hashtabelle kann man die Antwort in konstanter Zeit nachschlagen. Das Multiple-If-Konstrukt muss die Eingabe mit jeder der Möglichkeiten vergleichen, bis es die richtige Antwort findet.

Mit anderen Worten ist die Kartensuche O (1), während die ifs O (n) sind, wobei n die Anzahl möglicher Eingaben ist.

Die Kartenerstellung ist O (n), wird jedoch nur einmal durchgeführt, wenn es sich um einen statischen konstanten Zustand handelt. Bei einer häufig durchgeführten Suche übertrifft die Map die ifAnweisungen auf lange Sicht, was etwas mehr Zeit kostet, wenn das Programm gestartet wird (oder die Klasse je nach Sprache geladen wird).

Trotzdem ist die Karte nicht immer das richtige Werkzeug für diesen Job. Es ist schön, wenn es viele Werte gibt oder die Werte über eine Textdatei, Benutzereingabe oder Datenbank konfigurierbar sein müssen (in diesem Fall fungiert die Karte als Cache).


quelle
Ja, bei großen Wertemengen ist die Leistung der Karte besser. Aber die Anzahl der Werte ist fest und es sind drei.
RemcoGerlich
Die Erstellung der Karte ist O (N), nur die Suche darin ist O (1).
Pieter B
Gute Punkte, ich habe meine Antwort klargestellt.
Die Karte erfordert auch ein automatisches Unboxing, was die Leistung nur geringfügig beeinträchtigt.
user949300
@ user949300 in Java, ja, und der Code in der Frage scheint Java zu sein. Es ist jedoch keiner Sprache zugeordnet, und der Ansatz funktioniert in mehreren Sprachen (einschließlich C # und C ++, für die keine Box erforderlich ist).
3

In der Software gibt es zwei Geschwindigkeiten: die Zeit, die zum Schreiben / Lesen / Debuggen des Codes benötigt wird; und die Zeit, die zum Ausführen des Codes benötigt wird.

Wenn Sie mich (und Ihre Code-Prüfer) davon überzeugen können, dass die Hashmap-Funktion tatsächlich langsamer ist als das if / then / else (nach dem Refactoring, um eine statische Hashmap zu erstellen), UND Sie mich / die Prüfer davon überzeugen können, dass sie genügend Male aufgerufen wird, um eine tatsächliche zu erstellen Unterschied, dann fahren Sie fort und ersetzen Sie die Hashmap durch das if / else.

Andernfalls ist der Hashmap-Code hervorragend lesbar. und (wahrscheinlich) fehlerfrei; Sie können dies schnell feststellen, indem Sie es sich ansehen. Man kann nicht wirklich dasselbe über das Wenn / Sonst sagen, ohne es wirklich zu studieren; Der Unterschied ist noch übertrieben, wenn es Hunderte von Optionen gibt.

Robert Hanson
quelle
3
Nun, dieser Einwand fällt auseinander, wenn man stattdessen mit einem Schalter vergleicht ...
Deduplikator
Fällt auch auseinander, wenn Sie die if-Anweisungen in eine einzelne Zeile schreiben.
Gnasher729
2
Indem Sie die Hashmap-Erstellung an einem anderen Ort ablegen, wird es nur schwieriger, herauszufinden, was tatsächlich passiert. Sie müssen diese Tasten und Werte sehen, um zu wissen, wie sich die Funktion tatsächlich auswirkt.
RemcoGerlich
2

Ich bevorzuge die Antwort im HashMap-Stil.

Dafür gibt es eine Metrik

Es gibt eine Codequalitätsmetrik namens Cyclomatic Complexity . Diese Metrik zählt im Wesentlichen die Anzahl der verschiedenen Pfade durch den Code ( wie die zyklomatische Komplexität berechnet wird ).

Für jeden möglichen Ausführungspfad wird es immer schwieriger, eine Methode zu verstehen UND die Richtigkeit vollständig zu testen.

Es läuft darauf hinaus, dass "Keywords steuern" wie: ifs, elses, whiles usw. boolesche Tests nutzen, die falsch sein können. Die wiederholte Verwendung von "Schlüsselwörtern steuern" erzeugt fragilen Code.

Zusätzliche Vorteile

Der "kartenbasierte Ansatz" ermutigt Entwickler außerdem, sich die Eingabe-Ausgabe-Paare als einen Datensatz vorzustellen, der zur Laufzeit extrahiert, wiederverwendet, bearbeitet, getestet und verifiziert werden kann. Zum Beispiel habe ich unten "foo" umgeschrieben, damit wir nicht dauerhaft in "A-> 12, B-> 21, C-> 45" eingeschlossen sind:

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

rachet_freak erwähnt diese Art von Refactor in seiner Antwort, er plädiert für Geschwindigkeit und Wiederverwendung, ich plädiere für Laufzeitflexibilität (obwohl die Verwendung einer unveränderlichen Sammlung je nach Situation enorme Vorteile haben kann)

Ivan
quelle
1
Das Hinzufügen dieser Laufzeitflexibilität ist entweder eine großartige, vorausschauende Idee oder eine unnötige Überarbeitung, die es viel schwieriger macht, herauszufinden, was passiert. :-).
user949300
@ JimmyJames Der Link funktioniert für mich: Es ist: leepoint.net/principles_and_practices/complexity/…
Ivan
1
@ user949300 Der Punkt ist, dass die Schlüssel- / Wertdaten, die die "foo" -Methode unterstützen, ein separates Konzept sind, das irgendeine Form von Klarheit verdienen könnte . Die Menge an Code, die Sie schreiben möchten, um die Karte zu isolieren, hängt stark davon ab, wie viele Elemente die Karte enthält und wie oft diese Elemente möglicherweise geändert werden müssen.
Ivan
1
@ user949300 Ein Grund, warum ich vorschlage, die if-else-Kette herauszuziehen, ist, dass ich gesehen habe, dass if-else-Ketten in Gruppen existieren. Ein bisschen wie Kakerlaken, wenn es eine Methode gibt, die auf einer if-else-Kette basiert, gibt es wahrscheinlich eine andere Methode an anderer Stelle in der Codebasis mit einer ähnlichen / identischen if-else-Kette. Das Extrahieren der Karte zahlt sich aus, wenn wir davon ausgehen, dass es andere Methoden gibt, die eine ähnliche Lookup- / Switch-ähnliche Logikstruktur verwenden.
Ivan
Ich habe auch doppelte, fast identische Blöcke gesehen, die überall verstreut sind. Gut ausgedrückt
Jon Chesterfield
1

Daten sind besser als Code. Nicht zuletzt, weil es zu verlockend ist, dem Code noch einen weiteren Zweig hinzuzufügen, ist es jedoch schwierig, einer Zeile eine Zeile hinzuzufügen. Diese Frage ist ein kleines Beispiel dafür. Sie schreiben eine Nachschlagetabelle. Schreiben Sie entweder eine Implementierung mit bedingter Logik und Dokumentation oder schreiben Sie die Tabelle aus und suchen Sie danach.

Eine Datentabelle ist immer eine bessere Darstellung einiger Daten als Code, Modulo-Optimierungsdurchläufe. Wie schwierig es ist, die Tabelle auszudrücken, hängt möglicherweise von der Sprache ab - ich kenne Java nicht, würde aber hoffen, dass es eine Nachschlagetabelle einfacher als das Beispiel im OP implementieren kann.

Dies ist eine Nachschlagetabelle in Python. Wenn dies als einladender Konflikt angesehen wird, beachten Sie bitte, dass die Frage nicht mit Java gekennzeichnet ist, das Refactoring sprachunabhängig ist und dass die meisten Menschen Java nicht kennen.

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

Die Idee, den Code neu zu strukturieren, um die Laufzeit zu verkürzen, hat sich bewährt, aber ich würde viel lieber einen Compiler verwenden, der das allgemeine Setup hochzieht, als dies selbst zu tun.

Jon Chesterfield
quelle
-1 keine Antwort auf die Frage.
Pieter B
In welchem ​​Sinne? Ich hätte den Autor gebeten, die if else-Kette durch eine Karte zu ersetzen, da Daten und Code getrennt sein sollten. Dies ist ein klarer Grund, warum der zweite Code besser ist als der erste, obwohl alle map.put-Aufrufe unglücklich sind.
Jon Chesterfield
2
@ JonChesterfield Diese Antwort lautet im Grunde "benutze eine bessere Sprache", was selten hilfreich ist.
Walpen
@walpen fair point. Ivan hat ungefähr die gleiche Beobachtung über Java gemacht, so dass ich nicht eindeutig auf Python fallen musste. Ich werde sehen, ob ich es ein wenig aufräumen kann
Jon Chesterfield
In einigen älteren Java-Codes brauchte ich ein paar Karten für etwas sehr Ähnliches, also schrieb ich ein kleines Dienstprogramm, um N-dimensionale Arrays von 2-D-Arrays in eine Karte zu konvertieren. Ein bisschen hacky, hat aber gut funktioniert. Diese Antwort zeigt die Leistungsfähigkeit von Python / JS bei der einfachen und einfachen Unterstützung der JSONy-Notation.
user949300