Troppe dichiarazioni "if"?


263

Il seguente codice funziona come ne ho bisogno, ma è brutto, eccessivo o un numero di altre cose. Ho esaminato le formule e ho tentato di scrivere alcune soluzioni, ma ho finito con una quantità simile di dichiarazioni.

Esiste un tipo di formula matematica che mi gioverebbe in questo caso o sono 16 se le affermazioni sono accettabili?

Per spiegare il codice, è per una specie di gioco a turni simultanei. Due giocatori hanno quattro pulsanti di azione ciascuno e i risultati provengono da un array (0-3), ma le variabili 'one' e 'two' possono essere assegnato qualcosa se questo aiuta. Il risultato è 0 = nessuna vittoria, 1 = p1 vince, 2 = p2 vince, 3 = entrambe vince.

public int fightMath(int one, int two) {

    if(one == 0 && two == 0) { result = 0; }
    else if(one == 0 && two == 1) { result = 0; }
    else if(one == 0 && two == 2) { result = 1; }
    else if(one == 0 && two == 3) { result = 2; }
    else if(one == 1 && two == 0) { result = 0; }
    else if(one == 1 && two == 1) { result = 0; }
    else if(one == 1 && two == 2) { result = 2; }
    else if(one == 1 && two == 3) { result = 1; }
    else if(one == 2 && two == 0) { result = 2; }
    else if(one == 2 && two == 1) { result = 1; }
    else if(one == 2 && two == 2) { result = 3; }
    else if(one == 2 && two == 3) { result = 3; }
    else if(one == 3 && two == 0) { result = 1; }
    else if(one == 3 && two == 1) { result = 2; }
    else if(one == 3 && two == 2) { result = 3; }
    else if(one == 3 && two == 3) { result = 3; }

    return result;
}


9
Sicuramente c'è qualche logica qui che può essere generalizzata piuttosto che forzata bruta? Sicuramente c'è qualche funzione f(a, b)che dà la risposta nel caso generale? Non hai spiegato la logica del calcolo, quindi tutte le risposte sono solo rossetto su un maiale. Vorrei iniziare a ripensare seriamente la logica del programma, l'utilizzo di intflag per le azioni è molto obsoleto. enumGli s possono contenere la logica e sono descrittivi, questo ti permetterebbe di scrivere il tuo codice in un modo più moderno.
Boris the Spider,

Dopo aver letto le risposte fornite da @Steve Benett nella sua domanda alternativa di cui sopra, posso presumere che non ci sia una risposta diretta alla formula in quanto è essenzialmente la stessa di un database. Ho cercato di spiegare nella domanda originale che stavo realizzando un gioco semplice (combattente) e gli utenti hanno una selezione di 4 pulsanti: blockHigh (0), blockLow (1), attackHigh (2) e attackLow (3). Questi numeri sono conservati in un array fino a quando non è necessario. Successivamente vengono utilizzati dalla funzione 'fightMath ()' che chiama le selezioni di playerOne contro playerTwos per dare il risultato. Nessun rilevamento effettivo delle collisioni.
TomFirth

9
Se hai una risposta, per favore pubblicala come tale. La discussione estesa nei commenti è difficile da seguire, specialmente quando è coinvolto il codice. Se vuoi parlare se questa domanda avrebbe dovuto essere migrata a Code Review, c'è una discussione Meta su questo.
George Stocker,

1
Cosa intendi con "uguale a un database"? Se questi valori sono nel database, estrarli da lì. Altrimenti, se è davvero così complesso, lo lascerei come ce l'hai e aggiungerei commenti di logica aziendale dopo ogni riga in modo che le persone capiscano cosa sta succedendo. È meglio (per me) lungo ed esplicito - qualcuno in futuro può capire cosa sta succedendo. Se lo metti in una mappa o provi a salvare 8 righe di codice, il lato positivo è davvero piccolo e il ridimensionamento è più grande: lo rendi sempre più confuso per qualcuno che deve leggere il tuo codice un giorno.
skaz,

Risposte:


600

Se non riesci a trovare una formula, puoi utilizzare una tabella per un numero così limitato di risultati:

final int[][] result = new int[][] {
  { 0, 0, 1, 2 },
  { 0, 0, 2, 1 },
  { 2, 1, 3, 3 },
  { 1, 2, 3, 3 }
};
return result[one][two];

7
Questo è interessante perché non ho mai visto questa soluzione prima. Non sono sicuro di aver compreso il risultato di ritorno, ma mi divertirò a provarlo.
TomFirth

4
Non hai bisogno dell'asserzione, Java lancerà IndexOutOfBoundsExceptioncomunque se uno o più indici sono fuori limite.
JAB

43
@JoeHarper Se vuoi qualcosa di facile da leggere, non utilizzerai i numeri magici in primo luogo e avrai un commento che spiega la mappatura. Così com'è, preferisco questa versione all'originale, ma per qualcosa di mantenibile a lungo termine utilizzerei un approccio che coinvolge tipi enumerati o almeno costanti nominate.
JAB

13
@JoeHarper "Teoricamente" è una cosa, "praticamente" è un'altra. Certo, cerco di usare un nome descrittivo (ad eccezione della convenzione i/ j/ kper le variabili del ciclo), le costanti nominate, la disposizione del mio codice in modo leggibile, ecc., Ma quando i nomi delle variabili e delle funzioni iniziano a contenere più di 20 caratteri ciascuno lo trovo effettivamente porta a un codice meno leggibile. Il mio approccio abituale è cercare un codice comprensibile ma conciso con commenti qua e là per spiegare perché il codice è strutturato così com'è (rispetto a come). Mettere il perché nei nomi ingombra tutto.
JAB

13
Mi piace questa soluzione per questo problema specifico perché i risultati sono in realtà dettati da una matrice di risultati.
Prova il

201

Poiché il tuo set di dati è così piccolo, puoi comprimere tutto in 1 intero lungo e trasformarlo in una formula

public int fightMath(int one,int two)
{
   return (int)(0xF9F66090L >> (2*(one*4 + two)))%4;
}

Variante più bit per bit:

Questo fa uso del fatto che tutto è un multiplo di 2

