¿Demasiadas declaraciones 'si'?

Resuelto TomFirth asked hace 10 años • 26 respuestas

El siguiente código funciona como lo necesito, pero es feo, excesivo o varias otras cosas. Miré fórmulas e intenté escribir algunas soluciones, pero termino con una cantidad similar de declaraciones.

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

Para explicar el código, es para una especie de juego por 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 Asigné algo si esto ayuda. El resultado es, 0 = ninguno 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;
}
TomFirth avatar Mar 19 '14 16:03 TomFirth
Aceptado

Si no puede encontrar una fórmula, puede utilizar 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];
laalto avatar Mar 19 '2014 09:03 laalto

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 bit a bit:

Esto aprovecha el hecho de que todo es 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 exige 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 varias de las respuestas una reformulación del problema como un mapa de 1 número de base 4 de 2 dígitos 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 de base 4 de 1 dígito {0,1,2,3} - 4 valores diferentes, también importantes.

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

La constante M es una codificación del mapa m donde m(0) está codificado en bits M[0:1], m(1) está codificado en bits M[2:3] y m(n) está codificado 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 hacia 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 multiplican cosas teniendo en cuenta que 2*x = x << 1 y 8*x = x << 3.

waTeim avatar Mar 19 '2014 15:03 waTeim

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

Así es como escribiría este código: solo conozco C#, no Java, pero ya te haces una idea:

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én es afectado por qué ataque y devolviendo ambos resultados.

Sin embargo, esto podría ser aún mejor; esa matriz booleana es algo opaca. Me gusta el enfoque de búsqueda en tablas, pero me inclinaría a escribirlo de tal manera que dejara claro cuál era la semántica prevista para el juego. Es decir, en lugar de "un ataque de cero y una defensa de uno no da como resultado ningún golpe", busque una manera de hacer que el código implique más claramente "un ataque de patada baja y una defensa de bloqueo bajo no da como resultado ningún golpe". Haga que el código refleje la lógica empresarial del juego.

Eric Lippert avatar Mar 19 '2014 15:03 Eric Lippert