Fissão , 1328 989 887 797 bytes
Esta resposta é um pouco irracionalmente longa (eu gostaria que tivéssemos regiões dobráveis ) ... por favor, não se esqueça de passar por isso e mostrar às outras respostas um pouco de amor!
Trabalhar nesse código foi o que inspirou esse desafio. Eu queria adicionar uma resposta em Fission ao EOEIS, o que me levou a essa sequência. No entanto, realmente aprender a Fissão e implementá-la levou algumas semanas trabalhando dentro e fora. Enquanto isso, a sequência realmente cresceu em mim, então eu decidi postar um desafio separado para ela (além disso, isso não teria sido particularmente distante na EOEIS).
Então, apresento a vocês a Monstrosity:
R'0@+\
/ Y@</ /[@ Y]_L
[? % \ / \ J
\$@ [Z/;[{+++++++++L
UR+++++++++>/;
9\ ; 7A9
SQS {+L /$ \/\/\/\/\/ 5/ @ [~ &@[S\/ \ D /8/
~4X /A@[ %5 /; & K } [S//~KSA /
3 \ A$@S S\/ \/\/\/ \/>\ /S]@A / \ { +X
W7 X X /> \ +\ A\ / \ /6~@/ \/
/ ~A\; +;\ /@
ZX [K / {/ / @ @ } \ X @
\AS </ \V / }SZS S/
X ;;@\ /;X /> \ ; X X
; \@+ >/ }$S SZS\+; //\V
/ \\ /\; X X @ @ \~K{
\0X / /~/V\V / 0W//
\ Z [K \ //\
W /MJ $$\\ /\7\A /;7/\/ /
4}K~@\ &] @\ 3/\
/ \{ }$A/1 2 }Y~K <\
[{/\ ;@\@ / \@<+@^ 1;}++@S68
@\ <\ 2 ; \ /
$ ;}++ +++++++L
%@A{/
M \@+>/
~ @
SNR'0YK
\ A!/
Ele espera que não haja uma nova linha à direita na entrada; portanto, convém chamá-lo assim echo -n 120 | ./Fission oeis256504.fis
.
O layout provavelmente ainda pode ser mais eficiente, então acho que ainda há muito espaço para melhorias aqui (por exemplo, isso contém 911 581 461 374 espaços).
Antes de chegarmos à explicação, uma observação sobre o teste: o intérprete oficial não funciona totalmente como está. a) Mirror.cpp
não compila em muitos sistemas. Se você encontrar esse problema, basta comentar a linha incorreta - o componente afetado (um espelho aleatório) não é usado neste código. b) Existem alguns erros que podem levar a um comportamento indefinido (e provavelmente resultarão em um programa desse complexo). Você pode aplicar esse patch para corrigi-los. Depois de fazer isso, você poderá compilar o intérprete com
g++ -g --std=c++11 *.cpp -o Fission
Curiosidade: Este programa usa quase todos os componentes que a Fissão tem a oferecer, exceto #
(espelho aleatório), :
(meio espelho) -
ou |
(espelho simples) e "
(modo de impressão).
O que na Terra?
Aviso: Isso será bastante longo ... Suponho que você esteja realmente interessado em saber como a Fissão funciona e como se pode programar nela. Porque se você não estiver, não tenho certeza de como poderia resumir isso. (O próximo parágrafo fornece uma descrição geral do idioma.)
A fissão é uma linguagem de programação bidimensional, onde dados e fluxo de controle são representados por átomos se movendo através de uma grade. Se você já viu ou usou o Marbelous antes, o conceito deve ser vagamente familiar. Cada átomo tem duas propriedades inteiras: uma massa não negativa e uma energia arbitrária. Se a massa se tornar negativa, o átomo é removido da grade. Na maioria dos casos, você pode tratar a massa como o "valor" do átomo e a energia como algum tipo de metapropriedade usada por vários componentes para determinar o fluxo dos átomos (ou seja, a maioria dos tipos de comutadores depende do sinal de a energia). Denotarei átomos por (m,E)
, quando necessário. No início do programa, a grade começa com um monte de(1,0)
átomos de onde você coloca um dos quatro componentes UDLR
(onde a letra indica a direção em que o átomo está se movendo inicialmente). O quadro é então preenchido com um monte de componentes que alteram a massa e a energia dos átomos, mudam de direção ou fazem outras coisas mais sofisticadas. Para uma lista completa, consulte a página esolangs , mas apresentarei a maioria deles nesta explicação. Outro ponto importante (que o programa utiliza várias vezes) é que a grade é toroidal: um átomo que atinge qualquer um dos lados reaparece no lado oposto, movendo-se na mesma direção.
Escrevi o programa em várias partes menores e montei-as no final, e é assim que vou explicar a explicação.
atoi
Esse componente pode parecer bastante desinteressante, mas é agradável e simples e permite que eu introduza muitos dos conceitos importantes do fluxo aritmético e de controle da Fission. Portanto, irei abordar esta parte com detalhes bastante meticulosos, para que eu possa reduzir as outras partes à introdução de novas mecânicas de Fissão e apontar componentes de nível superior cujo fluxo de controle detalhado você deve seguir.
A fissão só pode ler valores de bytes de caracteres individuais, não números inteiros. Embora seja uma prática aceitável por aqui, imaginei que, enquanto fazia isso, poderia fazer o certo e analisar números inteiros reais no STDIN. Aqui está o atoi
código:
;
R'0@+\
/ Y@</ /[@ Y]_L
[? % \ / \ J
\$@ [Z/;[{+++++++++L
UR+++++++++>/;
O
Dois dos componentes mais importantes na fissão são os reatores de fissão e fusão. Reatores de fissão são qualquer um V^<>
(o código acima usa <
e >
). Um reator de fissão pode armazenar um átomo (enviando-o para a cunha do personagem), sendo o padrão (2,0)
. Se um átomo atingir o ápice do personagem, dois novos átomos serão enviados para os lados. Sua massa é determinada dividindo-se a massa recebida pela massa armazenada (ou seja, diminuindo pela metade) - o átomo à esquerda obtém esse valor e o átomo à direita obtém o restante da massa (ou seja, a massa é conservada na fissão) . Ambos os átomos de saída terão a energia recebida menosa energia armazenada. Isso significa que podemos usar reatores de fissão para aritmética - tanto para subtração quanto para divisão. Se um reator de fissão é atingido a partir do local, o átomo é simplesmente refletido na diagonal e depois se move na direção do ápice do personagem.
Reatores de fusão são qualquer um YA{}
(o código acima usa Y
e {
). Sua função é semelhante: eles podem armazenar um átomo (padrão (1,0)
) e, quando atingidos do ápice, dois novos átomos serão enviados para os lados. No entanto, neste caso, os dois átomos serão idênticos, sempre retendo a energia recebida e multiplicando a massa recebida pela massa armazenada. Ou seja, por padrão, o reator de fusão simplesmente duplica qualquer átomo atingindo seu ápice. Quando atingidos pelos lados, os reatores de fusão são um pouco mais complicados: o átomo também éarmazenado (independentemente da outra memória) até que um átomo atinja o lado oposto. Quando isso acontece, um novo átomo é liberado na direção do ápice, cuja massa e energia são a soma dos dois átomos antigos. Se um novo átomo atinge o mesmo lado antes de um átomo correspondente atingir o lado oposto, o átomo antigo será simplesmente substituído. Os reatores de fusão podem ser usados para implementar adição e multiplicação.
Outro componente simples Eu quero ficar fora do caminho é [
e ]
que simplesmente definir a direção do átomo para a direita e esquerda, respectivamente (independentemente da direção de entrada). Os equivalentes verticais são M
(para baixo) e W
(para cima), mas não são usados para o atoi
código. UDLR
também atuam como WM][
após liberar seus átomos iniciais.
De qualquer forma, vamos olhar o código lá em cima. O programa começa com 5 átomos:
- O
R
e L
na parte inferior simplesmente obtêm seu incremento de massa (com +
) (10,0)
e depois são armazenados em um reator de fissão e de fusão, respectivamente. Usaremos esses reatores para analisar a entrada da base 10.
- O
L
canto superior direito faz com que sua massa diminua (com _
) para se tornar (0,0)
e é armazenada no lado de um reator de fusão Y
. Isso é para acompanhar o número que estamos lendo - aumentaremos gradualmente e multiplicaremos isso à medida que lermos números.
- No
R
canto superior esquerdo, sua massa é definida como o código de caractere de 0
(48) com '0
, então massa e energia são trocadas @
e, finalmente, a massa é incrementada uma vez com +
a doação (1,48)
. Em seguida, é redirecionado com espelhos diagonais \
e /
armazenado em um reator de fissão. Usaremos a 48
subtração for para transformar a entrada ASCII nos valores reais dos dígitos. Também tivemos que aumentar a massa 1
para evitar a divisão 0
.
- Finalmente,
U
no canto inferior esquerdo é o que realmente coloca tudo em movimento e é inicialmente usado apenas para controlar o fluxo.
Depois de ser redirecionado para a direita, o átomo de controle é atingido ?
. Este é o componente de entrada. Ele lê um caractere e define a massa do átomo para o valor ASCII lido e a energia para 0
. Se atingirmos EOF, a energia será ajustada para 1
.
O átomo continua e depois bate %
. Este é um interruptor de espelho. Para energia não positiva, isso age como um /
espelho. Mas, para energia positiva, age como um \
(e também diminui a energia em 1). Então, enquanto estivermos lendo os personagens, o átomo será refletido para cima e podemos processar o personagem. Mas quando terminarmos a entrada, o átomo será refletido para baixo e podemos aplicar uma lógica diferente para recuperar o resultado. Para sua informação, o componente oposto é &
.
Então, temos um átomo subindo por enquanto. O que queremos fazer para cada caractere é ler o valor do dígito, adicionar isso ao total da corrida e multiplicar esse total por 10 para preparar o próximo dígito.
O átomo de caractere primeiro atinge um reator de fusão (padrão) Y
. Isso divide o átomo e usamos a cópia à esquerda como um átomo de controle para retornar ao componente de entrada e ler o próximo caractere. A cópia correta será processada. Considere o caso em que lemos o personagem 3
. Nosso átomo será (51,0)
. Trocamos massa e energia com @
, de modo que possamos fazer uso da subtração do próximo reator de fissão. O reator subtrai 48
a energia (sem alterar a massa), então envia duas cópias de (0,3)
- a energia agora corresponde ao dígito que lemos. A cópia inicial é simplesmente descartada ;
(um componente que destrói todos os átomos recebidos). Continuaremos trabalhando com a cópia descendente. Você precisará seguir seu caminho através do/
e \
espelha um pouco.
O @
pouco antes do reator de fusão troca massa e energia novamente, de modo que adicionaremos (3,0)
ao total em execução no Y
. Observe que o total em execução, portanto, sempre terá 0
energia.
Agora J
é um salto. O que ele faz é pular qualquer átomo recebido para a frente por sua energia. Se for 0
, o átomo continua seguindo em frente. Se for 1
, pulará uma célula, se for 2
, pulará duas células e assim por diante. A energia é gasta no salto, então o átomo sempre acaba com energia 0
. Como o total em execução tem energia zero, o salto é ignorado por enquanto e o átomo é redirecionado para o reator de fusão {
que multiplica sua massa por 10
. A cópia descendente é descartada ;
enquanto a cópia descendente é realimentada no Y
reator como o novo total em execução.
O exemplo acima continua repetindo (de uma maneira engraçada em pipeline, onde novos dígitos estão sendo processados antes que os anteriores sejam feitos) até atingirmos o EOF. Agora o %
enviará o átomo para baixo. A idéia é transformar esse átomo (0,1)
agora antes de atingir o reator total em funcionamento, de modo que a) o total não seja afetado (massa zero) eb) tenhamos energia 1
para pular o [
. Podemos cuidar facilmente da energia $
que aumenta a energia.
A questão é que ?
não redefinir a massa quando você estiver atingindo o EOF, portanto a massa ainda será a do último caractere lido, e a energia será 0
(porque %
diminuiu a 1
volta para 0
). Então, queremos nos livrar dessa massa. Para fazer isso, trocamos massa e energia @
novamente.
Eu preciso introduzir mais um componente antes de terminar esta seção: Z
. É essencialmente o mesmo que %
ou &
. A diferença é que ela permite que átomos de energia positiva passem direto (enquanto diminui a energia) e desvia os átomos de energia não positiva 90 graus para a esquerda. Podemos usar isso para eliminar a energia de um átomo, repetindo-a Z
repetidamente - assim que a energia acabar, o átomo será desviado e sairá do loop. Esse é esse padrão:
/ \
[Z/
onde o átomo se moverá para cima quando a energia for zero. Usarei esse padrão de uma forma ou de outra várias vezes nas outras partes do programa.
Então, quando o átomo sair desse pequeno loop, ele será (1,0)
trocado (0,1)
pelo @
antes de atingir o reator de fusão para liberar o resultado final da entrada. No entanto, o total atual será reduzido em um fator de 10, porque já o multiplicamos por outro dígito.
Então agora, com energia 1
, esse átomo irá pular [
e pular para dentro /
. Isso o desvia para um reator de fissão que preparamos para dividir por 10 e corrigir nossa multiplicação estranha. Novamente, descartamos uma metade com ;
e mantemos a outra como saída (aqui representada com a O
qual simplesmente imprimiríamos o caractere correspondente e destruiríamos o átomo - no programa completo, continuamos usando o átomo).
itoa
/ \
input -> [{/\ ;@
@\ <\
$ ;}++ +++++++L
%@A{/
M \@+>/
~ @
SNR'0YK
\ A!/
Obviamente, também precisamos converter o resultado novamente em uma string e imprimi-lo. É para isso que serve essa parte. Isso pressupõe que a entrada não chegue antes do ponto 10, mas no programa completo que é facilmente fornecido. Este bit pode ser encontrado na parte inferior do programa completo.
Este código apresenta um novo e poderoso componente de fissão: a pilha K
. A pilha está inicialmente vazia. Quando um átomo com energia não negativa atinge a pilha, o átomo é simplesmente empurrado para a pilha. Quando um átomo com energia negativa atinge a pilha, sua massa e energia serão substituídas pelo átomo no topo da pilha (que é assim liberado). Se a pilha estiver vazia, a direção do átomo é revertida e sua energia se torna positiva (isto é, é multiplicada por -1
).
Ok, voltando ao código real. A idéia do itoa
trecho é pegar repetidamente o módulo de entrada 10 para encontrar o próximo dígito enquanto divide a entrada por 10 para a próxima iteração. Isso produzirá todos os dígitos na ordem inversa (do menos significativo para o mais significativo). Para fixar a ordem, colocamos todos os dígitos em uma pilha e, no final, os separamos um a um para imprimi-los.
A metade superior do código calcula os dígitos: o L
com as vantagens dá um 10, que clonamos e alimentamos em um reator de fissão e fusão, para que possamos dividir e multiplicar por 10. O loop começa essencialmente após o [
canto superior esquerdo . O valor atual é dividido: uma cópia é dividida por 10, depois multiplicada por 10 e armazenada em um reator de fissão, que é atingido pela outra cópia no ápice. Isso calcula i % 10
como i - ((i/10) * 10)
. Observe também que A
divide o resultado intermediário após a divisão e antes da multiplicação, para que possamos alimentar i / 10
a próxima iteração.
A %
aborta o loop uma vez que a variável iteração atinge 0. Uma vez que este é mais ou menos um do-while loop, este código seria mesmo trabalho para a impressão 0
(sem criar zeros à esquerda em contrário). Uma vez que deixamos o loop, queremos esvaziar a pilha e imprimir os dígitos. S
é o oposto de Z
, portanto, é um interruptor que desviará um átomo de entrada com energia não positiva 90 graus para a direita. Portanto, o átomo realmente se move sobre a borda, da S
reta para a, K
para sair de um dígito (observe o ~
que garante que o átomo recebido tenha energia -1
). Esse dígito é incrementado por 48
para obter o código ASCII do caractere de dígito correspondente. O A
divide o dígito para imprimir uma cópia com!
e alimente a outra cópia novamente no Y
reator para o próximo dígito. A cópia impressa é usada como o próximo gatilho para a pilha (observe que os espelhos também a enviam pela borda para atingir M
a esquerda).
Quando a pilha está vazia, K
ela refletirá o átomo e transformará sua energia em +1
, de modo que passe diretamente através do S
. N
imprime uma nova linha (apenas porque é elegante :)). E então o átomo passa R'0
novamente para o lado do Y
. Como não há mais átomos por perto, isso nunca será lançado e o programa será encerrado.
Cálculo do número de fissão: a estrutura
Vamos para a carne real do programa. O código é basicamente uma porta da minha implementação de referência do Mathematica:
fission[n_] := If[
(div =
SelectFirst[
Reverse@Divisors[2 n],
(OddQ@# == IntegerQ[n/#]
&& n/# > (# - 1)/2) &
]
) == 1,
1,
1 + Total[fission /@ (Range@div + n/div - (div + 1)/2)]
]
onde div
é o número de números inteiros na partição máxima.
As principais diferenças são que não podemos lidar com valores de meio inteiro na Fissão, então estou fazendo muitas coisas multiplicadas por dois e que não há recursão na Fissão. Para contornar isso, estou pressionando todos os números inteiros em uma partição em uma fila para serem processados posteriormente. Para cada número que processamos, incrementaremos um contador em um e, quando a fila estiver vazia, liberaremos o contador e enviaremos para impressão. (Uma fila,, Q
funciona exatamente da mesma forma K
, apenas na ordem FIFO.)
Aqui está uma estrutura para este conceito:
+--- input goes in here
v
SQS ---> compute div from n D /8/
~4X | /~KSA /
3 +-----------> { +X
initial trigger ---> W 6~@/ \/
4
W ^ /
| 3
^ generate range |
| from n and div <-+----- S6
| -then-
+---- release new trigger
Os novos componentes mais importantes são os dígitos. Estes são teleportadores. Todos os teleportadores com o mesmo dígito pertencem um ao outro. Quando um átomo atinge um teletransportador, ele imediatamente move o próximo teletransportador no mesmo grupo, onde o próximo é determinado na ordem usual da esquerda para a direita e de cima para baixo. Isso não é necessário, mas ajuda no layout (e, portanto, no golfe). Há também o X
que simplesmente duplica um átomo, enviando uma cópia para a frente e a outra para trás.
A essa altura, você poderá resolver a maior parte da estrutura. O canto superior esquerdo tem a fila de valores ainda a serem processados e libera um de n
cada vez. Uma cópia n
é teletransportada para o fundo porque precisamos dela ao calcular o intervalo, a outra cópia entra no bloco na parte superior que calcula div
(essa é de longe a maior seção do código). Uma vez div
calculado, ele é duplicado - uma cópia incrementa um contador no canto superior direito, armazenado em K
. A outra cópia é teleportada para o fundo. Se div
foi 1
, desviámo-lo para cima imediatamente e usá-lo como gatilho para a próxima iteração, sem enfileirar novos valores. Caso contrário, usamos div
en
na seção na parte inferior para gerar o novo intervalo (ou seja, um fluxo de átomos com as massas correspondentes que são subsequentemente colocadas na fila) e, em seguida, solte um novo gatilho após o término do intervalo.
Quando a fila estiver vazia, o gatilho será refletido, passando direto pelo S
e reaparecendo no canto superior direito, de onde ele libera o contador (o resultado final) A
, do qual é então teleportado para a itoa
via 8
.
Cálculo do número de fissão: o corpo do loop
Então, tudo o que resta são as duas seções para calcular div
e gerar o intervalo. Computação div
é esta parte:
;
{+L /$ \/\/\/\/\/ 5/ @ [~ &@[S\/ \
/A@[ %5 /; & K } [S/
\ A$@S S\/ \/\/\/ \/>\ /S]@A / \
X X /> \ +\ A\ / \ /
/ ~A\; +;\ /@
ZX [K / {/ / @ @ } \ X @
\AS </ \V / }SZS S/
X ;;@\ /;X /> \ ; X X
\@+ >/ }$S SZS\+; //\V
/ \\ /\; X X @ @ \~K{
\0X / /~/V\V / 0W//
\ Z [K \ //\
\ /\7\A /;7/\/
Você provavelmente já viu o suficiente agora para resolver isso sozinho com alguma paciência. O detalhamento de alto nível é o seguinte: as primeiras 12 colunas geram um fluxo de divisores de 2n
. As próximas 10 colunas filtram as que não satisfazem OddQ@# == IntegerQ[n/#]
. As próximas 8 colunas filtram as que não satisfazem n/# > (# - 1)/2)
. Finalmente, empurramos todos os divisores válidos em uma pilha e, quando terminamos, esvaziamos toda a pilha em um reator de fusão (sobrescrevendo todos, exceto o último / maior divisor) e liberamos o resultado, seguido pela eliminação de sua energia (que não era zero de verificar a desigualdade).
Existem muitos caminhos loucos por lá que realmente não fazem nada. Predominantemente, a \/\/\/\/
loucura no topo (os 5
s também fazem parte) e um caminho ao redor do fundo (que passa pelos 7
). Eu tive que adicioná-los para lidar com algumas condições desagradáveis de corrida. A fissão poderia usar um componente de atraso ...
O código que gera o novo intervalo de n
e div
é este:
/MJ $$\
4}K~@\ &] @\ 3/\
\{ }$A/1 2 }Y~K <\
\@ / \@<+@^ 1;}++@
2 ; \ /
Primeiro calculamos n/div - (div + 1)/2
(ambos os termos, o que resulta no mesmo resultado) e armazenamos para mais tarde. Em seguida, geramos um intervalo de div
baixo para 1
e adicionamos o valor armazenado a cada um deles.
Existem dois novos padrões comuns em ambos, que devo mencionar: um é SX
ou é ZX
atingido por baixo (ou versões rotacionadas). Essa é uma boa maneira de duplicar um átomo, se você quiser que uma cópia avance (já que o redirecionamento das saídas de um reator de fusão às vezes pode ser complicado). O S
ou Z
gira o átomo para dentro X
e, em seguida, gira a cópia espelhada de volta para a direção original de propagação.
O outro padrão é
[K
\A --> output
Se armazenarmos algum valor K
, podemos recuperá-lo repetidamente, atingindo K
com energia negativa do topo. O A
duplica o valor que estamos interessados e envia o que copiar de volta à direita para a pilha para a próxima vez que precisar.
Bem, isso foi um tomo ... mas se você realmente passou por isso, espero que você tenha a ideia de que a Fission