Agradecemos a FryAmTheEggman por alguma inspiração necessária para a solução XOR.
0000 !@
0001 ?.|@!
0010 #?#!)@
0011 ?!@
0100 +?|@!?
0101 ??!@
0110 ?<@!!<_\~(
0111 ?<<@!
1000 )\!#?@{
1001 (~?/@#!
1010 ??|@!)
1011 \#??!1@
1100 ?(~!@
1101 ?.|@!)
1110 ?$@#)!<
1111 1!@
Todos os programas usam 0
para false e 1
true.
Experimente online! Este não é um conjunto de testes, você deverá copiar os diferentes programas e entradas.
A solução acima está dentro de 2 bytes de otimização (a menos que relaxemos a interpretação da verdade / falsidade, eu acho). Eu deixei uma pesquisa de força bruta correr para perto de dois dias sobre todos os programas que se encaixam no lado de comprimento 2, ou seja, até 7 bytes (não completamente todos os programas - Eu fiz algumas suposições sobre o que todas as necessidades programa válido eo que não programa válido poderia ter). A pesquisa encontrou soluções para 15 dos 16 portões possíveis - e muitas vezes muito mais do que apenas um. Você pode encontrar uma lista de todas as soluções alternativas nesta pasta onde também as agrupei por comportamento equivalente. As que estou mostrando acima selecionei porque são a solução mais simples ou a mais interessante, e adicionarei explicações para elas amanhã.
Quanto ao 16º portão: XOR é o único portão que aparentemente não pode ser implementado em 7 bytes. Infelizmente, uma pesquisa de força bruta em programas maiores não é viável com o código que tenho atualmente. Então o XOR teve que ser escrito à mão. O mais curto que encontrei até agora é o programa de 10 bytes acima, que se baseia em uma tentativa falha (mas muito próxima) do FryAmTheEggman. É possível que exista uma solução de 8 ou 9 bytes, mas fora isso, todas as soluções devem ser ótimas.
Explicações
Aviso: parede de texto. Se alguém estiver interessado em saber como esses programas Hexagony altamente compactados realmente funcionam, incluí explicações abaixo para cada um deles. Tentei escolher a solução mais simples para cada porta nos casos em que existe mais de um programa ideal, a fim de manter as explicações razoavelmente curtas. No entanto, alguns deles ainda confundem a mente, então pensei que eles merecem um pouco mais de elaboração.
0000
: False
Não acho que precisaremos de um diagrama para este:
! @
. . .
. .
Como toda a grade de memória é inicializada em zeros, !
simplesmente imprime um zero e @
finaliza o programa.
Essa também é a única solução de 2 bytes.
0001
: E
? .
| @ !
. .
Isso basicamente implementa curto-circuito . O diagrama cinza abaixo mostra o início do programa, onde a primeira entrada é lida ?
e o ponteiro de instrução (IP) envolve o canto esquerdo, onde o |
espelho o reflete. Agora, o canto atua como condicional; portanto, existem dois caminhos de execução diferentes, dependendo do valor da primeira entrada. O diagrama vermelho mostra o fluxo de controle para A = 0
e o diagrama verde para A = 1
:
Como você pode ver, quando o A
é 0
, simplesmente o imprimimos e terminamos (lembre-se de que todos .
são não-ops). Mas quando A
é 1
, o IP percorre a primeira linha novamente, lendo B
e imprimindo isso.
No total, existem dezesseis soluções de 5 bytes para esse gate. Quatorze deles são essencialmente os mesmos que os anteriores, usando em >
vez de |
ou substituindo o .
comando por um comando efetivamente não operacional ou colocando ?
na segunda posição:
?.|@! .?|@! ?=|@! =?|@! ?_|@! _?|@! ?0|@!
?.>@! .?>@! ?=>@! =?>@! ?_>@! _?>@! ?0>@!
E existem outras duas soluções (que são equivalentes entre si). Eles também implementam a mesma lógica de curto-circuito, mas os caminhos de execução são um pouco mais loucos (e são deixados como um exercício para o leitor):
?<!@|
?<!@<
0010
: A e não B
# ?
# ! )
@ .
Isso também implementa uma forma de curto-circuito, mas devido ao uso do #
fluxo de controle é muito mais complicado. #
é um comutador IP condicional. Na verdade, o Hexagony vem com seis IPs rotulados 0
como 5
, que começam nos seis cantos da grade, apontando ao longo de sua borda no sentido horário (e o programa sempre começa com IP 0
). Quando a #
é encontrado, o valor atual é obtido no módulo 6
e o fluxo de controle continua com o IP correspondente. Não sei ao certo qual ataque de loucura me fez adicionar esse recurso, mas certamente permite alguns programas surpreendentes (como este).
Vamos distinguir três casos. Quando A = 0
, o programa é bastante simples, porque o valor é sempre 0
quando #
é encontrado, de forma que nenhuma alternância de IP ocorre:
#
não faz nada, ?
lê A
(ou seja, também não faz nada), #
ainda não faz nada, !
imprime 0
, )
incrementa (isso é importante, caso contrário, o IP não iria para a terceira linha), @
finaliza o programa. Simples o suficiente. Agora vamos considerar o caso (A, B) = (1, 0)
:
O caminho vermelho ainda corresponde ao IP 0
, e eu adicionei o caminho verde para o IP 1
. Vemos que, após as ?
leituras A
( 1
desta vez), as #
opções para o IP iniciam no canto superior direito. Isso significa que ?
pode ler B
( 0
). Agora )
incrementa isso para 1
, de modo que o #
canto superior esquerdo não faça nada e permanecemos com IP 1
. As !
impressões 1
e o IP envolvem a diagonal esquerda. #
ainda não faz nada e @
finaliza o programa.
Finalmente, o caso realmente estranho em que ambas as entradas são 1
:
Desta vez, a segunda entrada também é 1
e a )
incrementa 2
. Isso significa que o #
canto superior esquerdo causa outro switch IP para IP 2
, indicado em azul. Nesse caminho, primeiro incrementamos ainda mais 3
(embora isso seja irrelevante) e depois passamos pela ?
terceira vez. Como agora atingimos o EOF (ou seja, a entrada está esgotada), ?
retorna 0
, !
imprime isso e @
finaliza o programa.
Notavelmente, esta é a única solução de 6 bytes para esse gate.
0011
: UMA
? !
@ . .
. .
Isso é simples o suficiente para não precisarmos de um diagrama: ?
lê A
, !
imprime, @
termina.
Essa é a única solução de 3 bytes para esse gate. (Em princípio, também seria possível ,;@
, mas a pesquisa não incluiu ;
, porque acho que nunca poderá salvar bytes !
para esta tarefa.)
0100
: B e não A
+ ?
| @ !
? .
Este é muito mais simples que o seu "irmão" 0010
. O fluxo de controle é realmente o mesmo que vimos acima para 0001
(E). Se A = 0
, o IP percorre a linha inferior, lendo B
e imprimindo isso antes de terminar. Se, A = 1
então, o IP percorrer a primeira linha novamente, também lendo B
, mas +
adiciona duas bordas de memória não utilizadas, de modo que tudo o que faz é redefinir o valor atual para 0
, para que !
sempre seja impresso 0
.
Existem várias alternativas de 6 bytes para isso (42 no total). Primeiro, há uma tonelada de soluções equivalentes às anteriores. Podemos novamente escolher livremente entre |
e >
, e +
podemos ser substituídos por qualquer outro comando que nos dê uma vantagem vazia:
"?|@!? &?|@!? '?|@!? *?|@!? +?|@!? -?|@!? ^?|@!? {?|@!? }?|@!?
"?>@!? &?>@!? '?>@!? *?>@!? +?>@!? -?>@!? ^?>@!? {?>@!? }?>@!?
Além disso, também podemos usar em ]
vez de ?
. ]
move para o próximo IP (ou seja, seleciona IP 1
), para que esse ramo reutilize o ?
no canto superior direito. Isso dá outras 18 soluções:
"?|@!] &?|@!] '?|@!] *?|@!] +?|@!] -?|@!] ^?|@!] {?|@!] }?|@!]
"?>@!] &?>@!] '?>@!] *?>@!] +?>@!] -?>@!] ^?>@!] {?>@!] }?>@!]
E há outras seis soluções que funcionam de maneira diferente com níveis variados de loucura:
/[<@!? ?(#!@] ?(#>@! ?/@#/! [<<@!? [@$\!?
0101
: B
? ?
! @ .
. .
Woohoo, outro simples: ler A
, ler B
, imprimir B
, encerrar. Na verdade, existem alternativas para isso. Como A
é apenas um caractere, também podemos lê-lo com ,
:
,?!@
E também há a opção de usar um único ?
e um espelho para percorrê-lo duas vezes:
?|@! ?>@!
0110
: Xor
? < @
! ! < _
\ ~ ( . .
. . . .
. . .
Como eu disse acima, esse foi o único portão que não caberia no lado 2, então essa é uma solução escrita à mão por FryAmTheEggman e por mim e há uma boa chance de que não seja o ideal. Existem dois casos para distinguir. Se A = 0
o fluxo de controle for bastante simples (porque, nesse caso, precisamos apenas imprimir B
):
Começamos no caminho vermelho. ?
lê A
, <
é um ramo que desvia o zero restante. O IP _
passa para o fundo, então é outro espelho e, quando atinge o canto, ele passa para o canto superior esquerdo e continua no caminho azul. ?
lê B
, !
imprime. Agora o (
diminui. Isso é importante porque garante que o valor não seja positivo (seja agora 0
ou seja -1
). Isso faz com que o IP seja quebrado no canto direito, onde @
finaliza o programa.
Quando as A = 1
coisas ficam um pouco mais complicadas. Nesse caso, queremos imprimir not B
, o que por si só não é muito difícil, mas o caminho da execução é um pouco complicado.
Dessa vez, ele <
desvia o IP corretamente e, em seguida, <
age apenas como um espelho. Portanto, o IP percorre o mesmo caminho ao contrário, lendo B
quando encontra ?
novamente. O IP passa pelo canto direito e continua no caminho verde. It próximos encontros (~
que é "decremento, multiplicar por -1", que troca 0
e 1
e, por conseguinte, calcula not B
. \
é apenas um espelho e !
imprime o resultado desejado. Em seguida, ?
tenta retornar outro número, mas retorna zero. O IP agora continua no canto inferior esquerdo no caminho azul. (
diminui, <
reflete,(
diminui novamente, para que o valor atual seja negativo quando o IP atingir o canto. Ele se move pela diagonal inferior direita e, finalmente, pressiona @
para finalizar o programa.
0111
: Ou
? <
< @ !
. .
Mais curto-circuito.
O A = 0
caso (o caminho vermelho) é um pouco confuso aqui. O IP é desviado para a esquerda, passa para o canto inferior esquerdo, é imediatamente refletido pelo <
e retorna ?
à leitura B
. Em seguida, envolve o canto rígido, imprime B
com !
e termina.
O A = 1
caso (o caminho verde) é um pouco mais simples. A <
ramificação desvia o IP para a direita, então simplesmente imprimimos !
, voltamos para o canto superior esquerdo e terminamos em @
.
Existe apenas uma outra solução de 5 bytes:
\>?@!
Funciona essencialmente da mesma forma, mas os caminhos de execução reais são bem diferentes e usa um canto para ramificação em vez de a <
.
1000
: Nem
) \
! # ?
@ {
Este pode ser o meu programa favorito encontrado nesta pesquisa. O mais interessante é que essa implementação nor
realmente funciona para até 5 entradas. Vou ter que entrar um pouco nos detalhes do modelo de memória para explicar este. Assim, como uma atualização rápida, o modelo de memória do Hexagony é uma grade hexagonal separada, onde cada aresta possui um valor inteiro (inicialmente todo zero). Há um ponteiro de memória (MP) que indica uma aresta e uma direção ao longo dessa aresta (de modo que há duas arestas vizinhas na frente e atrás da aresta atual, com vizinhos significativos à esquerda e à direita). Aqui está um diagrama das arestas que usaremos, com o MP começando como mostrado em vermelho:
Vamos primeiro considerar o caso em que ambas as entradas são 0
:
Começamos no caminho cinza, que simplesmente incrementa a borda A para 1
que o #
comutador para IP, 1
que é o caminho azul, comece no canto superior direito. \
não faz nada lá e ?
lê uma entrada. Envolvemos no canto superior esquerdo, onde )
incrementa essa entrada. Agora, desde que a entrada seja zero, isso resultará em um 1
, para que #
não faça nada. Em seguida, {
move-se o MP para a esquerda, isto é, sobre a primeira iteração de um de B . Como essa borda ainda tem seu zero inicial, o IP volta ao canto superior direito e a uma nova borda de memória. Portanto, esse loop continuará enquanto ?
lê zeros, movendo o MP ao redor do hexágono de Bpara C para D e assim por diante. Não importa se ?
retorna um zero porque era uma entrada ou porque era EOF.
Após seis iterações através deste circuito, {
retorna a um . Dessa vez, a borda já mantém o valor 1
desde a primeira iteração; portanto, o IP é movido para o canto esquerdo e continua no caminho verde. !
simplesmente imprime isso 1
e @
finaliza o programa.
Agora, e se alguma das entradas for 1
?
Em seguida, ?
lê isso 1
em algum momento e o )
incrementa para 2
. Isso significa #
que agora vamos mudar de IP novamente e continuaremos no canto direito no caminho vermelho. ?
lê outra entrada (se houver), o que realmente não importa e {
move uma borda ainda mais. Isso deve ser uma borda não utilizada, portanto, isso funciona para até 5 entradas. O IP passa para o canto superior direito, onde é imediatamente refletido e para o canto esquerdo. !
imprime 0
na borda não utilizada e #
volta para IP 0
. Esse IP ainda estava esperando no #
sudoeste (caminho cinza), então ele imediatamente bate no @
e termina o programa.
No total, existem sete soluções de 7 bytes para esse gate. 5 deles funcionam da mesma forma que isso e simplesmente usam outros comandos para mover para uma aresta não utilizada (e podem caminhar em torno de um hexágono diferente ou em uma direção diferente):
)\!#?@" )\!#?@' )\!#?@^ )\!#?@{ )\!#?@}
E há outra classe de soluções que funciona apenas com duas entradas, mas cujos caminhos de execução são ainda mais confusos:
?]!|<)@ ?]!|<1@
1001
: Igualdade
( ~
? / @
# !
Isso também faz um uso muito inteligente da seleção de IP condicional. Precisamos distinguir novamente entre A = 0
e A = 1
. No primeiro caso, queremos imprimir not B
, no segundo, queremos imprimir B
. Pois A = 0
também distinguimos os dois casos para B
. Vamos começar com A = B = 0
:
Começamos no caminho cinza. (~
pode ser ignorado, o IP passa para o canto esquerdo (ainda no caminho cinza) e lê A
com ?
. (
diminui isso, então obtemos um -1
IP wrap no canto inferior esquerdo. Agora, como eu disse anteriormente, #
pega o módulo de valor 6
antes de escolher o IP, então um valor de -1
realmente sai do IP 5
, que começa no canto esquerdo no caminho vermelho. ?
lê B
, (
diminui isso também, para que continuemos no IP 5
quando acertarmos #
novamente. ~
nega o, de -1
modo que o IP passe para o canto inferior direito, imprima 1
e termina.
Agora, se B
for o caso 1
, o valor atual será 0
quando atingirmos #
a segunda vez; portanto, voltamos ao IP 0
(agora no caminho verde). Isso atinge ?
uma terceira vez, produzindo 0
, !
imprime e @
termina.
Finalmente, o caso em que A = 1
. Desta vez, o valor atual já é zero quando atingimos #
pela primeira vez, portanto, isso nunca muda para IP 5
em primeiro lugar. Simplesmente continuamos imediatamente no caminho verde. ?
agora não basta dar um zero, mas retornar B
. !
imprime e @
termina novamente.
No total, existem três soluções de 7 bytes para este gate. Os outros dois funcionam de maneira muito diferente (até um do outro) e fazem uso ainda mais estranho #
. Em particular, eles leem um ou mais valores com ,
(lendo um código de caractere em vez de um número inteiro) e depois usam esse valor módulo 6 para escolher um IP. É muito louco.
),)#?@!
?~#,~!@
1010
: Não ser
? ?
| @ !
) .
Este é bastante simples. O caminho da execução é o ramo horizontal que já conhecemos and
anteriormente. ??
lê A
e depois imediatamente B
. Após refletir |
e ramificar, B = 0
executaremos o ramo inferior, onde )
incrementa o valor pelo 1
qual é impresso !
. No ramo superior (se B = 1
), ?
basta redefinir a borda para a 0
qual também é impressa !
.
Existem oito programas de 6 bytes para este gate. Quatro deles são praticamente iguais, usando em >
vez de |
ou em 1
vez de )
(ou ambos):
??>@!) ??>@!1 ??|@!) ??|@!1
Dois usam um único ?
que é usado duas vezes devido a um espelho. A negação acontece como fizemos xor
com um (~
ou outro ~)
.
?>!)~@ ?>!~(@
E, finalmente, duas soluções usam um comutador IP condicional, porque por que usar da maneira simples se a complicada também funciona:
??#)!@ ??#1!@
1011
: B implica A
\ #
? ? !
1 @
Isso usa algumas opções de IP bastante elaboradas. Vou começar com o A = 1
caso desta vez, porque é mais simples:
Começamos no caminho cinza, que lê A
com ?
e depois bate no #
. Desde A
é 1
este muda para IP 1
(caminho verde). Ele !
imprime imediatamente que, o IP passa para o canto superior esquerdo, lê B
(desnecessariamente) e termina.
Quando as A = 0
coisas ficam um pouco mais interessantes. Primeiro vamos considerar A = B = 0
:
Desta vez, o #
não faz nada e permanecemos no IP 0
(caminho vermelho a partir desse ponto). ?
lê B
e 1
transforma em um 1
. Depois de empacotar no canto superior esquerdo, atingimos #
novamente, então terminamos no caminho verde, afinal, e imprimimos 1
como antes, antes de terminar.
Finalmente, aqui está (A, B) = (0, 1)
o caso falso:
Observe que removi o caminho cinza inicial para maior clareza, mas o programa começa da mesma maneira e terminamos no caminho vermelho como antes. Então desta vez o segundo ?
retorna 1
. Agora encontramos o 1
. Neste ponto, é importante entender o que os dígitos realmente fazem no Hexagony (até agora os usamos apenas em zeros): quando um dígito é encontrado, o valor atual é multiplicado por 10 e, em seguida, o dígito é adicionado. Isso normalmente é usado para escrever números decimais literalmente no código-fonte, mas significa que B = 1
na verdade é mapeado para o valor 11
. Então, quando atingimos #
, esse módulo é usado 6
para dar 5
e, portanto, passamos para IP 5
(em vez de 1
como antes) e continuamos no caminho azul. Batendo?
uma terceira vez retorna um zero, então !
imprime que, e depois de mais duas ?
, o IP passa para o canto inferior direito onde o programa termina.
Existem quatro soluções de 7 bytes para isso e todas elas funcionam de maneira diferente:
#)/!?@$ <!?_@#1 \#??!1@ |/)#?@!
1100
: Não A
? (
~ ! @
. .
Apenas um linear simples: ler A
com ?
, nega com (~
, imprimir com !
, termina com @
.
Há uma solução alternativa, que é negar ~)
:
?~)!@
1101
: A implica B
? .
| @ !
) .
Isso é muito mais simples do que a implicação oposta da qual acabamos de falar. É novamente um daqueles programas de ramificação horizontal, como esse and
. Se A
for 0
, simplesmente é incrementado para 1
o ramo inferior e impresso. Caso contrário, a ramificação superior é executada novamente onde ?
lê B
e depois !
imprime.
Há uma tonelada de alternativas aqui (66 soluções no total), principalmente devido à livre escolha de no-ops eficazes. Para começar, podemos variar a solução acima da mesma maneira que and
podemos e também podemos escolher entre )
e 1
:
?.|@!) .?|@!) ?=|@!) =?|@!) ?_|@!) _?|@!) ?0|@!)
?.|@!1 .?|@!1 ?=|@!1 =?|@!1 ?_|@!1 _?|@!1 ?0|@!1
?.>@!) .?>@!) ?=>@!) =?>@!) ?_>@!) _?>@!) ?0>@!)
?.>@!1 .?>@!1 ?=>@!1 =?>@!1 ?_>@!1 _?>@!1 ?0>@!1
E há uma versão diferente usando a seleção condicional de IP, onde o primeiro comando pode ser escolhido quase arbitrariamente, e também há uma opção entre )
e 1
para algumas dessas opções:
"?#1!@ &?#1!@ '?#1!@ )?#1!@ *?#1!@ +?#1!@ -?#1!@ .?#1!@
0?#1!@ 1?#1!@ 2?#1!@ 3?#1!@ 4?#1!@ 5?#1!@ 6?#1!@ 7?#1!@
8?#1!@ 9?#1!@ =?#1!@ ^?#1!@ _?#1!@ {?#1!@ }?#1!@
"?#)!@ &?#)!@ '?#)!@ *?#)!@ +?#)!@ -?#)!@
0?#)!@ 2?#)!@ 4?#)!@ 6?#)!@
8?#)!@ ^?#)!@ _?#)!@ {?#)!@ }?#)!@
1110
: Nand
? $
@ # )
! <
O último complicado. Se você ainda está lendo, quase conseguiu. :) Vamos olhar A = 0
primeiro:
?
lê A
e depois batemos $
. Este é um comando de salto (como o de Befunge #
) que pula a próxima instrução para que não terminemos no @
. Em vez disso, o IP continua em #
. No entanto, uma vez que A
é 0
, isso não faz nada. )
o incrementa para 1
que o IP continue no caminho inferior onde 1
é impresso. Ele <
desvia o IP para a direita, onde ele fica no canto esquerdo e o programa termina.
Em seguida, quando a entrada é apresentada, (A, B) = (1, 0)
temos esta situação:
É essencialmente o mesmo que antes, exceto que no #
que mudar para IP 1
(caminho verde), mas desde que B
é 0
que voltar para IP 0
quando bateu #
uma segunda vez (caminho agora azul), onde ele imprime 1
como antes.
Finalmente, o A = B = 1
caso:
Desta vez, #
na segunda vez, o valor atual ainda é 1
para que não alteremos o IP novamente. O <
reflete e, na terceira vez que atingimos ?
, obtemos um zero. Portanto, o IP passa para o canto inferior esquerdo, onde !
imprime o zero e o programa termina.
Existem nove soluções de 7 bytes no total para isso. A primeira alternativa simplesmente usa, em 1
vez de )
:
?$@#1!<
Depois, há duas soluções que farão sua cabeça com a quantidade de comutação de IP que está acontecendo:
)?#_[!@ 1?#_[!@
Isso realmente me impressionou: a parte interessante é que a comutação de IP pode ser usada como uma condicional adiada. As regras de comutação de IP do idioma são tais que o IP atual dá outro passo antes que o comutador aconteça. Se essa etapa passar por um canto, o valor atual decidirá em qual filial o IP continuará se voltarmos a ele. Exatamente isso acontece quando a entrada é A = B = 1
. Embora tudo isso seja consistente com a maneira como eu projetei a linguagem, nunca estive ciente dessa implicação da especificação, por isso é bom quando minha linguagem me ensina alguns novos truques: D.
Depois, há uma terceira solução cuja quantidade de comutação de IP é ainda pior (embora não faça uso desse efeito condicional adiado):
>?1]#!@
E depois há outro:
?$@#)!<
E existem essas quatro soluções equivalentes, que usam alguma troca de IP não condicional e, em vez disso, implementam toda a lógica por meio de ramificações e cantos:
]<?<@!) ]<?<@!1 ]|?<@!) ]|?<@!1
1111
: Verdadeiro
1 !
@ . .
. .
Você ganhou algo simples para o final: defina a borda para 1
, imprima com !
, encerre com @
. :)
Claro, há uma alternativa:
)!@
Como sempre, todos os diagramas de fluxo de controle criados com o HexagonyColorer de Timwi e o diagrama de memória com seu EsotericIDE .