Perché una HashMap dovrebbe essere usata (nelle funzioni) per determinare quale valore restituire (per una chiave) quando un costrutto if else può fare il lavoro in un momento migliore?


9

Mentre di recente lavoravo in una grande azienda, ho notato che i programmatori hanno seguito questo stile di programmazione:

Supponiamo che io abbia una funzione che restituisce 12 se l'ingresso è A, 21 se l'ingresso è B e 45 se l'ingresso è C.

Quindi posso scrivere la firma della funzione come:

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

Ma sulla revisione del codice mi è stato chiesto di modificare la funzione come segue:

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

Non riesco a convincermi del perché il secondo codice sia migliore del primo. Il secondo codice richiederebbe sicuramente più tempo per l'esecuzione.

L'unico motivo per utilizzare il secondo codice può essere che offre una migliore leggibilità. Ma se la funzione viene chiamata molte volte, la seconda funzione non rallenterebbe il tempo di esecuzione dell'utilità che la chiama?

Cosa ne pensi di questo?


4
Per tre valori, una mappa sembra eccessiva ( switchsembra più appropriata di if-else). Ma a un certo punto, diventa problematico. Il vantaggio principale dell'uso di una mappa è che puoi caricarlo da un file o da una tabella, ecc. Se stai codificando l'input sulla mappa, non vedo molto valore su uno switch.
JimmyJames,

Risposte:


16

Il punto è spostare la creazione dell'hashmap al di fuori della funzione e farlo una volta (o solo meno volte rispetto al resto).

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

Tuttavia da allora java7 è stato in grado di avere stringhe (finali) negli switch:

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

1
Non vedo come questo risponda alla domanda dei PO, quindi -1 lì. Ma +1 per suggerire switch.
user949300

Illustra come implementare correttamente lo stile di codifica per avere effettivamente senso e possibilmente migliorare le prestazioni. Non ha ancora senso per 3 scelte, ma il codice originale era probabilmente molto più lungo.
Florian F,

12

Nel secondo esempio, Mapdovrebbe essere un membro statico privato per evitare sovraccarico di inizializzazione ridondante.

Per grandi quantità di valori, la mappa funzionerà meglio. Usando una tabella hash, si può cercare la risposta in tempo costante. Il costrutto multiplo deve confrontare l'input con ciascuna delle possibilità fino a trovare la risposta giusta.

In altre parole, la ricerca della mappa è O (1) mentre le ifs sono O (n) dove n è il numero di possibili input.

La creazione della mappa è O (n), ma viene eseguita una sola volta se si tratta di uno stato costante statico. Per una ricerca eseguita frequentemente, la mappa supererà le ifdichiarazioni a lungo termine, al costo di un po 'più di tempo all'avvio del programma (o la classe viene caricata, a seconda della lingua).

Detto questo, la mappa non è sempre lo strumento giusto per questo lavoro. È utile quando ci sono molti valori o i valori devono essere configurabili tramite un file di testo, input dell'utente o database (nel qual caso la mappa funge da cache).


Sì, per grandi quantità di valori, la mappa funzionerà meglio. Ma la quantità di valori è fissa ed è tre.
RemcoGerlich,

la creazione della mappa è O (N) solo la ricerca in essa è O (1).
Pieter B,

Aspetti positivi, ho chiarito la mia risposta.

La mappa richiede anche l'auto-unboxing che influenza leggermente le prestazioni.
user949300

@ user949300 in Java, sì, e il codice nella domanda sembra essere Java. Tuttavia, non è taggato con nessuna lingua e l'approccio funziona in più lingue (inclusi C # e C ++, nessuno dei quali richiede la boxe).

3

Ci sono due velocità nel software: il tempo necessario per scrivere / leggere / eseguire il debug del codice; e il tempo necessario per eseguire il codice.

Se riesci a convincere me (e i tuoi revisori di codice) che la funzione hashmap è effettivamente più lenta di if / then / else (dopo il refactoring per creare una hashmap statica) E puoi convincermi / revisori che è stato chiamato abbastanza volte per rendere effettivo differenza, quindi vai avanti e sostituisci l'hashmap con if / else.

Altrimenti, il codice hashmap è leggibile in modo eminente; e (probabilmente) privo di bug; puoi determinarlo rapidamente semplicemente guardandolo. Non puoi davvero dire la stessa cosa dell'if / altro senza davvero studiarlo; la differenza è ancora più esagerata quando ci sono centinaia di opzioni.


3
Bene, quell'obiezione cade quando si confronta con un interruttore invece ...
Deduplicatore

Inoltre cade a pezzi se scrivi le istruzioni if ​​in un'unica riga.
gnasher729,

2
Mettendo la creazione di hashmap da qualche altra parte, stai solo rendendo più difficile capire cosa succede realmente. È necessario vedere quei tasti e valori per sapere quale sia l'effetto reale della funzione.
RemcoGerlich,