public int fightMath(int one,int two)
{
   return (0xF9F66090 >> ((one << 3) | (two << 1))) & 0x3;
}

L'origine della costante magica

Cosa posso dire? Il mondo ha bisogno di magia, a volte la possibilità che qualcosa richieda la sua creazione.

L'essenza della funzione che risolve il problema di OP è una mappa da 2 numeri (uno, due), dominio {0,1,2,3} nell'intervallo {0,1,2,3}. Ciascuna delle risposte si è avvicinata a come implementare quella mappa.

Inoltre, puoi vedere in un numero di risposte una riaffermazione del problema come una mappa di 1 numero di base 2 a 4 cifre N (uno, due) dove uno è la cifra 1, due è la cifra 2 e N = 4 * uno + due; N = {0,1,2, ..., 15} - sedici valori diversi, questo è importante. L'output della funzione è un numero di base 4 a 1 cifra {0,1,2,3} - 4 valori diversi, anch'essi importanti.

Ora, un numero di base 4 di 1 cifra può essere espresso come un numero di base 2 di 2 cifre; {0,1,2,3} = {00,01,10,11} e quindi ogni uscita può essere codificata con solo 2 bit. Da sopra, ci sono solo 16 uscite diverse possibili, quindi 16 * 2 = 32 bit è tutto ciò che è necessario per codificare l'intera mappa; questo può rientrare in 1 intero.

La costante M è una codifica della mappa m in cui m (0) è codificato in bit M [0: 1], m (1) è codificato in bit M [2: 3] e m (n) è codificato in bit M [n * 2: n * 2 + 1].

Non resta che indicizzare e restituire la parte giusta della costante, in questo caso puoi spostare M a destra 2 * N volte e prendere i 2 bit meno significativi, ovvero (M >> 2 * N) e 0x3. Le espressioni (una << 3) e (due << 1) stanno semplicemente moltiplicando le cose osservando che 2 * x = x << 1 e 8 * x = x << 3.


79
intelligente, ma nessun altro che legge il codice avrà la possibilità di capirlo.
Aran Mulholland,

106
Penso che questa sia una pratica estremamente negativa. Nessun altro, tranne l'autore, lo capirà. Vuoi guardare un pezzo di codice e comprenderlo rapidamente. Ma questa è solo una perdita di tempo.
Balázs Németh,

14
Sono con @ BalázsMáriaNémeth su questo. Anche se molto impressionante, dovresti codificare per psicopatici violenti!
OrhanC1,

90
Tutti i downvoter pensano che questo sia un odore di codice orribile. Tutti i votanti pensano allo stesso modo, ma ammirano l'intelligenza che c'è dietro. +1 (non usare mai questo codice.)
usr

4
Che bell'esempio di scrivere solo codice !
lanka

98

Non mi piace nessuna delle soluzioni presentate ad eccezione di JAB. Nessuno degli altri semplifica la lettura del codice e la comprensione di ciò che viene calcolato .

Ecco come scriverei questo codice: conosco solo C #, non Java, ma ottieni l'immagine:

const bool t = true;
const bool f = false;
static readonly bool[,] attackResult = {
    { f, f, t, f }, 
    { f, f, f, t },
    { f, t, t, t },
    { t, f, t, t }
};
[Flags] enum HitResult 
{ 
    Neither = 0,
    PlayerOne = 1,
    PlayerTwo = 2,
    Both = PlayerOne | PlayerTwo
}
static HitResult ResolveAttack(int one, int two)
{
    return 
        (attackResult[one, two] ? HitResult.PlayerOne : HitResult.Neither) | 
        (attackResult[two, one] ? HitResult.PlayerTwo : HitResult.Neither);
}    

Ora è molto più chiaro ciò che viene calcolato qui: questo sottolinea che stiamo calcolando chi viene colpito da quale attacco e restituendo entrambi i risultati.

Tuttavia questo potrebbe essere ancora migliore; quell'array booleano è alquanto opaco. Mi piace l'approccio alla ricerca da tavolo, ma sarei propenso a scriverlo in modo tale da chiarire quali fossero le semantiche di gioco previste. Cioè, piuttosto che "un attacco pari a zero e una difesa di uno si traduce in nessun colpo", trovano invece un modo per rendere il codice più chiaramente implicito "un attacco a calcio basso e una difesa a blocco basso provoca nessun colpo". Fai in modo che il codice rifletta la logica aziendale del gioco.


66
Senza senso. I programmi più delicati saranno in grado di apprezzare i consigli che vengono forniti qui e applicare lo stile di programmazione al proprio linguaggio. La domanda era come evitare una serie di if. Questo mostra come.
GreenAsade il

6
@ user3414693: Sono ben consapevole che si tratta di una domanda Java. Se leggi attentamente la risposta, diventa chiaro. Se ritieni che la mia risposta non sia saggia, ti incoraggio a scrivere la tua risposta che ti piace di più.
Eric Lippert,

1
@EricLippert Mi piace anche la soluzione di JAB. IMHO, il tipo enum in C # lascia molto a desiderare. Non segue la fossa della filosofia di successo che fanno le altre funzionalità. Ad esempio stackoverflow.com/a/847353/92414 Il team c # ha in programma di creare un nuovo tipo di enum (in modo da evitare la rottura del codice esistente) che è progettato meglio?
SoluzioneYogi

@SolutionYogi: Neanche a me piacciono le enumerazioni in C #, anche se lo sono per buone ragioni storiche. (Principalmente per compatibilità con enumerazioni COM esistenti.) Non sono a conoscenza di piani per aggiungere nuove attrezzature per enumerazioni in C # 6.
Eric Lippert

3
@Lista no, i commenti non vengono eseguiti. OP ha fatto esattamente ciò che dovrebbe essere fatto; converti i commenti in codice chiaro. Vedi ad es. Steve McConnell Code Complete stevemcconnell.com/cccntnt.htm
djechlin

87

È possibile creare una matrice che contiene risultati

int[][] results = {{0, 0, 1, 2}, {0, 0, 2, 1},{2, 1, 3, 3},{2, 1, 3, 3}};

Quando vuoi ottenere valore, userai

public int fightMath(int one, int two) {
  return this.results[one][two]; 
}

