if statement - varias - ¿Demasiadas declaraciones 'si'?




sentencia if else en c++ (18)

  1. Use constantes o enumeraciones para hacer que el código sea más legible
  2. Intenta dividir el código en más funciones.
  3. Intenta utilizar la simetría del problema.

Aquí hay una sugerencia de cómo podría verse esto, pero usar un ints aquí todavía es un poco feo:

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

Sería mejor utilizar un tipo estructurado para la entrada y la salida. La entrada en realidad tiene dos campos: la posición y el tipo (bloque o ataque). La salida también tiene dos campos: player1Wins y player2Wins. Codificar esto en un solo entero hace que sea más difícil leer el código.

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

Desafortunadamente, Java no es muy bueno para expresar ese tipo de tipos de datos.

El siguiente código funciona como lo necesito, pero es feo, excesivo o una serie de otras cosas. He mirado fórmulas y he intentado escribir algunas soluciones, pero acabo con una cantidad similar de declaraciones.

¿Hay algún tipo de fórmula matemática que me beneficiaría en este caso o son 16 si las declaraciones son aceptables?

Para explicar el código, es para un tipo de juego basado en turnos simultáneos ... dos jugadores tienen cuatro botones de acción cada uno y los resultados provienen de una matriz (0-3), pero las variables 'uno' y 'dos' pueden ser Asignado algo si esto ayuda. El resultado es, 0 = ninguno de los dos gana, 1 = p1 gana, 2 = p2 gana, 3 = ambos ganan.

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

¿Por qué no usar una matriz?

Empezaré desde el principio. Veo un patrón, los valores van de 0 a 3 y desea capturar todos los valores posibles. Esta es su mesa:

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

Cuando miramos este mismo binario de tabla vemos los siguientes resultados:

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

Ahora tal vez ya vea algún patrón, pero cuando combino el valor uno y dos veo que está utilizando todos los valores 0000, 0001, 0010, ... 1110 y 1111. Ahora combinemos el valor uno y dos para hacer una sola 4 bits entero.

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

Cuando volvemos a traducir esto a valores decimales, vemos una gran variedad de valores en los que uno y dos combinados podrían usarse como índice:

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

La matriz es entonces {0, 0, 1, 2, 0, 0, 2, 1, 2, 1, 3, 3, 2, 1, 3, 3} , donde su índice es simplemente uno y dos combinados.

No soy un programador de Java, pero puedes deshacerte de todas las declaraciones if y simplemente escribirlo como algo así:

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

No sé si un desplazamiento de bits por 2 es más rápido que la multiplicación. Pero podría valer la pena intentarlo.


Dado que su conjunto de datos es tan pequeño, puede comprimir todo en 1 entero largo y convertirlo en una fórmula

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

Más variante bitwise:

Esto hace uso del hecho de que todo es un múltiplo de 2.

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

El origen de la constante mágica

¿Qué puedo decir? El mundo necesita magia, a veces la posibilidad de algo requiere su creación.

La esencia de la función que resuelve el problema de OP es un mapa de 2 números (uno, dos), dominio {0,1,2,3} al rango {0,1,2,3}. Cada una de las respuestas ha abordado cómo implementar ese mapa.

Además, puede ver en una cantidad de las respuestas una reafirmación del problema como un mapa de 1 número de base de 2 dígitos 4 número N (uno, dos) donde uno es el dígito 1, dos es el dígito 2 y N = 4 * uno + dos; N = {0,1,2, ..., 15} - dieciséis valores diferentes, eso es importante. La salida de la función es un número 4 de base de 1 dígito {0,1,2,3} - 4 valores diferentes, también importantes.

Ahora, un número base de 1 dígito 4 se puede expresar como un número base de 2 dígitos 2; {0,1,2,3} = {00,01,10,11}, por lo que cada salida puede codificarse con solo 2 bits. Desde arriba, solo son posibles 16 salidas diferentes, por lo que 16 * 2 = 32 bits es todo lo que se necesita para codificar todo el mapa; todo esto puede caber en 1 entero.

