O código a seguir funciona como eu preciso, mas é feio, excessivo ou várias outras coisas. Examinei as fórmulas e tentei escrever algumas soluções, mas acabo com uma quantidade semelhante de instruções.
Existe um tipo de fórmula matemática que me beneficiaria nesse caso ou tem 16 se as instruções forem aceitáveis?
Para explicar o código, é para um tipo de jogo baseado em turnos simultâneos. Dois jogadores têm quatro botões de ação cada e os resultados vêm de uma matriz (0-3), mas as variáveis 'um' e 'dois' podem ser atribuído qualquer coisa, se isso ajudar. O resultado é: 0 = nenhuma vitória, 1 = p1 vence, 2 = p2 vence, 3 = ambos vencem.
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;
}
if-statement
math
formula
TomFirth
fonte
fonte
f(a, b)
que produz a resposta no caso geral? Você não explicou a lógica do cálculo, portanto, todas as respostas são apenas batom em um porco. Eu começaria repensando seriamente a lógica do seu programa, o uso deint
sinalizadores para ações está muito desatualizado.enum
s podem conter lógica e são descritivos, isso permitiria escrever seu código de uma maneira mais moderna.Respostas:
Se você não conseguir criar uma fórmula, poderá usar uma tabela para um número tão limitado de resultados:
fonte
IndexOutOfBoundsException
assim mesmo se um ou mais índices estiverem fora dos limites.i
/j
/k
para variáveis de loop), constantes nomeadas, organizando meu código de forma legível etc., mas quando os nomes de variáveis e funções começam a ocupar mais de 20 caracteres cada um acho que realmente leva a um código menos legível. Minha abordagem usual é tentar um código compreensível, mas sucinto, com comentários aqui e ali, para explicar por que o código está estruturado da maneira que é (versus como). Colocar o porquê nos nomes apenas confunde tudo.Como seu conjunto de dados é muito pequeno, você pode compactar tudo em um inteiro longo e transformá-lo em uma fórmula
Variante mais bit a bit:
Isso faz uso do fato de que tudo é um múltiplo de 2
A origem da constante mágica
O que posso dizer? O mundo precisa de mágica, às vezes a possibilidade de algo exige sua criação.
A essência da função que resolve o problema do OP é um mapa de 2 números (um, dois), domínio {0,1,2,3} até o intervalo {0,1,2,3}. Cada uma das respostas abordou como implementar esse mapa.
Além disso, você pode ver em várias respostas uma reafirmação do problema como um mapa de 1 número base de 2 dígitos 4 número N (um, dois) em que um é o dígito 1, dois é o dígito 2 e N = 4 * um + dois; N = {0,1,2, ..., 15} - dezesseis valores diferentes, isso é importante. A saída da função é um número base de 1 dígito 4 {0,1,2,3} - 4 valores diferentes, também importantes.
Agora, um número base 4 de 1 dígito pode ser expresso como um número base 2 de 2 dígitos; {0,1,2,3} = {00,01,10,11} e, portanto, cada saída pode ser codificada com apenas 2 bits. Acima, existem apenas 16 saídas diferentes possíveis; portanto, 16 * 2 = 32 bits é tudo o que é necessário para codificar o mapa inteiro; tudo isso pode caber em 1 inteiro.
A constante M é uma codificação do mapa m onde m (0) é codificado nos bits M [0: 1], m (1) é codificado nos bits M [2: 3] e m (n) é codificado em bits M [n * 2: n * 2 + 1].
Tudo o que resta é indexar e retornar a parte correta da constante; nesse caso, você pode deslocar M para a direita 2 * N vezes e pegar os 2 bits menos significativos, ou seja (M >> 2 * N) e 0x3. As expressões (um << 3) e (dois << 1) estão apenas multiplicando as coisas, observando que 2 * x = x << 1 e 8 * x = x << 3.
fonte
Não gosto de nenhuma das soluções apresentadas, exceto as da JAB. Nenhum dos outros facilita a leitura do código e o entendimento do que está sendo calculado .
Aqui está como eu escreveria esse código - só sei C #, não Java, mas você entendeu:
Agora é muito mais claro o que está sendo computado aqui: isso enfatiza que estamos computando quem é atingido por qual ataque e retornando os dois resultados.
No entanto, isso poderia ser ainda melhor; essa matriz booleana é um pouco opaca. Eu gosto da abordagem de pesquisa de tabela, mas gostaria de escrevê-la de maneira a deixar claro qual era a semântica de jogo pretendida. Ou seja, em vez de "um ataque de zero e uma defesa de um resultar em nenhum acerto", em vez disso, encontre uma maneira de tornar o código mais claro implica "um ataque de chute baixo e uma defesa de bloco baixa resulta em nenhum acerto". Faça o código refletir a lógica comercial do jogo.
fonte
Você pode criar uma matriz que contenha resultados
Quando você deseja obter valor, você usará
fonte
Outras pessoas já sugeriram minha ideia inicial, o método da matriz, mas, além de consolidar as instruções if, você pode evitar parte do que possui, certificando-se de que os argumentos fornecidos estejam no intervalo esperado e usando retornos no local (algumas codificações padrões que vi impor um ponto de saída para funções, mas descobri que vários retornos são muito úteis para evitar a codificação de seta e com a prevalência de exceções em Java, não há muito sentido em impor estritamente essa regra de qualquer maneira como qualquer exceção não capturada lançada dentro do método é um possível ponto de saída). Aninhar instruções de chave é uma possibilidade, mas, para o pequeno intervalo de valores que você está verificando aqui, acho que as instruções são mais compactas e provavelmente não resultam em muita diferença de desempenho,
Isso acaba sendo menos legível do que poderia ser devido à irregularidade de partes do mapeamento de entrada-> resultado. Eu prefiro o estilo da matriz devido à sua simplicidade e como você pode configurá-la para fazer sentido visualmente (embora isso seja em parte influenciado pelas minhas memórias dos mapas de Karnaugh):
Atualização: dada a sua menção de bloqueio / batida, aqui está uma mudança mais radical na função que utiliza tipos enumerados apropriados / contendo atributos para entradas e o resultado e também modifica um pouco o resultado para levar em conta o bloqueio, o que deve resultar em mais função legível.
Você nem precisa alterar a função se quiser adicionar bloqueios / ataques de mais alturas, apenas as enumerações; adicionar tipos adicionais de movimentos provavelmente exigirá modificação da função. Além disso,
EnumSet
s pode ser mais extensível do que usar enumerações extras como propriedades da enumeração principal, por exemplo,EnumSet<Move> attacks = EnumSet.of(Move.ATTACK_HIGH, Move.ATTACK_LOW, ...);
e então emattacks.contains(move)
vez demove.type == MoveType.ATTACK
, embora usarEnumSet
s provavelmente seja um pouco mais lento do que as verificações diretas de igual.No caso de um bloco bem-sucedido resultar em um contador, você pode substituir
if (one.height == two.height) return LandedHit.NEITHER;
porAlém disso, a substituição de algumas
if
instruções pelo uso do operador ternário (boolean_expression ? result_if_true : result_if_false
) poderia tornar o código mais compacto (por exemplo, o código no bloco anterior se tornariareturn one.isAttack() ? LandedHit.PLAYER_TWO : LandedHit.PLAYER_ONE;
), mas isso pode levar a oneliners mais difíceis de ler, então eu não faria ' não recomendo para ramificações mais complexas.fonte
one
etwo
ser reutilizado como pontos de partida na minha planilha. Embora não exija muito código adicional para permitir isso.EnumMap
classe que você pode usar para mapear os enums para seus deslocamentos inteiros (você também pode usar os valores ordinais dos membros enum diretamente, por exemplo,Move.ATTACK_HIGH.ordinal()
seria0
,Move.ATTACK_LOW.ordinal()
seria1
, etc., mas isso é mais frágil / menos flexível do que explicitamente associando cada membro com um valor como adicionar valores enum entre os existentes jogaria fora a contagem, o que não seria o caso com umEnumMap
).attack(against)
método aoMove
enum, retornando HIT quando o movimento é um ataque bem-sucedido, BACKFIRE quando o movimento é um ataque bloqueado e NADA quando não é um ataque. Desta forma, você pode implementá-lo em geral (public boolean attack(Move other) { if this.isAttack() return (other.isAttack() || other.height != this.height) ? HIT : BACKFIRE; return NOTHING; }
), e substituí-lo em movimentos específicos quando necessário (movimentos fracos que qualquer bloco podem bloquear, ataques que nunca mais sair pela culatra etc.)Por que não usar uma matriz?
Eu vou começar do começo. Eu vejo um padrão, os valores vão de 0 a 3 e você deseja capturar todos os valores possíveis. Esta é a sua mesa:
quando olhamos para a mesma tabela binária, vemos os seguintes resultados:
Agora talvez você já veja algum padrão, mas quando eu combino os valores um e dois, vejo que você está usando todos os valores 0000, 0001, 0010, ..... 1110 e 1111. Agora vamos combinar os valores um e dois para criar um único Número inteiro de 4 bits.
Quando convertemos isso de volta em valores decimais, vemos uma matriz muito possível de valores em que um e dois combinados podem ser usados como índice:
A matriz é então
{0, 0, 1, 2, 0, 0, 2, 1, 2, 1, 3, 3, 2, 1, 3, 3}
, onde seu índice é simplesmente um e dois combinados.Eu não sou um programador Java, mas você pode se livrar de todas as instruções if e anotá-las da seguinte forma:
Não sei se um deslocamento de bits por 2 é mais rápido que a multiplicação. Mas pode valer a pena tentar.
fonte
Isso usa um pouco de bitmagic (você já está fazendo isso mantendo dois bits de informação (baixo / alto e ataque / bloco) em um único número inteiro):
Eu não o executei, apenas digitei aqui, por favor, verifique novamente.A ideia certamente funciona. EDIT: Agora é testado para cada entrada, funciona bem.Ou devo sugerir separar os dois bits de informação em variáveis separadas? Código baseado principalmente em operações de bits como esta acima é geralmente muito difícil de manter.
fonte
return ((one ^ two) & 2) == 0 ? (one & 2) / 2 * 3 : ((one & 2) / 2 ^ ((one ^ two) & 1)) + 1;
Para ser sincero, todo mundo tem seu próprio estilo de código. Eu não teria pensado que o desempenho seria afetado demais. Se você entende isso melhor do que usar uma versão de gabinete, continue usando isso.
Você pode aninhar os ifs, portanto, potencialmente, haveria um ligeiro aumento de desempenho para as suas últimas verificações se, pois não teriam passado por tantas declarações if. Mas no seu contexto de um curso básico de java, provavelmente não será beneficiado.
Então, ao invés de ...
Você faria ...
E apenas reformate-o como preferir.
Isso não faz o código parecer melhor, mas potencialmente o acelera um pouco, acredito.
fonte
Vamos ver o que sabemos
1: suas respostas são simétricas para P1 (jogador um) e P2 (jogador dois). Isso faz sentido para um jogo de luta, mas também é algo que você pode aproveitar para melhorar sua lógica.
2: 3 batidas 0 bate 2 batidas 1 batida 3. Os únicos casos não cobertos por esses casos são combinações de 0 vs 1 e 2 vs 3. Para colocar de outra maneira, a tabela de vitórias única é assim: 0 batida 2, 1 batida 3, 2 tempos 1, 3 tempos 0.
3: Se 0/1 subir um contra o outro, haverá um empate sem sucesso, mas se 2/3 subirem um contra o outro, ambos baterão
Primeiro, vamos criar uma função unidirecional nos dizendo se vencemos:
Podemos então usar esta função para compor o resultado final:
Embora isso seja indiscutivelmente mais complexo e provavelmente mais lento do que a pesquisa de tabela oferecida em muitas respostas, acredito que seja um método superior, porque na verdade encapsula a lógica do seu código e a descreve para qualquer pessoa que esteja lendo o código. Eu acho que isso torna uma implementação melhor.
(Já faz um tempo desde que eu fiz algum Java para pedir desculpas se a sintaxe estiver desativada, espero que ainda seja inteligível se eu entendi um pouco errado)
A propósito, 0-3 claramente significa alguma coisa; eles não são valores arbitrários, portanto, seria útil nomeá-los.
fonte
Espero entender a lógica corretamente. Que tal algo como:
Verificar um golpe alto ou um golpe baixo não é bloqueado e o mesmo para o jogador dois.
Edit: Algoritmo não foi totalmente compreendido, "hit" concedido ao bloquear o que eu não percebi (Thx elias):
fonte
Como não tenho experiência com Java, pode haver erros de digitação. Por favor, considere o código como pseudo-código.
Eu iria com um simples interruptor. Para isso, você precisaria de uma avaliação de número único. No entanto, para este caso, uma vez que
0 <= one < 4 <= 9
e0 <= two < 4 <= 9
, podemos converter ambas as entradas em um int simples, multiplicandoone
por 10 e adicionandotwo
. Em seguida, use uma opção no número resultante como este:Há outro método curto que eu apenas quero apontar como um código teórico. No entanto, eu não o usaria porque tem uma complexidade extra com a qual você normalmente não deseja lidar. A complexidade extra vem da base 4 , porque a contagem é 0, 1, 2, 3, 10, 11, 12, 13, 20, ...
Realmente apenas uma observação adicional, caso eu esteja perdendo algo do Java. No PHP eu faria:
fonte
Como você prefere
if
condicionais aninhados , aqui está outra maneira.Observe que ele não usa o
result
membro e não altera nenhum estado.fonte
else
correntes, mas não fez diferença.else if
instruções, podemos acelerar o códigoswitch
ou consultar tabelas.one==0
ele executar o código, será necessário verificarone==1
seone==2
e finalmente, seone==3
- Se houvesse outro if, não faria as últimas três verificações, porque sairia da instrução após a primeira partida. E sim, você poderia otimizar ainda mais usando uma instrução switch no lugar dasif (one...
instruções e, em seguida, usando outra opção dentro do caso daone's
. No entanto, essa não é minha pergunta.Experimente com a caixa do interruptor...
Dê uma olhada aqui ou aqui para obter mais informações sobre ele
Você pode adicionar várias condições (não simultaneamente) a ele e até ter uma opção padrão onde nenhum outro caso foi satisfeito.
PS: Somente se uma condição for atendida.
Se duas condições surgirem simultaneamente ... não acho que o switch possa ser usado. Mas você pode reduzir seu código aqui.
Instrução do switch Java vários casos
fonte
A primeira coisa que me ocorreu foi essencialmente a mesma resposta dada por Francisco Presencia, mas um pouco otimizada:
Você pode otimizá-lo ainda mais, tornando o último caso (para 3) o caso padrão:
A vantagem desse método é que é mais fácil ver quais valores
one
etwo
correspondem a quais valores retornados do que alguns dos outros métodos sugeridos.fonte
fonte
; --unicorns
Você pode usar um caso de interruptor de seleção em vez de várias
if
Também para mencionar que, como você tem duas variáveis, é necessário mesclar as duas variáveis para usá-las no switch
Verifique esta instrução do switch Java para lidar com duas variáveis?
fonte
Ao desenhar uma tabela entre um / dois e o resultado, vejo um padrão,
O exposto acima reduziria pelo menos 3 declarações se. Não vejo um padrão definido nem sou capaz de captar muito do código fornecido - mas se essa lógica puder ser derivada, isso reduziria várias instruções if.
Espero que isto ajude.
fonte
Um bom ponto seria definir as regras como texto, então você pode derivar mais facilmente a fórmula correta. Isso é extraído da bela representação de matriz do laalto:
E aqui vamos nós com alguns comentários gerais, mas você deve descrevê-los em termos de regra:
É claro que você pode reduzir isso para menos código, mas geralmente é uma boa idéia entender o que você codifica, em vez de encontrar uma solução compacta.
Alguma explicação sobre os complicados acessos p1 / p2 seria ótima, parece interessante!
fonte
A solução mais curta e ainda legível:
ou ainda mais curto:
Não contém números "mágicos";) Espero que ajude.
fonte
static int val(int i, int u){ int q = (i & 1) ^ (u & 1); return ((i >> 1) << (1 ^ q))|((u >> 1) << q); }
fonte
Pessoalmente, gosto de colocar em cascata operadores ternários:
Mas no seu caso, você pode usar:
Ou você pode notar um padrão em bits:
Então você pode usar magia:
fonte
Aqui está uma versão bastante concisa, semelhante à resposta do JAB . Isso utiliza um mapa para armazenar que move triunfo sobre os outros.
Exemplo:
Impressões:
fonte
static final Map<Move, List<Move>> beats = new java.util.EnumMap<>();
, em vez disso, deve ser um pouco mais eficiente.Eu usaria um mapa, um HashMap ou um TreeMap
Especialmente se os parâmetros não estiverem no formulário
0 <= X < N
Como um conjunto de números inteiros positivos aleatórios ..
Código
fonte
Graças a @Joe Harper, como acabei usando uma variação de sua resposta. Para reduzi-lo ainda mais, pois 2 resultados por 4 eram os mesmos, diminuí-lo ainda mais.
Eu posso voltar a isso em algum momento, mas se não houver grande resistência causada por várias
if
declarações, vou manter isso por enquanto. Examinarei a matriz da tabela e alternarei ainda mais as soluções de instrução.fonte
Aqui está uma sugestão de como isso pode parecer, mas o uso de um ints aqui ainda é meio feio:
Seria melhor usar um tipo estruturado para a entrada e a saída. A entrada realmente tem dois campos: a posição e o tipo (bloqueio ou ataque). A saída também possui dois campos: player1Wins e player2Wins. Codificar isso em um único inteiro torna mais difícil a leitura do código.
Infelizmente, Java não é muito bom para expressar esses tipos de tipos de dados.
fonte
Em vez disso, faça algo parecido com isto
fonte