2

Preferisco di gran lunga la risposta in stile HashMap.

C'è una metrica per questo

Esiste una metrica di qualità del codice chiamata Complessità ciclomatica . Questa metrica fondamentalmente conta il numero di percorsi diversi attraverso il codice ( come calcolare la complessità ciclomatica ).

Per ogni possibile percorso di esecuzione un metodo diventa sempre più difficile da comprendere e testare completamente la correttezza.

Si riduce al fatto che "il controllo delle parole chiave" come: ifs, elses, whiles, ecc ... sfrutta test booleani che possono essere sbagliati. L'uso ripetuto di "controllo delle parole chiave" produce un codice fragile.

Benefici addizionali

Inoltre, l '"approccio basato su mappe" incoraggia gli sviluppatori a pensare alle coppie input-output come un set di dati che può essere estratto, riutilizzato, manipolato in fase di esecuzione, testato e verificato. Ad esempio, di seguito riscrivo "pippo" in modo da non essere permanentemente bloccati in "A-> 12, B-> 21, C-> 45":

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

rachet_freak menziona questo tipo di refattore nella sua risposta, argomenta per la velocità e il riutilizzo, sto sostenendo per la flessibilità di runtime (anche se l'uso di una raccolta immutabile può avere enormi benefici a seconda della situazione)


1
L'aggiunta di tale flessibilità di runtime è una grande idea lungimirante o inutile eccessiva ingegnerizzazione che rende molto più difficile capire cosa sta succedendo. :-).
user949300

@JimmyJames Il link funziona per me: è: leepoint.net/principles_and_practices/complexity/…
Ivan

1
@ user949300 Il punto è che i dati chiave / valore che supportano il metodo "pippo" sono un concetto separato che potrebbe meritare qualche forma di chiarezza. La quantità di codice che si desidera scrivere per isolare la mappa dipenderà in larga misura dal numero di elementi contenuti nella mappa e dalla frequenza con cui tali elementi potrebbero dover essere modificati.
Ivan

1
@ user949300 Parte del motivo per cui suggerisco di estrarre la catena if-else è che ho visto che esistono catene if-else in gruppi. Un po 'come gli scarafaggi simili, se esiste un metodo basato su una catena if-else probabilmente c'è un altro metodo altrove nella base di codice con una catena if-else simile / identica. L'estrazione della mappa paga dividendi se assumiamo che potrebbero esserci altri metodi che utilizzano una struttura logica simile a un look-up / switch.
Ivan

Anch'io ho visto blocchi duplicati, quasi identici se / else sparsi dappertutto. Ben messo
Jon Chesterfield il

1

I dati sono migliori del codice. Non da ultimo perché è troppo allettante aggiungere un altro ramo al codice, ma è difficile sbagliare aggiungere una riga a una tabella. Questa domanda è un piccolo esempio di questo. Stai scrivendo una tabella di ricerca. O scrivere un'implementazione, completa di logica condizionale e documentazione, oppure scrivere la tabella e cercarla al suo interno.

Una tabella di dati è sempre una migliore rappresentazione di alcuni dati rispetto al codice, passa l'ottimizzazione del modulo. Quanto sia difficile esprimere la tabella può dipendere dal linguaggio - non conosco Java, ma spero che possa implementare una tabella di ricerca più semplicemente dell'esempio nell'OP.

Questa è una tabella di ricerca in Python. Se questo è visto come un conflitto invitante, ti preghiamo di considerare che la domanda non è taggata java, il refactoring è agnostico sulla lingua e che la maggior parte delle persone non conosce java.

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

L'idea di ristrutturare il codice per ridurre il tempo di esecuzione ha valore, ma preferirei di gran lunga utilizzare un compilatore che solleva l'installazione comune piuttosto che farlo da solo.


-1 non una risposta alla domanda.
Pieter B,

In che senso? Avrei chiesto all'autore di sostituire la catena if else con una mappa sulla base della necessità di separare dati e codice. Questo è un chiaro motivo per cui il secondo codice è migliore del primo, sebbene tutte le chiamate map.put siano sfortunate.
Jon Chesterfield,

2
@JonChesterfield Questa risposta è fondamentalmente "usa una lingua migliore", che è raramente utile.
Walpen

@walpen fair point. Ivan ha fatto all'incirca la stessa osservazione tramite Java, quindi non avevo bisogno di passare a Python. Vedrò se riesco a ripulirlo un po '
Jon Chesterfield il

In alcuni vecchi codici Java, avevo bisogno di alcune mappe per qualcosa di molto simile, quindi ho scritto una piccola utilità per convertire array N-dimensionali di array 2D in una mappa. Un po 'confuso ma ha funzionato bene. Questa risposta sottolinea la potenza di Python / JS nel supportare la notazione JSONy in modo semplice e facile.
user949300
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.