Montagem x86, 9 bytes (para entrada concorrente)
Todo mundo que está tentando esse desafio em idiomas de alto nível está perdendo a verdadeira diversão de manipular bits brutos. Há tantas variações sutis nas maneiras de fazer isso, é insano - e muito divertido de se pensar. Aqui estão algumas soluções que eu criei na linguagem assembly x86 de 32 bits.
Peço desculpas antecipadamente por não ser a resposta típica do código-golfe. Vou falar muito sobre o processo de otimização iterativa (por tamanho). Espero que seja interessante e educativo para um público maior, mas se você é do tipo TL; DR, não ficarei ofendido se você pular para o final.
A solução óbvia e eficiente é testar se o valor é ímpar ou par (o que pode ser feito com eficiência observando o bit menos significativo) e depois selecionar entre n + 1 ou n-1 de acordo. Supondo que a entrada seja passada como um parâmetro no ECXregistro e o resultado seja retornado no EAXregistro, obtemos a seguinte função:
F6 C1 01 | test cl, 1 ; test last bit to see if odd or even
8D 41 01 | lea eax, DWORD PTR [ecx + 1] ; set EAX to n+1 (without clobbering flags)
8D 49 FF | lea ecx, DWORD PTR [ecx - 1] ; set ECX to n-1 (without clobbering flags)
0F 44 C1 | cmovz eax, ecx ; move in different result if input was even
C3 | ret
(13 bytes)
Mas, para fins de código-golfe, essas LEAinstruções não são boas, pois levam 3 bytes para codificar. Um simples DECarranjo de ECXseria muito menor (apenas um byte), mas isso afeta os sinalizadores, por isso temos que ser um pouco inteligentes na maneira como organizamos o código. Podemos fazer o decremento primeiro e o teste impar / par em segundo , mas depois temos que inverter o resultado do teste ímpar / par.
Além disso, podemos alterar a instrução de movimentação condicional para uma ramificação, o que pode tornar o código mais lento (dependendo da previsibilidade da ramificação - se a entrada alternar inconsistentemente entre ímpar e par, uma ramificação será mais lenta; se houver uma padrão, será mais rápido), o que nos salvará outro byte.
De fato, com esta revisão, toda a operação pode ser realizada no local, usando apenas um único registro. Isso é ótimo se você estiver incorporando esse código em algum lugar (e as chances são de que você seria, pois é muito curto).
48 | dec eax ; decrement first
A8 01 | test al, 1 ; test last bit to see if odd or even
75 02 | jnz InputWasEven ; (decrement means test result is inverted)
40 | inc eax ; undo the decrement...
40 | inc eax ; ...and add 1
InputWasEven: ; (two 1-byte INCs are shorter than one 3-byte ADD with 2)
(embutido: 7 bytes; como uma função: 10 bytes)
Mas e se você quisesse tornar isso uma função? Nenhuma convenção de chamada padrão usa o mesmo registro para passar parâmetros como para o valor de retorno; portanto, você precisa adicionar uma MOVinstrução de registro no início ou no final da função. Isso praticamente não tem custo em velocidade, mas adiciona 2 bytes. (A RETinstrução também adiciona um byte e há alguma sobrecarga introduzida pela necessidade de fazer e retornar de uma chamada de função, o que significa que este é um exemplo em que o inlining produz um benefício de velocidade e tamanho, em vez de ser apenas uma velocidade clássica - no comércio). No total, escrito como uma função, esse código aumenta para 10 bytes.
O que mais podemos fazer em 10 bytes? Se nos preocupamos com o desempenho (pelo menos, o desempenho previsível ), seria bom nos livrar desse ramo. Aqui está uma solução sem ramificação, com variação de bits do mesmo tamanho em bytes. A premissa básica é simples: usamos um XOR bit a bit para virar o último bit, convertendo um valor ímpar em um par e vice-versa. Mas há uma niggle - para entradas ímpares, que nos dá n-1 , enquanto para entradas pares, nos dá n + 1 - exatamente o oposto do que queremos. Portanto, para corrigir isso, executamos a operação com um valor negativo, lançando efetivamente o sinal.
8B C1 | mov eax, ecx ; copy parameter (ECX) to return register (EAX)
|
F7 D8 | neg eax ; two's-complement negation
83 F0 01 | xor eax, 1 ; XOR last bit to invert odd/even
F7 D8 | neg eax ; two's-complement negation
|
C3 | ret ; return from function
(embutido: 7 bytes; como uma função: 10 bytes)
Muito liso; é difícil ver como isso pode ser melhorado. Uma coisa me chama a atenção, no entanto: essas duas NEGinstruções de 2 bytes . Francamente, dois bytes parecem um byte a mais para codificar uma simples negação, mas é com esse conjunto de instruções que temos que trabalhar. Existem soluções alternativas? Certo! Se XORpor -2, podemos substituir a segunda NEGação por um INCrement:
8B C1 | mov eax, ecx
|
F7 D8 | neg eax
83 F0 FE | xor eax, -2
40 | inc eax
|
C3 | ret
(embutido: 6 bytes; em função de 9 bytes)
Outra das peculiaridades do conjunto de instruções x86 é a LEAinstrução multiuso , que pode executar um movimento de registro e registro, uma adição de registro e registro, compensando uma constante e dimensionando tudo em uma única instrução!
8B C1 | mov eax, ecx
83 E0 01 | and eax, 1 ; set EAX to 1 if even, or 0 if odd
8D 44 41 FF | lea eax, DWORD PTR [ecx + eax*2 - 1]
C3 | ret
(10 bytes)
A ANDinstrução é como a TESTinstrução que usamos anteriormente, em que ambos fazem AND e bit a bit de acordo, mas ANDna verdade atualizam o operando de destino. A LEAinstrução então dimensiona isso em 2, adiciona o valor original de entrada e diminui em 1. Se o valor de entrada for ímpar, isso subtrai 1 (2 × 0 - 1 = -1) dele; se o valor de entrada for par, isso adiciona 1 (2 × 1 - 1 = 1) a ele.
Essa é uma maneira muito rápida e eficiente de escrever o código, já que grande parte da execução pode ser feita no front-end, mas não nos compra muito na forma de bytes, pois são necessárias muitas para codificar um complexo LEAinstrução. Esta versão também não funciona tão bem para fins de inclusão, pois requer que o valor da entrada original seja preservado como uma entrada da LEAinstrução. Portanto, com essa última tentativa de otimização, voltamos ao passado, sugerindo que talvez seja hora de parar.
Portanto, para a entrada final competitiva, temos uma função de 9 bytes que pega o valor de entrada no ECXregistro (uma convenção de chamada semi-padrão baseada em registro em x86 de 32 bits) e retorna o resultado no EAXregistro (como em todas as convenções de chamada x86):
SwapParity PROC
8B C1 mov eax, ecx
F7 D8 neg eax
83 F0 FE xor eax, -2
40 inc eax
C3 ret
SwapParity ENDP
Pronto para montar com MASM; chamada de C como:
extern int __fastcall SwapParity(int value); // MSVC
extern int __attribute__((fastcall)) SwapParity(int value); // GNU