69

Altre persone hanno già suggerito la mia idea iniziale, il metodo matrix, ma oltre a consolidare le istruzioni if ​​puoi evitare parte di ciò che hai assicurandoti che gli argomenti forniti siano nell'intervallo previsto e utilizzando i rendimenti sul posto (alcuni codici gli standard che ho visto impongono un punto di uscita per le funzioni, ma ho scoperto che i ritorni multipli sono molto utili per evitare la codifica delle frecce e con la prevalenza delle eccezioni in Java non ha molto senso applicare rigorosamente una regola del genere poiché qualsiasi eccezione non rilevata generata all'interno del metodo costituisce comunque un possibile punto di uscita). Le istruzioni switch di nidificazione sono una possibilità, ma per il piccolo intervallo di valori che stai verificando qui trovo se le istruzioni sono più compatte e non rischiano di determinare una grande differenza di prestazioni,

public int fightMath(int one, int two) {
    if (one > 3 || one < 0 || two > 3 || two < 0) {
        throw new IllegalArgumentException("Result is undefined for arguments outside the range [0, 3]");
    }

    if (one <= 1) {
        if (two <= 1) return 0;
        if (two - one == 2) return 1;
        return 2; // two can only be 3 here, no need for an explicit conditional
    }

    // one >= 2
    if (two >= 2) return 3;
    if (two == 1) return 1;
    return 2; // two can only be 0 here
}

Questo finisce per essere meno leggibile di quanto potrebbe altrimenti essere dovuto all'irregolarità di parti della mappatura input-> result. Preferisco invece lo stile matrice grazie alla sua semplicità e al modo in cui puoi impostare la matrice in modo che abbia senso visivamente (anche se questo è in parte influenzato dai miei ricordi delle mappe di Karnaugh):

int[][] results = {{0, 0, 1, 2},
                   {0, 0, 2, 1},
                   {2, 1, 3, 3},
                   {2, 1, 3, 3}};

Aggiornamento: data la tua menzione di blocco / colpire, ecco una modifica più radicale alla funzione che utilizza tipi enumerati di proprietà / possesso di attributi per gli input e il risultato e modifica anche un po 'il risultato per tenere conto del blocco, che dovrebbe tradursi in un funzione leggibile.

enum MoveType {
    ATTACK,
    BLOCK;
}

enum MoveHeight {
    HIGH,
    LOW;
}

enum Move {
    // Enum members can have properties/attributes/data members of their own
    ATTACK_HIGH(MoveType.ATTACK, MoveHeight.HIGH),
    ATTACK_LOW(MoveType.ATTACK, MoveHeight.LOW),
    BLOCK_HIGH(MoveType.BLOCK, MoveHeight.HIGH),
    BLOCK_LOW(MoveType.BLOCK, MoveHeight.LOW);

    public final MoveType type;
    public final MoveHeight height;

    private Move(MoveType type, MoveHeight height) {
        this.type = type;
        this.height = height;
    }

    /** Makes the attack checks later on simpler. */
    public boolean isAttack() {
        return this.type == MoveType.ATTACK;
    }
}

enum LandedHit {
    NEITHER,
    PLAYER_ONE,
    PLAYER_TWO,
    BOTH;
}

LandedHit fightMath(Move one, Move two) {
    // One is an attack, the other is a block
    if (one.type != two.type) {
        // attack at some height gets blocked by block at same height
        if (one.height == two.height) return LandedHit.NEITHER;

        // Either player 1 attacked or player 2 attacked; whoever did
        // lands a hit
        if (one.isAttack()) return LandedHit.PLAYER_ONE;
        return LandedHit.PLAYER_TWO;
    }

    // both attack
    if (one.isAttack()) return LandedHit.BOTH;

    // both block
    return LandedHit.NEITHER;
}

Non devi nemmeno cambiare la funzione stessa se vuoi aggiungere blocchi / attacchi di più altezze, solo gli enum; l'aggiunta di ulteriori tipi di mosse richiederà probabilmente una modifica della funzione. Inoltre, EnumSets potrebbe essere più estensibile dell'utilizzo di enumerazioni extra come proprietà dell'enum principale, ad esempio EnumSet<Move> attacks = EnumSet.of(Move.ATTACK_HIGH, Move.ATTACK_LOW, ...);e quindi attacks.contains(move)piuttosto che move.type == MoveType.ATTACK, sebbene l'uso di EnumSets sarà probabilmente leggermente più lento dei controlli di uguale diretto.


Nel caso in cui un blocco riuscito comporti un contatore, è possibile sostituirlo if (one.height == two.height) return LandedHit.NEITHER;con

if (one.height == two.height) {
    // Successful block results in a counter against the attacker
    if (one.isAttack()) return LandedHit.PLAYER_TWO;
    return LandedHit.PLAYER_ONE;
}

Inoltre, la sostituzione di alcune delle ifdichiarazioni con l'uso dell'operatore ternario ( boolean_expression ? result_if_true : result_if_false) potrebbe rendere il codice più compatto (ad esempio, il codice nel blocco precedente diventerebbe return one.isAttack() ? LandedHit.PLAYER_TWO : LandedHit.PLAYER_ONE;), ma ciò può portare a oneliners più difficili da leggere, quindi non vorrei lo consiglio per ramificazioni più complesse.


Lo esaminerò sicuramente, ma il mio codice attuale mi consente di utilizzare il valore int di onee twodi essere riutilizzato come punti di partenza sul mio foglio di calcolo. Anche se non richiede molto codice aggiuntivo per consentirlo.
TomFirth

2
@ TomFirth84 C'è una EnumMapclasse che è possibile utilizzare per mappare le enumerazioni ai tuoi offset interi (si potrebbe anche utilizzare i valori ordinali dei membri enum direttamente, ad esempio, Move.ATTACK_HIGH.ordinal()sarebbe 0, Move.ATTACK_LOW.ordinal()sarebbe 1, ecc, ma che è più fragile / meno flessibile che esplicitamente associare ciascun membro a un valore come l'aggiunta di valori enum tra quelli esistenti eliminerebbe il conteggio, il che non sarebbe il caso di un EnumMap.)
JAB