La constante M es una codificación del mapa m donde m (0) está codificada en los bits M [0: 1], m (1) está codificada en los bits M [2: 3], y m (n) está codificada en bits M [n * 2: n * 2 + 1].

Todo lo que queda es indexar y devolver la parte derecha de la constante, en este caso, puede desplazar M a la derecha 2 * N veces y tomar los 2 bits menos significativos, es decir (M >> 2 * N) y 0x3. Las expresiones (uno << 3) y (dos << 1) simplemente están multiplicando las cosas al mismo tiempo que se observa que 2 * x = x << 1 y 8 * x = x << 3.


Espero entender la lógica correctamente. ¿Qué tal algo como:

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

El control de un golpe alto o bajo no está bloqueado y lo mismo para el jugador dos.

Edición: el algoritmo no se entendió completamente, se otorgó "golpe" cuando se bloqueó, lo cual no me di cuenta (Thx 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;
}

No me gustan ninguna de las soluciones presentadas, excepto las de JAB. Ninguno de los otros facilita la lectura del código y comprende lo que se está calculando .

Así es como escribiría este código: solo conozco C #, no Java, pero obtienes la imagen:

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

Ahora está mucho más claro lo que se está calculando aquí: esto enfatiza que estamos calculando quiénes son los atacados y qué resultados se obtienen.

Sin embargo, esto podría ser aún mejor; Esa matriz booleana es algo opaca. Me gusta el enfoque de búsqueda en la mesa, pero me inclino a escribirlo de tal manera que quede claro cuáles eran las semánticas de juego previstas. Es decir, en lugar de "un ataque de cero y una defensa de uno resulta sin impacto", en lugar de encontrar una manera de hacer que el código implique más claramente "un ataque de patada baja y un resultado de defensa de bloqueo bajo no tiene impacto". Hacer que el código refleje la lógica de negocios del juego.


No tengo experiencia con Java, por lo que puede haber algunos errores tipográficos. Por favor considere el código como pseudo-código.

Yo iría con un simple interruptor. Para eso, necesitarías una evaluación de un solo número. Sin embargo, para este caso, dado que 0 <= one < 4 <= 9 y 0 <= two < 4 <= 9 , podemos convertir ambos ints a un simple int multiplicando one por 10 y sumando two . Luego usa un interruptor en el número resultante como este:

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

Hay otro método corto que solo quiero señalar como un código teórico. Sin embargo, no lo usaría porque tiene una complejidad adicional con la que normalmente no quieres lidiar. La complejidad adicional proviene de la base 4 , porque el conteo es 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];
}

Realmente solo una nota adicional, en caso de que me esté perdiendo algo de Java. En PHP yo haría:

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

Para ser honesto, cada uno tiene su propio estilo de código. No hubiera pensado que el rendimiento se vería afectado demasiado. Si entiende esto mejor que usar una versión de caja de conmutador, continúe usándolo.

Puede anidar los ifs, por lo que potencialmente podría haber un ligero aumento en el rendimiento de sus últimos if, ya que no habría pasado por tantas declaraciones if. Pero en su contexto de un curso básico de java probablemente no se beneficiará.

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

Entonces, en lugar de ...

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

Tu harias

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

Y simplemente reformatéalo como prefieras.

Esto no hace que el código se vea mejor, pero creo que potencialmente lo acelera un poco.


Pruébalo con la caja del interruptor. ..

Echa un vistazo here o here para más información al respecto.

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

Puede agregarle múltiples condiciones (no simultáneamente) e incluso tener una opción predeterminada donde no se hayan satisfecho otros casos.

PS: Solo si una condición debe ser satisfecha ..

Si surgen 2 condiciones simultáneamente ... No creo que se pueda usar el interruptor. Pero puedes reducir tu código aquí.

