ATUALIZAR! Melhoria maciça de desempenho!n = 7 agora é concluído em menos de 10 minutos! Veja a explicação na parte inferior!
Foi divertido escrever isso. Este é um solucionador de força bruta para esse problema escrito em Funciton. Alguns factóides:
- Ele aceita um número inteiro em STDIN. Qualquer espaço em branco estranho o quebra, incluindo uma nova linha após o número inteiro.
- Ele usa os números 0 a n - 1 (não 1 a n ).
- Ele preenche a grade "para trás", para que você obtenha uma solução na qual a linha inferior lê,
3 2 1 0
e não na linha superior 0 1 2 3
.
- Ele gera corretamente
0
(a única solução) para n = 1.
- Saída vazia para n = 2 en = 3.
- Quando compilado em um exe, leva cerca de 8¼ minutos para n = 7 (foi cerca de uma hora antes da melhoria de desempenho). Sem compilar (usando o intérprete), leva cerca de 1,5 vezes mais, portanto vale a pena usar o compilador.
- Como marco pessoal, esta é a primeira vez que escrevi um programa inteiro da Funciton sem antes escrever o programa em uma linguagem de pseudocódigo. Eu escrevi em C # real primeiro.
- (Entretanto, não é a primeira vez que faço uma alteração para melhorar enormemente o desempenho de algo no Funciton. A primeira vez que fiz isso foi na função fatorial. Trocar a ordem dos operandos da multiplicação fez uma enorme diferença por causa de como o algoritmo de multiplicação funciona . Caso você tenha curiosidade.)
Sem mais delongas:
┌────────────────────────────────────┐ ┌─────────────────┐
│ ┌─┴─╖ ╓───╖ ┌─┴─╖ ┌──────┐ │
│ ┌─────────────┤ · ╟─╢ Ӂ ╟─┤ · ╟───┤ │ │
│ │ ╘═╤═╝ ╙─┬─╜ ╘═╤═╝ ┌─┴─╖ │ │
│ │ └─────┴─────┘ │ ♯ ║ │ │
│ ┌─┴─╖ ╘═╤═╝ │ │
│ ┌────────────┤ · ╟───────────────────────────────┴───┐ │ │
┌─┴─╖ ┌─┴─╖ ┌────╖ ╘═╤═╝ ┌──────────┐ ┌────────┐ ┌─┴─╖│ │
│ ♭ ║ │ × ╟───┤ >> ╟───┴───┘ ┌─┴─╖ │ ┌────╖ └─┤ · ╟┴┐ │
╘═╤═╝ ╘═╤═╝ ╘══╤═╝ ┌─────┤ · ╟───────┴─┤ << ╟─┐ ╘═╤═╝ │ │
┌───────┴─────┘ ┌────╖ │ │ ╘═╤═╝ ╘══╤═╝ │ │ │ │
│ ┌─────────┤ >> ╟─┘ │ └───────┐ │ │ │ │ │
│ │ ╘══╤═╝ ┌─┴─╖ ╔═══╗ ┌─┴─╖ ┌┐ │ │ ┌─┴─╖ │ │
│ │ ┌┴┐ ┌───────┤ ♫ ║ ┌─╢ 0 ║ ┌─┤ · ╟─┤├─┤ ├─┤ Ӝ ║ │ │
│ │ ╔═══╗ └┬┘ │ ╘═╤═╝ │ ╚═╤═╝ │ ╘═╤═╝ └┘ │ │ ╘═╤═╝ │ │
│ │ ║ 1 ╟───┬┘ ┌─┴─╖ └───┘ ┌─┴─╖ │ │ │ │ │ ┌─┴─╖ │
│ │ ╚═══╝ ┌─┴─╖ │ ɓ ╟─────────────┤ ? ╟─┘ │ ┌─┴─╖ │ ├─┤ · ╟─┴─┐
│ ├─────────┤ · ╟─┐ ╘═╤═╝ ╘═╤═╝ ┌─┴────┤ + ╟─┘ │ ╘═╤═╝ │
┌─┴─╖ ┌─┴─╖ ╘═╤═╝ │ ╔═╧═╕ ╔═══╗ ┌───╖ ┌─┴─╖ ┌─┴─╖ ╘═══╝ │ │ │
┌─┤ · ╟─┤ · ╟───┐ └┐ └─╢ ├─╢ 0 ╟─┤ ⌑ ╟─┤ ? ╟─┤ · ╟──────────────┘ │ │
│ ╘═╤═╝ ╘═╤═╝ └───┐ ┌┴┐ ╚═╤═╛ ╚═╤═╝ ╘═══╝ ╘═╤═╝ ╘═╤═╝ │ │
│ │ ┌─┴─╖ ┌───╖ │ └┬┘ ┌─┴─╖ ┌─┘ │ │ │ │
│ ┌─┴───┤ · ╟─┤ Җ ╟─┘ └────┤ ? ╟─┴─┐ ┌─────────────┘ │ │
│ │ ╘═╤═╝ ╘═╤═╝ ╘═╤═╝ │ │╔════╗╔════╗ │ │
│ │ │ ┌──┴─╖ ┌┐ ┌┐ ┌─┴─╖ ┌─┴─╖ │║ 10 ║║ 32 ║ ┌─────────────────┘ │
│ │ │ │ << ╟─┤├─┬─┤├─┤ · ╟─┤ · ╟─┘╚══╤═╝╚╤═══╝ ┌──┴──┐ │
│ │ │ ╘══╤═╝ └┘ │ └┘ ╘═╤═╝ ╘═╤═╝ │ ┌─┴─╖ ┌─┴─╖ ┌─┴─╖ │
│ │ ┌─┴─╖ ┌─┴─╖ ┌─┴─╖ ┌─┴─╖ ╔═╧═╕ └─┤ ? ╟─┤ · ╟─┤ % ║ │
│ └─────┤ · ╟─┤ · ╟──┤ Ӂ ╟──┤ ɱ ╟─╢ ├───┐ ╘═╤═╝ ╘═╤═╝ ╘═╤═╝ │
│ ╘═╤═╝ ╘═╤═╝ ╘═╤═╝ ╘═══╝ ╚═╤═╛ ┌─┴─╖ ┌─┴─╖ │ └────────────────────┘
│ └─────┤ │ └───┤ ‼ ╟─┤ ‼ ║ │ ┌──────┐
│ │ │ ╘═══╝ ╘═╤═╝ │ │ ┌────┴────╖
│ │ │ ┌─┴─╖ │ │ │ str→int ║
│ │ └──────────────────────┤ · ╟───┴─┐ │ ╘════╤════╝
│ │ ┌─────────╖ ╘═╤═╝ │ ╔═╧═╗ ┌──┴──┐
│ └──────────┤ int→str ╟──────────┘ │ ║ ║ │ ┌───┴───┐
│ ╘═════════╝ │ ╚═══╝ │ │ ┌───╖ │
└───────────────────────────────────────────────────────┘ │ └─┤ × ╟─┘
┌──────────────┐ ╔═══╗ │ ╘═╤═╝
╔════╗ │ ╓───╖ ┌───╖ │ ┌───╢ 0 ║ │ ┌─┴─╖ ╔═══╗
║ −1 ║ └─╢ Ӝ ╟─┤ × ╟──┴──────┐ │ ╚═╤═╝ └───┤ Ӂ ╟─╢ 0 ║
╚═╤══╝ ╙───╜ ╘═╤═╝ │ │ ┌─┴─╖ ╘═╤═╝ ╚═══╝
┌─┴──╖ ┌┐ ┌───╖ ┌┐ ┌─┴──╖ ╔════╗ │ │ ┌─┤ ╟───────┴───────┐
│ << ╟─┤├─┤ ÷ ╟─┤├─┤ << ║ ║ −1 ║ │ │ │ └─┬─╜ ┌─┐ ┌─────┐ │
╘═╤══╝ └┘ ╘═╤═╝ └┘ ╘═╤══╝ ╚═╤══╝ │ │ │ └───┴─┘ │ ┌─┴─╖ │
│ └─┘ └──────┘ │ │ └───────────┘ ┌─┤ ? ╟─┘
└──────────────────────────────┘ ╓───╖ └───────────────┘ ╘═╤═╝
┌───────────╢ Җ ╟────────────┐ │
┌────────────────────────┴───┐ ╙───╜ │
│ ┌─┴────────────────────┐ ┌─┴─╖
┌─┴─╖ ┌─┴─╖ ┌─┴─┤ · ╟──────────────────┐
│ ♯ ║ ┌────────────────────┤ · ╟───────┐ │ ╘═╤═╝ │
╘═╤═╝ │ ╘═╤═╝ │ │ │ ┌───╖ │
┌─────┴───┘ ┌─────────────────┴─┐ ┌───┴───┐ ┌─┴─╖ ┌─┴─╖ ┌─┤ × ╟─┴─┐
│ │ ┌─┴─╖ │ ┌───┴────┤ · ╟─┤ · ╟──────────┤ ╘═╤═╝ │
│ │ ┌───╖ ┌───╖ ┌──┤ · ╟─┘ ┌─┴─┐ ╘═╤═╝ ╘═╤═╝ ┌─┴─╖ │ │
│ ┌────┴─┤ ♭ ╟─┤ × ╟──┘ ╘═╤═╝ │ ┌─┴─╖ ┌───╖└┐ ┌──┴─╖ ┌─┤ · ╟─┘ │
│ │ ╘═══╝ ╘═╤═╝ ┌───╖ │ │ │ × ╟─┤ Ӝ ╟─┴─┤ ÷% ╟─┐ │ ╘═╤═╝ ┌───╖ │
│ ┌─────┴───┐ ┌────┴───┤ Ӝ ╟─┴─┐ │ ╘═╤═╝ ╘═╤═╝ ╘══╤═╝ │ │ └───┤ Ӝ ╟─┘
│ ┌─┴─╖ ┌───╖ │ │ ┌────╖ ╘═╤═╝ │ └───┘ ┌─┴─╖ │ │ └────┐ ╘═╤═╝
│ │ × ╟─┤ Ӝ ╟─┘ └─┤ << ╟───┘ ┌─┴─╖ ┌───────┤ · ╟───┐ │ ┌─┴─╖ ┌───╖ │ │
│ ╘═╤═╝ ╘═╤═╝ ╘══╤═╝ ┌───┤ + ║ │ ╘═╤═╝ ├──┴─┤ · ╟─┤ × ╟─┘ │
└───┤ └────┐ ╔═══╗ ┌─┴─╖ ┌─┴─╖ ╘═╤═╝ │ ╔═══╗ ┌─┴─╖ ┌─┴─╖ ╘═╤═╝ ╘═╤═╝ │
┌─┴─╖ ┌────╖ │ ║ 0 ╟─┤ ? ╟─┤ = ║ ┌┴┐ │ ║ 0 ╟─┤ ? ╟─┤ = ║ │ │ ┌────╖ │
│ × ╟─┤ << ╟─┘ ╚═══╝ ╘═╤═╝ ╘═╤═╝ └┬┘ │ ╚═══╝ ╘═╤═╝ ╘═╤═╝ │ └─┤ << ╟─┘
╘═╤═╝ ╘═╤══╝ ┌┐ ┌┐ │ │ └───┘ ┌─┴─╖ ├──────┘ ╘═╤══╝
│ └────┤├──┬──┤├─┘ ├─────────────────┤ · ╟───┘ │
│ └┘┌─┴─╖└┘ │ ┌┐ ┌┐ ╘═╤═╝ ┌┐ ┌┐ │
└────────────┤ · ╟─────────┘ ┌─┤├─┬─┤├─┐ └───┤├─┬─┤├────────────┘
╘═╤═╝ │ └┘ │ └┘ │ └┘ │ └┘
└───────────────┘ │ └────────────┘
Explicação da primeira versão
A primeira versão levou cerca de uma hora para resolver n = 7. O seguinte explica principalmente como essa versão lenta funcionou. Na parte inferior, explicarei as alterações que fiz para reduzir para menos de 10 minutos.
Uma excursão em pedaços
Este programa precisa de bits. Ele precisa de muitos bits, e precisa deles nos lugares certos. Programadores de funções experientes já sabem que se você precisar de n bits, poderá usar a fórmula
que em Funciton pode ser expresso como
Ao fazer minha otimização de desempenho, ocorreu-me que eu posso calcular o mesmo valor muito mais rapidamente usando esta fórmula:
Espero que você me perdoe por não ter atualizado todos os gráficos de equação neste post.
Agora, digamos que você não queira um bloco contíguo de bits; na verdade, você quer n bits em intervalos regulares a cada k- ésimo bit, assim:
LSB
↓
00000010000001000000100000010000001
└──┬──┘
k
A fórmula para isso é bastante direta quando você a conhece:
No código, a função Ӝ
toma valores de n e k e calcula esta fórmula.
Acompanhar os números usados
Existem n ² números na grade final e cada número pode ter qualquer um dos n valores possíveis. Para acompanhar quais números são permitidos em cada célula, mantemos um número que consiste em n ³ bits, no qual um bit é definido para indicar que um valor específico é obtido. Inicialmente, esse número é 0, obviamente.
O algoritmo começa no canto inferior direito. Depois de "adivinhar" o primeiro número é um 0, precisamos acompanhar o fato de que o 0 não é mais permitido em nenhuma célula na mesma linha, coluna e diagonal:
LSB (example n=5)
↓
10000 00000 00000 00000 10000
00000 10000 00000 00000 10000
00000 00000 10000 00000 10000
00000 00000 00000 10000 10000
10000 10000 10000 10000 10000
↑
MSB
Para esse fim, calculamos os quatro valores a seguir:
Linha atual: precisamos de n bits a cada n- ésimo bit (um por célula) e, em seguida, alteramos para a linha atual r , lembrando que cada linha contém n ² bits:
Coluna atual: precisamos de n bits a cada n² -ésimo bit (um por linha) e, em seguida, mudamos para a coluna atual c , lembrando que cada coluna contém n bits:
Diagonal para a frente: precisamos de n bits a cada ... (você prestou atenção? Rápido, resolva isso!) ... n ( n +1) -simo bit (bom trabalho!), Mas apenas se estivermos realmente ligados a diagonal para a frente:
Diagonal para trás: duas coisas aqui. Primeiro, como sabemos se estamos na diagonal para trás? Matematicamente, a condição é c = ( n - 1) - r , que é a mesma que c = n + (- r - 1). Ei, isso lembra alguma coisa? É isso mesmo, é o complemento de dois, para que possamos usar a negação bit a bit (muito eficiente no Funciton) em vez do decremento. Segundo, a fórmula acima pressupõe que queremos que o bit menos significativo seja definido, mas na diagonal reversa não, então temos que alterá-lo ... você sabe? ... Isso mesmo, n ( n - 1).
Este também é o único onde potencialmente dividimos por 0 se n = 1. No entanto, o Funciton não se importa. 0 ÷ 0 é apenas 0, você não sabe?
No código, a função Җ
(a inferior) pega n e um índice (do qual calcula r e c por divisão e restante), calcula esses quatro valores e or
os reúne.
O algoritmo de força bruta
O algoritmo de força bruta é implementado por Ӂ
(a função na parte superior). Leva n (o tamanho da grade), índice (onde atualmente estamos colocando um número na grade) e obtido (o número com n ³ bits nos dizendo quais números ainda podemos colocar em cada célula).
Esta função retorna uma sequência de strings. Cada string é uma solução completa para a grade. É um solucionador completo; retornaria todas as soluções, se você permitir, mas as retornará como uma sequência avaliada preguiçosamente.
Se o índice atingiu 0, preenchemos com êxito a grade inteira, portanto retornamos uma sequência contendo a sequência vazia (uma solução única que não cobre nenhuma das células). A string vazia é 0
, e usamos a função de biblioteca ⌑
para transformá-la em uma sequência de elemento único.
A verificação descrita em melhoria de desempenho abaixo acontece aqui.
Se o índice ainda não atingiu 0, diminuímos em 1 para obter o índice no qual agora precisamos colocar um número (chame ix ).
Usamos ♫
para gerar uma sequência lenta contendo os valores de 0 a n - 1.
Em seguida, usamos ɓ
(ligação monádica) com um lambda que faz o seguinte na ordem:
- Primeiro, observe o bit relevante a ser tomado para decidir se o número é válido aqui ou não. Podemos colocar um número i se, e somente se, for obtido & (1 << ( n × ix ) << i ) ainda não está definido. Se estiver definido, retorne
0
(sequência vazia).
- Use
Җ
para calcular os bits correspondentes à linha, coluna e diagonal (s) atuais. Desloque-o por i e depois or
para tomado .
- Chame recursivamente
Ӂ
para recuperar todas as soluções para as células restantes, passando a nova tomada e a ix decrementada . Isso retorna uma sequência de cadeias incompletas; cada sequência possui caracteres ix (a grade é preenchida até o índice ix ).
- Use
ɱ
(mapa) para percorrer as soluções assim encontradas e use ‼
para concatenar i ao final de cada uma. Acrescente uma nova linha se o índice for múltiplo de n , caso contrário, um espaço.
Gerando o resultado
O programa principal chama Ӂ
(o forçador bruto) com n , índice = n ² (lembre-se de preencher a grade para trás) e tomado = 0 (inicialmente nada é tomado). Se o resultado for uma sequência vazia (nenhuma solução encontrada), imprima a sequência vazia. Caso contrário, imprima a primeira string na sequência. Observe que isso significa que ele avaliará apenas o primeiro elemento da sequência, e é por isso que o solucionador não continua até encontrar todas as soluções.
Melhoria de desempenho
(Para quem já leu a versão antiga da explicação: o programa não gera mais uma sequência de sequências que precisa ser transformada separadamente em uma sequência de saída; apenas gera uma sequência de sequências diretamente. Editei a explicação de acordo . Mas essa não foi a principal melhoria. Aí vem.)
Na minha máquina, o exe compilado da primeira versão demorou quase exatamente 1 hora para resolver n = 7. Isso não estava dentro do prazo especificado de 10 minutos, então não descansei. (Bem, na verdade, a razão pela qual eu não descansei foi porque eu tinha essa ideia de como acelerar bastante.)
O algoritmo, conforme descrito acima, interrompe sua pesquisa e retorna sempre que encontra uma célula na qual todos os bits do número obtido são definidos, indicando que nada pode ser colocado nessa célula.
No entanto, o algoritmo continuará a preencher futilmente a grade até a célula na qual todos esses bits estão definidos. Seria muito mais rápido se pudesse parar assim que qualquer célula ainda a ser preenchida já tiver todos os bits configurados, o que já indica que nunca poderemos resolver o restante da grade, independentemente dos números que inserimos isto. Mas como você verifica com eficiência se alguma célula possui seus n bits definidos sem passar por todos eles?
O truque começa adicionando um único bit por célula ao número obtido . Em vez do que foi mostrado acima, agora fica assim:
LSB (example n=5)
↓
10000 0 00000 0 00000 0 00000 0 10000 0
00000 0 10000 0 00000 0 00000 0 10000 0
00000 0 00000 0 10000 0 00000 0 10000 0
00000 0 00000 0 00000 0 10000 0 10000 0
10000 0 10000 0 10000 0 10000 0 10000 0
↑
MSB
Em vez de n ³, agora existem n ² ( n + 1) bits nesse número. A função que preenche a linha / coluna / diagonal atual foi alterada de acordo (na verdade, completamente reescrita para ser sincera). Porém, essa função ainda preencherá apenas n bits por célula; portanto, o bit extra que acabamos de adicionar sempre será 0
.
Agora, digamos que estamos no meio do cálculo, apenas colocamos um 1
na célula do meio e o número obtido é mais ou menos assim:
current
LSB column (example n=5)
↓ ↓
11111 0 10010 0 01101 0 11100 0 11101 0
00011 0 11110 0 01101 0 11101 0 11100 0
11111 0 11110 0[11101 0]11100 0 11100 0 ← current row
11111 0 11111 0 11111 0 11111 0 11111 0
11111 0 11111 0 11111 0 11111 0 11111 0
↑
MSB
Como você pode ver, a célula superior esquerda (índice 0) e a célula esquerda central (índice 10) agora são impossíveis. Como determinamos isso com mais eficiência?
Considere um número no qual o 0º bit de cada célula está definido, mas apenas até o índice atual. É fácil calcular esse número usando a fórmula familiar:
O que teríamos se adicionássemos esses dois números?
LSB LSB
↓ ↓
11111 0 10010 0 01101 0 11100 0 11101 0 10000 0 10000 0 10000 0 10000 0 10000 0 ╓───╖
00011 0 11110 0 01101 0 11101 0 11100 0 ║ 10000 0 10000 0 10000 0 10000 0 10000 0 ║
11111 0 11110 0 11101 0 11100 0 11100 0 ═══╬═══ 10000 0 10000 0 00000 0 00000 0 00000 0 ═════ ╓─╜
11111 0 11111 0 11111 0 11111 0 11111 0 ║ 00000 0 00000 0 00000 0 00000 0 00000 0 ═════ ╨
11111 0 11111 0 11111 0 11111 0 11111 0 00000 0 00000 0 00000 0 00000 0 00000 0 o
↑ ↑
MSB MSB
O resultado é:
OMG
↓
00000[1]01010 0 11101 0 00010 0 00011 0
10011 0 00001 0 11101 0 00011 0 00010 0
═════ 00000[1]00001 0 00011 0 11100 0 11100 0
═════ 11111 0 11111 0 11111 0 11111 0 11111 0
11111 0 11111 0 11111 0 11111 0 11111 0
Como você pode ver, a adição transborda para o bit extra que adicionamos, mas apenas se todos os bits para essa célula estiverem definidos! Portanto, tudo o que resta a fazer é mascarar esses bits (mesma fórmula acima, mas << n ) e verificar se o resultado é 0:
00000[1]01010 0 11101 0 00010 0 00011 0 ╓╖ 00000 1 00000 1 00000 1 00000 1 00000 1 ╓─╖ ╓───╖
10011 0 00001 0 11101 0 00011 0 00010 0 ╓╜╙╖ 00000 1 00000 1 00000 1 00000 1 00000 1 ╓╜ ╙╖ ║
00000[1]00001 0 00011 0 11100 0 11100 0 ╙╥╥╜ 00000 1 00000 1 00000 0 00000 0 00000 0 ═════ ║ ║ ╓─╜
11111 0 11111 0 11111 0 11111 0 11111 0 ╓╜╙╥╜ 00000 0 00000 0 00000 0 00000 0 00000 0 ═════ ╙╖ ╓╜ ╨
11111 0 11111 0 11111 0 11111 0 11111 0 ╙──╨─ 00000 0 00000 0 00000 0 00000 0 00000 0 ╙─╜ o
Se não for zero, a grade é impossível e podemos parar.