7
Questa è la soluzione più leggibile poiché traduce il codice in qualcosa di significativo per la persona che legge il codice.
David Stanley

Il tuo codice, almeno quello che usa enum, è sbagliato. Secondo le istruzioni if ​​in OP un blocco riuscito porta a un colpo all'attaccante. Ma +1 per un codice significativo.
Taemyr,

2
Puoi anche aggiungere un attack(against)metodo Moveall'enum, restituendo HIT quando la mossa è un attacco andato a buon fine, BACKFIRE quando la mossa è un attacco bloccato e NIENTE quando non è un attacco. In questo modo puoi implementarlo in generale ( public boolean attack(Move other) { if this.isAttack() return (other.isAttack() || other.height != this.height) ? HIT : BACKFIRE; return NOTHING; }) e sovrascriverlo su mosse specifiche quando necessario (mosse deboli che qualsiasi blocco può bloccare, attacchi che non si ritorcono contro ecc.)
riscritto il

50

Perché non usare un array?

Inizierò dall'inizio. Vedo uno schema, i valori vanno da 0 a 3 e vuoi catturare tutti i valori possibili. Questo è il suo tavolo:

0 & 0 = 0
0 & 1 = 0
0 & 2 = 1
0 & 3 = 2
1 & 0 = 0
1 & 1 = 0
1 & 2 = 2
1 & 3 = 1
2 & 0 = 2
2 & 1 = 1
2 & 2 = 3
2 & 3 = 3
3 & 0 = 2
3 & 1 = 1
3 & 2 = 3
3 & 3 = 3

quando guardiamo questa stessa tabella binaria vediamo i seguenti risultati:

00 & 00 = 00
00 & 01 = 00
00 & 10 = 01
00 & 11 = 10
01 & 00 = 00
01 & 01 = 00
01 & 10 = 10
01 & 11 = 01
10 & 00 = 10
10 & 01 = 01
10 & 10 = 11
10 & 11 = 11
11 & 00 = 10
11 & 01 = 01
11 & 10 = 11
11 & 11 = 11

Ora forse vedi già qualche modello ma quando combino il valore uno e due vedo che stai usando tutti i valori 0000, 0001, 0010, ..... 1110 e 1111. Ora combiniamo il valore uno e due per creare un singolo Numero intero a 4 bit.

0000 = 00
0001 = 00
0010 = 01
0011 = 10
0100 = 00
0101 = 00
0110 = 10
0111 = 01
1000 = 10
1001 = 01
1010 = 11
1011 = 11
1100 = 10
1101 = 01
1110 = 11
1111 = 11

Quando lo traduciamo nuovamente in valori decimali, vediamo una matrice molto possibile di valori in cui l'uno e due combinati potrebbero essere usati come indice:

0 = 0
1 = 0
2 = 1
3 = 2
4 = 0
5 = 0
6 = 2
7 = 1
8 = 2
9 = 1
10 = 3
11 = 3
12 = 2
13 = 1
14 = 3
15 = 3

L'array è quindi {0, 0, 1, 2, 0, 0, 2, 1, 2, 1, 3, 3, 2, 1, 3, 3}, dove l'indice è semplicemente uno e due combinati.

Non sono un programmatore Java, ma puoi sbarazzarti di tutte le istruzioni if ​​e scriverlo come qualcosa del genere:

int[] myIntArray = {0, 0, 1, 2, 0, 0, 2, 1, 2, 1, 3, 3, 2, 1, 3, 3};
result = myIntArray[one * 4 + two]; 

Non so se un bit-shift di 2 sia più veloce della moltiplicazione. Ma potrebbe valere la pena provarlo.


2
Uno spostamento di bit di 2 è quasi sicuramente più veloce di una moltiplicazione di 4. Nella migliore delle ipotesi, la moltiplicazione per 4, riconoscerebbe che 4 è 2 ^ 2 e farebbe uno spostamento di bit stesso (tradotto potenzialmente dal compilatore). Francamente, per me, il turno è più leggibile.
Cruncher,

Mi piace il tuo approccio! Essenzialmente appiattisce una matrice 4x4 in una matrice di 16 elementi.
Cameron Tinker,

6
Al giorno d'oggi, se non sbaglio, il compilatore riconoscerà indubbiamente che si sta moltiplicando per una potenza di due e lo ottimizzerà di conseguenza. Quindi per te, il programmatore, il bit shift e la moltiplicazione dovrebbero avere esattamente le stesse prestazioni.
Tanner Swett,

24

Questo utilizza un po 'di bitmagic (lo stai già facendo tenendo due bit di informazione (basso / alto e attacco / blocco) in un unico numero intero):

Non l'ho eseguito, l'ho digitato solo qui, per favore ricontrolla. L'idea sicuramente funziona. EDIT: ora è testato per ogni input, funziona bene.

public int fightMath(int one, int two) {
    if(one<2 && two<2){ //both players blocking
        return 0; // nobody hits
    }else if(one>1 && two>1){ //both players attacking
        return 3; // both hit
    }else{ // some of them attack, other one blocks
        int different_height = (one ^ two) & 1; // is 0 if they are both going for the same height - i.e. blocker wins, and 1 if height is different, thus attacker wins
        int attacker = one>1?1:0; // is 1 if one is the attacker, two is the blocker, and 0 if one is the blocker, two is the attacker
        return (attacker ^ different_height) + 1;
    }
}

O dovrei suggerire di separare i due bit di informazioni in variabili separate? Il codice basato principalmente su operazioni su bit come questa sopra è di solito molto difficile da mantenere.


2
Sono d'accordo con questa soluzione, assomiglia molto a ciò che avevo in mente nel mio commento sulla domanda principale. Preferirei dividerlo in variabili separate, il che renderebbe più semplice aggiungere un attacco medio, ad esempio in futuro.
Yoh

2
Ho appena corretto alcuni bug nel codice sopra dopo averlo testato, ora funziona bene. Andando oltre sulla strada del manipolatore di bit, ho anche trovato una soluzione a una linea, che non è ancora mistica come la maschera di bit in altre risposte, ma è ancora abbastanza complicata da rovinare la tua mente:return ((one ^ two) & 2) == 0 ? (one & 2) / 2 * 3 : ((one & 2) / 2 ^ ((one ^ two) & 1)) + 1;
elias