Java cambiar la declaración de casos múltiples


Si no puede encontrar una fórmula, puede usar una tabla para un número tan limitado de resultados:

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];

Veamos lo que sabemos.

1: sus respuestas son simétricas para P1 (jugador uno) y P2 (jugador dos). Esto tiene sentido para un juego de lucha, pero también es algo que puedes aprovechar para mejorar tu lógica.

2: 3 tiempos 0 tiempos 2 tiempos 1 tiempos 3. Los únicos casos no cubiertos por estos casos son combinaciones de 0 vs 1 y 2 vs 3. Para decirlo de otra manera, la única tabla de victorias se ve así: 0 tiempos 2, 1 tiempos 3, 2 tiempos 1, 3 tiempos 0.

3: Si 0/1 se enfrenta uno contra el otro, entonces hay un empate sin hits, pero si 2/3 se enfrentan entre sí, ambos golpean

Primero, construyamos una función unidireccional que nos diga si ganamos:

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

Podemos usar esta función para componer el resultado final:

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

Si bien esto es posiblemente más complejo y probablemente más lento que la búsqueda en la tabla en muchas respuestas, creo que es un método superior porque en realidad encapsula la lógica de su código y lo describe a cualquiera que esté leyendo su código. Creo que esto hace que sea una mejor implementación.

(Ha pasado un tiempo desde que hice Java, así que pido disculpas si la sintaxis está desactivada, espero que todavía sea inteligible si la tengo un poco mal)

Por cierto, 0-3 claramente significa algo; No son valores arbitrarios por lo que ayudaría a nombrarlos.


La solución más corta y aún legible:

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 incluso más corto:

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

No contiene ningún número "mágico";) Espero que ayude.


Al dibujar una tabla entre uno / dos y el resultado, veo un patrón,

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

Lo anterior reduciría al menos 3 si las declaraciones. No veo un patrón establecido ni puedo extraer mucho del código dado, pero si se puede derivar tal lógica, se reduciría una serie de declaraciones if.

Espero que esto ayude.


Lo primero que se me ocurrió fue esencialmente la misma respuesta dada por Francisco Presencia, pero algo optimizada:

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

Puede optimizarlo aún más haciendo que el último caso (para 3) sea el caso predeterminado:

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

La ventaja de este método es que es más fácil ver para qué valores oney twoqué valores devuelven que otros métodos sugeridos.


Personalmente me gusta conectar en cascada los operadores ternarios:

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

Pero en tu caso, puedes usar:

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

O bien, puede notar un patrón en bits:

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

Así que puedes usar 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);
}

Aquí hay una versión bastante concisa, similar a la respuesta de JAB . Esto utiliza un mapa para almacenar que mueve el triunfo sobre otros.

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

Ejemplo:

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

Huellas dactilares:

P1Win

Gracias a @Joe Harper ya que terminé usando una variación de su respuesta. Para adelgazar aún más, ya que 2 resultados por 4 eran los mismos, lo adelgacé aún más.

Es posible que vuelva a este punto en algún momento, pero si no hay una resistencia importante causada por múltiples ifdeclaraciones, lo guardaré por ahora. Voy a mirar en la matriz de la tabla y cambiar las soluciones de declaración más.

public int fightMath(int one, int two) {

    if       (one == 0) {

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

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

    } else if(one == 2) {

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

    } else if(one == 3) {

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

    return result;
}

Un buen punto sería definir las reglas como texto, entonces puede derivar más fácilmente la fórmula correcta. Esto se extrae de la representación de matriz agradable de laalto:

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

Y aquí vamos con algunos comentarios generales, pero debes describirlos en términos de reglas:

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

Por supuesto, puede reducir esto a menos código, pero generalmente es una buena idea entender lo que usted codifica en lugar de encontrar una solución compacta.

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

¡Alguna explicación sobre los complicados hits de p1 / p2 sería genial, parece interesante!


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




formula