1
Questa è la risposta migliore dato che qualsiasi nuovo programmatore leggendo capirà effettivamente la magia che sta avvenendo dietro tutti quei numeri magici.
Bezmax,

20

Ad essere onesti, ognuno ha il proprio stile di codice. Non avrei pensato che le prestazioni sarebbero state influenzate troppo. Se lo capisci meglio dell'uso della versione di una custodia, continua a usarlo.

Potresti annidare gli if, quindi potenzialmente ci sarebbe un leggero aumento delle prestazioni per i tuoi ultimi controlli se non avrebbe attraversato tante istruzioni if. Ma nel tuo contesto di un corso Java di base probabilmente non ne trarrà beneficio.

else if(one == 3 && two == 3) { result = 3; }

Quindi, invece di ...

if(one == 0 && two == 0) { result = 0; }
else if(one == 0 && two == 1) { result = 0; }
else if(one == 0 && two == 2) { result = 1; }
else if(one == 0 && two == 3) { result = 2; }

Faresti...

if(one == 0) 
{ 
    if(two == 0) { result = 0; }
    else if(two == 1) { result = 0; }
    else if(two == 2) { result = 1; }
    else if(two == 3) { result = 2; }
}

E riformattalo come preferisci.

Questo non migliora l'aspetto del codice, ma potenzialmente lo accelera un po ', credo.


3
Non so se è davvero una buona pratica, ma per questo caso, probabilmente userei istruzioni switch nidificate. Ci vorrebbe più spazio ma sarebbe davvero chiaro.
coloranti

Funzionerebbe anche, ma credo sia una questione di preferenza. In realtà preferisco le dichiarazioni if ​​poiché praticamente dice cosa sta facendo il codice. Non esprimere la tua opinione ovviamente, qualunque cosa funzioni per te :). Voto per il suggerimento alternativo però!
Joe Harper,

12

Vediamo cosa sappiamo

1: le tue risposte sono simmetriche per P1 (giocatore uno) e P2 (giocatore due). Questo ha senso per un gioco di combattimento ma è anche qualcosa di cui puoi trarre vantaggio per migliorare la tua logica.

2: 3 battiti 0 battiti 2 battiti 1 battiti 3. Gli unici casi non coperti da questi casi sono combinazioni di 0 vs 1 e 2 vs 3. Per dirla in un altro modo, la tabella delle vittorie unica è simile a questa: 0 battiti 2, 1 battiti 3, 2 battiti 1, 3 battiti 0.

3: Se 0/1 vanno l'uno contro l'altro, allora c'è un pareggio senza colpi, ma se 2/3 vanno uno contro l'altro, entrambi colpiscono

Innanzitutto, costruiamo una funzione a senso unico che ci dice se abbiamo vinto:

// returns whether we beat our opponent
public boolean doesBeat(int attacker, int defender) {
  int[] beats = {2, 3, 1, 0};
  return defender == beats[attacker];
}

Possiamo quindi utilizzare questa funzione per comporre il risultato finale:

// returns the overall fight result
// bit 0 = one hits
// bit 1 = two hits
public int fightMath(int one, int two)
{
  // Check to see whether either has an outright winning combo
  if (doesBeat(one, two))
    return 1;

  if (doesBeat(two, one))
    return 2;

  // If both have 0/1 then its hitless draw but if both have 2/3 then they both hit.
  // We can check this by seeing whether the second bit is set and we need only check
  // one's value as combinations where they don't both have 0/1 or 2/3 have already
  // been dealt with 
  return (one & 2) ? 3 : 0;
}

Mentre questo è probabilmente più complesso e probabilmente più lento della ricerca della tabella offerta in molte risposte, credo che sia un metodo superiore perché incapsula effettivamente la logica del tuo codice e lo descrive a chiunque stia leggendo il tuo codice. Penso che questo lo renda una migliore implementazione.

(È da un po 'che non faccio Java, quindi mi scuso se la sintassi è disattivata, si spera che sia ancora comprensibile se ho sbagliato leggermente)

A proposito, 0-3 significa chiaramente qualcosa; non sono valori arbitrari, quindi sarebbe utile nominarli.


11

Spero di capire correttamente la logica. Che ne dici di qualcosa come:

public int fightMath (int one, int two)
{
    int oneHit = ((one == 3 && two != 1) || (one == 2 && two != 0)) ? 1 : 0;
    int twoHit = ((two == 3 && one != 1) || (two == 2 && one != 0)) ? 2 : 0;

    return oneHit+twoHit;
}

Controllare un colpo alto o un colpo basso non è bloccato e lo stesso vale per il giocatore due.

Edit: Algorithm non è stato del tutto compreso, "hit" assegnato durante il blocco che non avevo realizzato (grazie elias):

public int fightMath (int one, int two)
{
    int oneAttack = ((one == 3 && two != 1) || (one == 2 && two != 0)) ? 1 : (one >= 2) ? 2 : 0;
    int twoAttack = ((two == 3 && one != 1) || (two == 2 && one != 0)) ? 2 : (two >= 2) ? 1 : 0;

    return oneAttack | twoAttack;
}

Modo di trovare lo scalpiccio ottimo risultato!
Chad,

1
Mi piace l'approccio, ma temo che questa soluzione manchi completamente della possibilità di colpire bloccando un attacco (ad esempio se uno = 0 e due = 2 restituisce 0, ma 1 è previsto in base alle specifiche). Forse puoi lavorarci su per farlo bene, ma non sono sicuro che il codice risultante sarebbe ancora così elegante, poiché ciò significa che le linee cresceranno un po 'più a lungo.
elias,

Non ho realizzato che un "colpo" è stato assegnato per blocco. Grazie per segnalarlo. Aggiustato con una soluzione molto semplice.
Chris,

10

Non ho esperienza con Java, quindi potrebbero esserci degli errori di battitura. Si prega di considerare il codice come pseudo-codice.

Vorrei andare con un semplice interruttore. Per questo, avresti bisogno di una valutazione a singolo numero. Tuttavia, per questo caso, poiché 0 <= one < 4 <= 9e 0 <= two < 4 <= 9, possiamo convertire entrambi gli in in un semplice int moltiplicando oneper 10 e aggiungendo two. Quindi utilizzare un interruttore nel numero risultante come questo:

public int fightMath(int one, int two) {
    // Convert one and two to a single variable in base 10
    int evaluate = one * 10 + two;

    switch(evaluate) {
        // I'd consider a comment in each line here and in the original code
        // for clarity
        case 0: result = 0; break;
        case 1: result = 0; break;
        case 1: result = 0; break;
        case 2: result = 1; break;
        case 3: result = 2; break;
        case 10: result = 0; break;
        case 11: result = 0; break;
        case 12: result = 2; break;
        case 13: result = 1; break;
        case 20: result = 2; break;
        case 21: result = 1; break;
        case 22: result = 3; break;
        case 23: result = 3; break;
        case 30: result = 1; break;
        case 31: result = 2; break;
        case 32: result = 3; break;
        case 33: result = 3; break;
    }

    return result;
}

C'è un altro metodo breve che voglio solo indicare come codice teorico. Tuttavia non lo userei perché ha qualche complessità in più che normalmente non vorresti affrontare. La complessità extra deriva dalla base 4 , perché il conteggio è 0, 1, 2, 3, 10, 11, 12, 13, 20, ...

public int fightMath(int one, int two) {
    // Convert one and two to a single variable in base 4
    int evaluate = one * 4 + two;

    allresults = new int[] { 0, 0, 1, 2, 0, 0, 2, 1, 2, 1, 3, 3, 1, 2, 3, 3 };

    return allresults[evaluate];
}

Davvero solo una nota aggiuntiva, nel caso mi manchi qualcosa da Java. In PHP farei:

function fightMath($one, $two) {
    // Convert one and two to a single variable in base 4
    $evaluate = $one * 10 + $two;

    $allresults = array(
         0 => 0,  1 => 0,  2 => 1,  3 => 2,
        10 => 0, 11 => 0, 12 => 2, 13 => 1,
        20 => 2, 21 => 1, 22 => 3, 23 => 3,
        30 => 1, 31 => 2, 32 => 3, 33 => 3 );

    return $allresults[$evaluate];
}

Java non ha lamdas prima dell'ottava versione
Kirill Gamazkov,

1
Questo. Per un numero così piccolo di input, utilizzerei uno switch con un valore composito (anche se potrebbe essere più leggibile con un moltiplicatore maggiore di 10, come 100 o 1000).
Medinoc,

7

Dato che preferisci i ifcondizionali nidificati , ecco un altro modo.
Si noti che non utilizza il resultmembro e non cambia alcuno stato.

public int fightMath(int one, int two) {
    if (one == 0) {
      if (two == 0) { return 0; }
      if (two == 1) { return 0; }
      if (two == 2) { return 1; }
      if (two == 3) { return 2; }
    }   
    if (one == 1) {
      if (two == 0) { return 0; }
      if (two == 1) { return 0; }
      if (two == 2) { return 2; }
      if (two == 3) { return 1; }
    }
    if (one == 2) {
      if (two == 0) { return 2; }
      if (two == 1) { return 1; }
      if (two == 2) { return 3; }
      if (two == 3) { return 3; }
    }
    if (one == 3) {
      if (two == 0) { return 1; }
      if (two == 1) { return 2; }
      if (two == 2) { return 3; }
      if (two == 3) { return 3; }
    }
    return DEFAULT_RESULT;
}

perché non hai qualcun altro?
FDinoff,

3
@FDinoff avrei potuto usare le elsecatene ma non aveva fatto alcuna differenza.
Nick Dandoulakis,

1
So che è banale, ma l'aggiunta delle altre catene non sarebbe più veloce? in 3 casi su 4? Ho sempre l'abitudine di scrivere il codice per eseguire il più velocemente possibile anche se sono solo pochi cicli.
Brandon Bearden,

2
@BrandonBearden qui non faranno alcuna differenza (supponendo che l'ingresso sia sempre nell'intervallo 0..3). Il compilatore probabilmente micro-ottimizzerà comunque il codice. Se abbiamo una lunga serie di else ifistruzioni possiamo accelerare il codice con switcho cercare tabelle.
Nick Dandoulakis,

Com'è così? Se one==0eseguirà il codice, dovrà verificare se one==1quindi se one==2e infine se one==3- Se ci fosse altro se è presente, non farebbe gli ultimi tre controlli perché uscirebbe dall'istruzione dopo la prima corrispondenza. E sì, è possibile ottimizzare ulteriormente utilizzando un'istruzione switch al posto delle if (one...istruzioni e quindi utilizzando un altro switch all'interno del caso di one's. Tuttavia, questa non è la mia domanda.
Brandon Bearden,

6

Provalo con la custodia dell'interruttore. ..

Dai un'occhiata qui o qui per maggiori informazioni a riguardo

switch (expression)
{ 
  case constant:
        statements;
        break;
  [ case constant-2:
        statements;
        break;  ] ...
  [ default:
        statements;
        break;  ] ...
}

È possibile aggiungere più condizioni (non contemporaneamente) e persino avere un'opzione predefinita in cui non sono stati soddisfatti altri casi.

PS: solo se una condizione deve essere soddisfatta ..

Se si presentano contemporaneamente 2 condizioni .. Non credo che l'interruttore possa essere utilizzato. Ma puoi ridurre il tuo codice qui.

Istruzione switch Java più casi


6

La prima cosa che mi venne in mente fu essenzialmente la stessa risposta data da Francisco Presencia, ma ottimizzata in qualche modo:

public int fightMath(int one, int two)
{
    switch (one*10 + two)
    {
    case  0:
    case  1:
    case 10:
    case 11:
        return 0;
    case  2:
    case 13:
    case 21:
    case 30:
        return 1;
    case  3:
    case 12:
    case 20:
    case 31:
        return 2;
    case 22:
    case 23:
    case 32:
    case 33:
        return 3;
    }
}

È possibile ottimizzarlo ulteriormente rendendo l'ultimo caso (per 3) il caso predefinito:

    //case 22:
    //case 23:
    //case 32:
    //case 33:
    default:
        return 3;

Il vantaggio di questo metodo è che è più facile vedere quali valori per onee twocorrispondono a quali valori restituiti rispetto ad alcuni degli altri metodi suggeriti.


Questa è una variante di un'altra mia risposta qui .
David R Tribble,

6
((two&2)*(1+((one^two)&1))+(one&2)*(2-((one^two)&1)))/2

4
Quanto tempo hai impiegato per arrivare a questo?
mbatchkarov,

2
@mbatchkarov Circa 10 minuti di lettura delle altre risposte, quindi 10 minuti di scarabocchio con carta e matita.
Dawood ibn Kareem,

7
Sarei davvero triste se dovessi mantenere questo.
Meryovi,

uhmmm ... C'è un bug: ti stai perdendo; --unicorns
Alberto

Sono d'accordo con @Meryovi, propizi per essere concisi, ma terribile come il codice APL
Ed Griebel,


3

Mentre disegno un tavolo tra uno / due e il risultato, vedo uno schema,

if(one<2 && two <2) result=0; return;

Quanto sopra ridurrebbe almeno 3 se le dichiarazioni. Non vedo un modello prestabilito né sono in grado di ricavare molto dal codice fornito, ma se tale logica può essere derivata, ridurrebbe un numero di istruzioni if.

Spero che questo ti aiuti.


3

Un buon punto sarebbe quello di definire le regole come testo, quindi puoi facilmente ottenere la formula corretta. Questo è estratto dalla bella rappresentazione in array di laalto:

{ 0, 0, 1, 2 },
{ 0, 0, 2, 1 },
{ 2, 1, 3, 3 },
{ 1, 2, 3, 3 }

E qui andiamo con alcuni commenti generali, ma dovresti descriverli in termini di regole:

if(one<2) // left half
{
    if(two<2) // upper left half
    {
        result = 0; //neither hits
    }
    else // lower left half
    {
        result = 1+(one+two)%2; //p2 hits if sum is even
    }
}
else // right half
{
    if(two<2) // upper right half
    {
        result = 1+(one+two+1)%2; //p1 hits if sum is even
    }
    else // lower right half
    {
        return 3; //both hit
    }
}

Ovviamente potresti ridurlo a meno codice, ma è generalmente una buona idea capire cosa codifichi piuttosto che trovare una soluzione compatta.

if((one<2)&&(two<2)) result = 0; //top left
else if((one>1)&&(two>1)) result = 3; //bottom right
else result = 1+(one+two+((one>1)?1:0))%2; //no idea what that means

Qualche spiegazione sui complicati successi di p1 / p2 sarebbe ottima, sembra interessante!


3

La soluzione più breve e ancora leggibile:

static public int fightMath(int one, int two)
{
    if (one < 2 && two < 2) return 0;
    if (one > 1 && two > 1) return 3;
    int n = (one + two) % 2;
    return one < two ? 1 + n : 2 - n;
}

o anche più breve:

static public int fightMath(int one, int two)
{
    if (one / 2 == two / 2) return (one / 2) * 3;
    return 1 + (one + two + one / 2) % 2;
}

Non contiene numeri "magici";) Spero che aiuti.


2
Formule come queste renderanno impossibile modificare (aggiornare) il risultato di una combinazione in un secondo momento. L'unico modo sarebbe di rielaborare l'intera formula.
SNag

2
@SNag: sono d'accordo. La soluzione più flessibile sta usando l'array 2D. Ma l'autore di questo post ha voluto una formula e questa è la migliore che puoi ottenere solo con se e matematica semplici.
PW

2

static int val(int i, int u){ int q = (i & 1) ^ (u & 1); return ((i >> 1) << (1 ^ q))|((u >> 1) << q); }


1

Personalmente mi piace mettere in cascata operatori ternari:

int result = condition1
    ? result1
    : condition2
    ? result2
    : condition3
    ? result3
    : resultElse;

Ma nel tuo caso, puoi usare:

final int[] result = new int[/*16*/] {
    0, 0, 1, 2,
    0, 0, 2, 1,
    2, 1, 3, 3,
    1, 2, 3, 3
};

public int fightMath(int one, int two) {
    return result[one*4 + two];
}

In alternativa, puoi notare uno schema in bit:

one   two   result

section 1: higher bits are equals =>
both result bits are equals to that higher bits

00    00    00
00    01    00
01    00    00
01    01    00
10    10    11
10    11    11
11    10    11
11    11    11

section 2: higher bits are different =>
lower result bit is inverse of lower bit of 'two'
higher result bit is lower bit of 'two'

00    10    01
00    11    10
01    10    10
01    11    01
10    00    10
10    01    01
11    00    01
11    01    10

Quindi puoi usare la magia:

int fightMath(int one, int two) {
    int b1 = one & 2, b2 = two & 2;
    if (b1 == b2)
        return b1 | (b1 >> 1);

    b1 = two & 1;

    return (b1 << 1) | (~b1);
}

1

Ecco una versione abbastanza concisa, simile alla risposta di JAB . Questo utilizza una mappa per memorizzare che sposta il trionfo sugli altri.

public enum Result {
  P1Win, P2Win, BothWin, NeitherWin;
}

public enum Move {
  BLOCK_HIGH, BLOCK_LOW, ATTACK_HIGH, ATTACK_LOW;

  static final Map<Move, List<Move>> beats = new EnumMap<Move, List<Move>>(
      Move.class);

  static {
    beats.put(BLOCK_HIGH, new ArrayList<Move>());
    beats.put(BLOCK_LOW, new ArrayList<Move>());
    beats.put(ATTACK_HIGH, Arrays.asList(ATTACK_LOW, BLOCK_LOW));
    beats.put(ATTACK_LOW, Arrays.asList(ATTACK_HIGH, BLOCK_HIGH));
  }

  public static Result compare(Move p1Move, Move p2Move) {
    boolean p1Wins = beats.get(p1Move).contains(p2Move);
    boolean p2Wins = beats.get(p2Move).contains(p1Move);

    if (p1Wins) {
      return (p2Wins) ? Result.BothWin : Result.P1Win;
    }
    if (p2Wins) {
      return (p1Wins) ? Result.BothWin : Result.P2Win;
    }

    return Result.NeitherWin;
  }
} 

Esempio:

System.out.println(Move.compare(Move.ATTACK_HIGH, Move.BLOCK_LOW));

stampe:

P1Win

Consiglierei static final Map<Move, List<Move>> beats = new java.util.EnumMap<>();invece, dovrebbe essere leggermente più efficiente.
JAB

@JAB Sì, buona idea. Dimentico sempre che esiste quel tipo. E ... quanto è strano costruirne uno!
Duncan Jones,

1

Userei una mappa, una HashMap o una TreeMap

Soprattutto se i parametri non sono nel modulo 0 <= X < N

Come un insieme di numeri interi casuali positivi.

Codice

public class MyMap
{
    private TreeMap<String,Integer> map;

    public MyMap ()
    {
        map = new TreeMap<String,Integer> ();
    }

    public void put (int key1, int key2, Integer value)
    {
        String key = (key1+":"+key2);

        map.put(key, new Integer(value));
    }

    public Integer get (int key1, int key2)
    {
        String key = (key1+":"+key2);

        return map.get(key);
    }
}

1

Grazie a @Joe Harper mentre ho finito per usare una variante della sua risposta. Per ridurlo ulteriormente poiché 2 risultati per 4 erano gli stessi, l'ho ridotto ulteriormente.

Potrei tornare a questo ad un certo punto, ma se non ci sono grandi resistenze causate da più ifdichiarazioni, per ora lo terrò. Esaminerò la matrice della tabella e cambierò ulteriormente le soluzioni di istruzione.

public int fightMath(int one, int two) {
  if (one === 0) {
    if (two === 2) { return 1; }
    else if(two === 3) { return 2; }
    else { return 0; }
  } else if (one === 1) {
    if (two === 2) { return 2; }
    else if (two === 3) { return 1; }
    else { return 0; }
  } else if (one === 2) {
    if (two === 0) { return 2; }
    else if (two === 1) { return 1; }
    else { return 3; }
  } else if (one === 3) {
    if (two === 0) { return 1; }
    else if (two === 1) { return 2; }
    else { return 3; }
  }
}

13
Questo è in realtà meno leggibile rispetto all'originale e non riduce il numero di dichiarazioni if ​​...
Chad

@Chad L'idea era quella di aumentare la velocità del processo e sebbene sembri orribile, è facilmente aggiornabile se dovessi aggiungere più azioni in futuro. Detto questo ora sto usando una risposta precedente che non avevo capito completamente prima.
TomFirth

3
@ TomFirth84 C'è un motivo per cui non stai seguendo le convenzioni di codifica appropriate per le tue dichiarazioni if?
ylun.ca,

@ylun: avevo ridotto le linee prima di incollarlo su SO, non per leggibilità ma per puro spazio spam. Ci sono variazioni di pratica in questa pagina e purtroppo è solo il modo in cui ho imparato e mi sento a mio agio.
TomFirth,

2
@ TomFirth84 I Non penso che questo sia facilmente aggiornato, il numero di righe cresce come il prodotto del numero di valori consentiti.
Andrew Lazarus,

0
  1. Usa costanti o enumerazioni per rendere il codice più leggibile
  2. Prova a dividere il codice in più funzioni
  3. Prova a usare la simmetria del problema

Ecco un suggerimento su come potrebbe apparire, ma usare un ints qui è ancora brutto:

static final int BLOCK_HIGH = 0;
static final int BLOCK_LOW = 1;
static final int ATTACK_HIGH = 2;
static final int ATTACK_LOW = 3;

public static int fightMath(int one, int two) {
    boolean player1Wins = handleAttack(one, two);
    boolean player2Wins = handleAttack(two, one);
    return encodeResult(player1Wins, player2Wins); 
}



private static boolean handleAttack(int one, int two) {
     return one == ATTACK_HIGH && two != BLOCK_HIGH
        || one == ATTACK_LOW && two != BLOCK_LOW
        || one == BLOCK_HIGH && two == ATTACK_HIGH
        || one == BLOCK_LOW && two == ATTACK_LOW;

}

private static int encodeResult(boolean player1Wins, boolean player2Wins) {
    return (player1Wins ? 1 : 0) + (player2Wins ? 2 : 0);
}

Sarebbe più bello usare un tipo strutturato per l'input e l'output. L'input in realtà ha due campi: la posizione e il tipo (blocco o attacco). L'output ha anche due campi: player1Wins e player2Wins. La codifica in un unico numero intero rende più difficile la lettura del codice.

class PlayerMove {
    PlayerMovePosition pos;
    PlayerMoveType type;
}

enum PlayerMovePosition {
    HIGH,LOW
}

enum PlayerMoveType {
    BLOCK,ATTACK
}

class AttackResult {
    boolean player1Wins;
    boolean player2Wins;

    public AttackResult(boolean player1Wins, boolean player2Wins) {
        this.player1Wins = player1Wins;
        this.player2Wins = player2Wins;
    }
}

AttackResult fightMath(PlayerMove a, PlayerMove b) {
    return new AttackResult(isWinningMove(a, b), isWinningMove(b, a));
}

boolean isWinningMove(PlayerMove a, PlayerMove b) {
    return a.type == PlayerMoveType.ATTACK && !successfulBlock(b, a)
            || successfulBlock(a, b);
}

boolean successfulBlock(PlayerMove a, PlayerMove b) {
    return a.type == PlayerMoveType.BLOCK 
            && b.type == PlayerMoveType.ATTACK 
            && a.pos == b.pos;
}

Sfortunatamente, Java non è molto bravo a esprimere questo tipo di tipi di dati.


-2

Invece fai qualcosa del genere

   public int fightMath(int one, int two) {
    return Calculate(one,two)

    }


    private int Calculate(int one,int two){

    if (one==0){
        if(two==0){
     //return value}
    }else if (one==1){
   // return value as per condtiion
    }

    }

4
Hai appena creato una funzione privata racchiusa da quella pubblica. Perché non implementarlo solo nella funzione pubblica?
Martin Ueding,

5
E non hai ridotto il numero di istruzioni if.
Chad
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.