Funciton , non competitivo
AGGIORNARE! Massiccio miglioramento delle prestazioni! n = 7 ora completa in meno di 10 minuti! Vedi spiegazione in fondo!
È stato molto divertente scrivere. Questo è un risolutore di forza bruta per questo problema scritto a Funciton. Alcuni factoidi:
- Accetta un numero intero su STDIN. Qualsiasi spazio bianco estraneo lo interrompe, inclusa una nuova riga dopo l'intero.
- Usa i numeri da 0 a n - 1 (non da 1 a n ).
- Riempie la griglia "all'indietro", in modo da ottenere una soluzione in cui legge la riga inferiore
3 2 1 0
anziché in quella della riga superiore 0 1 2 3
.
- Emette correttamente
0
(l'unica soluzione) per n = 1.
- Uscita vuota per n = 2 e n = 3.
- Quando compilato per un exe, richiede circa 8¼ minuti per n = 7 (era circa un'ora prima del miglioramento delle prestazioni). Senza compilare (usando l'interprete) ci vuole circa 1,5 volte di più, quindi vale la pena usare il compilatore.
- Come traguardo personale, questa è la prima volta che scrivo un intero programma Funciton senza prima scrivere il programma in un linguaggio pseudocodico. L'ho scritto prima in C # vero però.
- (Tuttavia, questa non è la prima volta che ho apportato una modifica per migliorare in modo massiccio le prestazioni di qualcosa a Funciton. La prima volta che l'ho fatto è stata nella funzione fattoriale. Lo scambio dell'ordine degli operandi della moltiplicazione ha fatto una differenza enorme a causa di come funziona l'algoritmo di moltiplicazione . Nel caso fossi curioso.)
Senza ulteriori indugi:
┌────────────────────────────────────┐ ┌─────────────────┐
│ ┌─┴─╖ ╓───╖ ┌─┴─╖ ┌──────┐ │
│ ┌─────────────┤ · ╟─╢ Ӂ ╟─┤ · ╟───┤ │ │
│ │ ╘═╤═╝ ╙─┬─╜ ╘═╤═╝ ┌─┴─╖ │ │
│ │ └─────┴─────┘ │ ♯ ║ │ │
│ ┌─┴─╖ ╘═╤═╝ │ │
│ ┌────────────┤ · ╟───────────────────────────────┴───┐ │ │
┌─┴─╖ ┌─┴─╖ ┌────╖ ╘═╤═╝ ┌──────────┐ ┌────────┐ ┌─┴─╖│ │
│ ♭ ║ │ × ╟───┤ >> ╟───┴───┘ ┌─┴─╖ │ ┌────╖ └─┤ · ╟┴┐ │
╘═╤═╝ ╘═╤═╝ ╘══╤═╝ ┌─────┤ · ╟───────┴─┤ << ╟─┐ ╘═╤═╝ │ │
┌───────┴─────┘ ┌────╖ │ │ ╘═╤═╝ ╘══╤═╝ │ │ │ │
│ ┌─────────┤ >> ╟─┘ │ └───────┐ │ │ │ │ │
│ │ ╘══╤═╝ ┌─┴─╖ ╔═══╗ ┌─┴─╖ ┌┐ │ │ ┌─┴─╖ │ │
│ │ ┌┴┐ ┌───────┤ ♫ ║ ┌─╢ 0 ║ ┌─┤ · ╟─┤├─┤ ├─┤ Ӝ ║ │ │
│ │ ╔═══╗ └┬┘ │ ╘═╤═╝ │ ╚═╤═╝ │ ╘═╤═╝ └┘ │ │ ╘═╤═╝ │ │
│ │ ║ 1 ╟───┬┘ ┌─┴─╖ └───┘ ┌─┴─╖ │ │ │ │ │ ┌─┴─╖ │
│ │ ╚═══╝ ┌─┴─╖ │ ɓ ╟─────────────┤ ? ╟─┘ │ ┌─┴─╖ │ ├─┤ · ╟─┴─┐
│ ├─────────┤ · ╟─┐ ╘═╤═╝ ╘═╤═╝ ┌─┴────┤ + ╟─┘ │ ╘═╤═╝ │
┌─┴─╖ ┌─┴─╖ ╘═╤═╝ │ ╔═╧═╕ ╔═══╗ ┌───╖ ┌─┴─╖ ┌─┴─╖ ╘═══╝ │ │ │
┌─┤ · ╟─┤ · ╟───┐ └┐ └─╢ ├─╢ 0 ╟─┤ ⌑ ╟─┤ ? ╟─┤ · ╟──────────────┘ │ │
│ ╘═╤═╝ ╘═╤═╝ └───┐ ┌┴┐ ╚═╤═╛ ╚═╤═╝ ╘═══╝ ╘═╤═╝ ╘═╤═╝ │ │
│ │ ┌─┴─╖ ┌───╖ │ └┬┘ ┌─┴─╖ ┌─┘ │ │ │ │
│ ┌─┴───┤ · ╟─┤ Җ ╟─┘ └────┤ ? ╟─┴─┐ ┌─────────────┘ │ │
│ │ ╘═╤═╝ ╘═╤═╝ ╘═╤═╝ │ │╔════╗╔════╗ │ │
│ │ │ ┌──┴─╖ ┌┐ ┌┐ ┌─┴─╖ ┌─┴─╖ │║ 10 ║║ 32 ║ ┌─────────────────┘ │
│ │ │ │ << ╟─┤├─┬─┤├─┤ · ╟─┤ · ╟─┘╚══╤═╝╚╤═══╝ ┌──┴──┐ │
│ │ │ ╘══╤═╝ └┘ │ └┘ ╘═╤═╝ ╘═╤═╝ │ ┌─┴─╖ ┌─┴─╖ ┌─┴─╖ │
│ │ ┌─┴─╖ ┌─┴─╖ ┌─┴─╖ ┌─┴─╖ ╔═╧═╕ └─┤ ? ╟─┤ · ╟─┤ % ║ │
│ └─────┤ · ╟─┤ · ╟──┤ Ӂ ╟──┤ ɱ ╟─╢ ├───┐ ╘═╤═╝ ╘═╤═╝ ╘═╤═╝ │
│ ╘═╤═╝ ╘═╤═╝ ╘═╤═╝ ╘═══╝ ╚═╤═╛ ┌─┴─╖ ┌─┴─╖ │ └────────────────────┘
│ └─────┤ │ └───┤ ‼ ╟─┤ ‼ ║ │ ┌──────┐
│ │ │ ╘═══╝ ╘═╤═╝ │ │ ┌────┴────╖
│ │ │ ┌─┴─╖ │ │ │ str→int ║
│ │ └──────────────────────┤ · ╟───┴─┐ │ ╘════╤════╝
│ │ ┌─────────╖ ╘═╤═╝ │ ╔═╧═╗ ┌──┴──┐
│ └──────────┤ int→str ╟──────────┘ │ ║ ║ │ ┌───┴───┐
│ ╘═════════╝ │ ╚═══╝ │ │ ┌───╖ │
└───────────────────────────────────────────────────────┘ │ └─┤ × ╟─┘
┌──────────────┐ ╔═══╗ │ ╘═╤═╝
╔════╗ │ ╓───╖ ┌───╖ │ ┌───╢ 0 ║ │ ┌─┴─╖ ╔═══╗
║ −1 ║ └─╢ Ӝ ╟─┤ × ╟──┴──────┐ │ ╚═╤═╝ └───┤ Ӂ ╟─╢ 0 ║
╚═╤══╝ ╙───╜ ╘═╤═╝ │ │ ┌─┴─╖ ╘═╤═╝ ╚═══╝
┌─┴──╖ ┌┐ ┌───╖ ┌┐ ┌─┴──╖ ╔════╗ │ │ ┌─┤ ╟───────┴───────┐
│ << ╟─┤├─┤ ÷ ╟─┤├─┤ << ║ ║ −1 ║ │ │ │ └─┬─╜ ┌─┐ ┌─────┐ │
╘═╤══╝ └┘ ╘═╤═╝ └┘ ╘═╤══╝ ╚═╤══╝ │ │ │ └───┴─┘ │ ┌─┴─╖ │
│ └─┘ └──────┘ │ │ └───────────┘ ┌─┤ ? ╟─┘
└──────────────────────────────┘ ╓───╖ └───────────────┘ ╘═╤═╝
┌───────────╢ Җ ╟────────────┐ │
┌────────────────────────┴───┐ ╙───╜ │
│ ┌─┴────────────────────┐ ┌─┴─╖
┌─┴─╖ ┌─┴─╖ ┌─┴─┤ · ╟──────────────────┐
│ ♯ ║ ┌────────────────────┤ · ╟───────┐ │ ╘═╤═╝ │
╘═╤═╝ │ ╘═╤═╝ │ │ │ ┌───╖ │
┌─────┴───┘ ┌─────────────────┴─┐ ┌───┴───┐ ┌─┴─╖ ┌─┴─╖ ┌─┤ × ╟─┴─┐
│ │ ┌─┴─╖ │ ┌───┴────┤ · ╟─┤ · ╟──────────┤ ╘═╤═╝ │
│ │ ┌───╖ ┌───╖ ┌──┤ · ╟─┘ ┌─┴─┐ ╘═╤═╝ ╘═╤═╝ ┌─┴─╖ │ │
│ ┌────┴─┤ ♭ ╟─┤ × ╟──┘ ╘═╤═╝ │ ┌─┴─╖ ┌───╖└┐ ┌──┴─╖ ┌─┤ · ╟─┘ │
│ │ ╘═══╝ ╘═╤═╝ ┌───╖ │ │ │ × ╟─┤ Ӝ ╟─┴─┤ ÷% ╟─┐ │ ╘═╤═╝ ┌───╖ │
│ ┌─────┴───┐ ┌────┴───┤ Ӝ ╟─┴─┐ │ ╘═╤═╝ ╘═╤═╝ ╘══╤═╝ │ │ └───┤ Ӝ ╟─┘
│ ┌─┴─╖ ┌───╖ │ │ ┌────╖ ╘═╤═╝ │ └───┘ ┌─┴─╖ │ │ └────┐ ╘═╤═╝
│ │ × ╟─┤ Ӝ ╟─┘ └─┤ << ╟───┘ ┌─┴─╖ ┌───────┤ · ╟───┐ │ ┌─┴─╖ ┌───╖ │ │
│ ╘═╤═╝ ╘═╤═╝ ╘══╤═╝ ┌───┤ + ║ │ ╘═╤═╝ ├──┴─┤ · ╟─┤ × ╟─┘ │
└───┤ └────┐ ╔═══╗ ┌─┴─╖ ┌─┴─╖ ╘═╤═╝ │ ╔═══╗ ┌─┴─╖ ┌─┴─╖ ╘═╤═╝ ╘═╤═╝ │
┌─┴─╖ ┌────╖ │ ║ 0 ╟─┤ ? ╟─┤ = ║ ┌┴┐ │ ║ 0 ╟─┤ ? ╟─┤ = ║ │ │ ┌────╖ │
│ × ╟─┤ << ╟─┘ ╚═══╝ ╘═╤═╝ ╘═╤═╝ └┬┘ │ ╚═══╝ ╘═╤═╝ ╘═╤═╝ │ └─┤ << ╟─┘
╘═╤═╝ ╘═╤══╝ ┌┐ ┌┐ │ │ └───┘ ┌─┴─╖ ├──────┘ ╘═╤══╝
│ └────┤├──┬──┤├─┘ ├─────────────────┤ · ╟───┘ │
│ └┘┌─┴─╖└┘ │ ┌┐ ┌┐ ╘═╤═╝ ┌┐ ┌┐ │
└────────────┤ · ╟─────────┘ ┌─┤├─┬─┤├─┐ └───┤├─┬─┤├────────────┘
╘═╤═╝ │ └┘ │ └┘ │ └┘ │ └┘
└───────────────┘ │ └────────────┘
Spiegazione della prima versione
La prima versione ha impiegato circa un'ora per risolvere n = 7. Quanto segue spiega principalmente come ha funzionato questa versione lenta. In fondo spiegherò quale modifica ho apportato per farlo arrivare a meno di 10 minuti.
Un'escursione in pezzi
Questo programma ha bisogno di bit. Ha bisogno di molti bit e ne ha bisogno nei posti giusti. I programmatori esperti di Funciton sanno già che se hai bisogno di n bit, puoi usare la formula
che a Funciton può essere espresso come
Durante l'ottimizzazione delle prestazioni, mi è venuto in mente che posso calcolare lo stesso valore molto più velocemente utilizzando questa formula:
Spero che mi perdonerai che non ho aggiornato di conseguenza tutta la grafica delle equazioni in questo post.
Ora, supponiamo che tu non voglia un blocco contiguo di bit; in effetti, vuoi n bit a intervalli regolari ogni k -esimo bit, in questo modo:
LSB
↓
00000010000001000000100000010000001
└──┬──┘
k
La formula per questo è abbastanza semplice una volta che la conosci:
Nel codice, la funzione Ӝ
assume valori n e k e calcola questa formula.
Tenere traccia dei numeri usati
Ci sono n ² numeri nella griglia finale e ogni numero può essere uno qualsiasi di n valori possibili. Al fine di tenere traccia di quali numeri sono ammessi in ciascuna cella, manteniamo un numero costituito da n ³ bit, in cui un bit è impostato per indicare che viene preso un determinato valore. Inizialmente questo numero è 0, ovviamente.
L'algoritmo inizia nell'angolo in basso a destra. Dopo aver "indovinato" il primo numero è uno 0, dobbiamo tenere traccia del fatto che lo 0 non è più consentito in nessuna cella lungo la stessa riga, colonna e diagonale:
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
A tal fine, calcoliamo i seguenti quattro valori:
Riga corrente: abbiamo bisogno di n bit ogni n -esimo bit (uno per cella), quindi spostarlo sulla riga corrente r , ricordando che ogni riga contiene n ² bit:
Colonna corrente: abbiamo bisogno di n bit ogni n ²-esimo (uno per riga), quindi spostarlo sulla colonna corrente c , ricordando che ogni colonna contiene n bit:
Diagonale in avanti: abbiamo bisogno di n bit ogni ... (hai prestato attenzione? Veloce, capisci!) ... n ( n +1) -th bit (ben fatto!), Ma solo se siamo effettivamente attivi la diagonale in avanti:
Diagonale all'indietro: due cose qui. Innanzitutto, come facciamo a sapere se siamo sulla diagonale all'indietro? Matematicamente, la condizione è c = ( n - 1) - r , che è la stessa di c = n + (- r - 1). Ehi, ti ricorda qualcosa? Esatto, è un complemento a due, quindi possiamo usare la negazione bit per bit (molto efficiente a Funciton) invece del decremento. In secondo luogo, la formula sopra presuppone che vogliamo impostare il bit meno significativo, ma nella diagonale all'indietro non lo facciamo, quindi dobbiamo spostarlo verso l'alto di ... lo sai? ... Esatto, n ( n - 1).
Questo è anche l'unico in cui potenzialmente dividiamo per 0 se n = 1. Tuttavia, a Funciton non importa. 0 ÷ 0 è solo 0, non lo sai?
Nel codice, la funzione Җ
(quella in basso) prende n e un indice (da cui calcola r per c per divisione e resto), calcola questi quattro valori e or
li mette insieme.
L'algoritmo a forza bruta
L'algoritmo a forza bruta è implementato da Ӂ
(la funzione in alto). Prende n (la dimensione della griglia), indice (dove nella griglia stiamo attualmente posizionando un numero) e preso (il numero con n ³ bit che ci dice quali numeri possiamo ancora posizionare in ogni cella).
Questa funzione restituisce una sequenza di stringhe. Ogni stringa è una soluzione completa alla griglia. È un risolutore completo; restituirebbe tutte le soluzioni se lo lasciate, ma le restituirà come una sequenza valutata in modo pigro.
Se l' indice ha raggiunto 0, abbiamo riempito con successo l'intera griglia, quindi restituiamo una sequenza contenente la stringa vuota (un'unica soluzione che non copre nessuna delle celle). La stringa vuota è 0
e usiamo la funzione libreria ⌑
per trasformarla in una sequenza a elemento singolo.
Il controllo descritto sotto il miglioramento delle prestazioni di seguito avviene qui.
Se l' indice non ha ancora raggiunto 0, lo diminuiamo di 1 per ottenere l'indice in cui ora è necessario posizionare un numero (chiamare quel ix ).
Usiamo ♫
per generare una sequenza pigra contenente i valori da 0 a n - 1.
Quindi usiamo ɓ
(monadic bind) con un lambda che esegue le seguenti operazioni:
- Prima guarda il bit rilevante preso per decidere se il numero è valido qui o no. Possiamo inserire un numero i se e solo se preso e (1 << ( n × ix ) << i ) non è già impostato. Se impostato, torna
0
(sequenza vuota).
- Utilizzare
Җ
per calcolare i bit corrispondenti alla riga, colonna e diagonale corrente. Sposta da i e poi or
su preso .
- Chiamata ricorsiva
Ӂ
per recuperare tutte le soluzioni per le celle rimanenti, passandole la nuova presa e la ix decrementata . Ciò restituisce una sequenza di stringhe incomplete; ogni stringa ha caratteri ix (la griglia riempita fino all'indice ix ).
- Utilizzare
ɱ
(mappa) per esaminare le soluzioni così trovate e utilizzare ‼
per concatenare i alla fine di ciascuna. Aggiungi una nuova riga se l' indice è un multiplo di n , altrimenti uno spazio.
Generare il risultato
Il programma principale chiama Ӂ
(il bruto forcer) con n , indice = n ² (ricorda che riempiamo la griglia all'indietro) e preso = 0 (inizialmente non viene preso nulla). Se il risultato è una sequenza vuota (nessuna soluzione trovata), genera la stringa vuota. Altrimenti, genera la prima stringa nella sequenza. Si noti che ciò significa che valuterà solo il primo elemento della sequenza, motivo per cui il solutore non continua fino a quando non ha trovato tutte le soluzioni.
Miglioramento delle prestazioni
(Per coloro che hanno già letto la vecchia versione della spiegazione: il programma non genera più una sequenza di sequenze che deve essere trasformata separatamente in una stringa per l'output; genera semplicemente una sequenza di stringhe direttamente. Ho modificato la spiegazione di conseguenza Ma questo non è stato il principale miglioramento. Ecco che arriva.)
Sulla mia macchina, l'exe compilato della prima versione ha impiegato praticamente esattamente 1 ora per risolvere n = 7. Questo non era entro il limite di tempo di 10 minuti, quindi non mi sono riposato. (Beh, in realtà, il motivo per cui non mi sono riposato è perché avevo questa idea su come accelerare massicciamente.)
L'algoritmo come descritto sopra interrompe la sua ricerca e torna indietro ogni volta che incontra una cella in cui sono impostati tutti i bit nel numero preso , indicando che nulla può essere inserito in questa cella.
Tuttavia, l'algoritmo continuerà a riempire inutilmente la griglia fino alla cella in cui sono impostati tutti quei bit. Sarebbe molto più veloce se potesse fermarsi non appena una cella ancora da compilare ha già tutti i bit impostati, il che indica già che non possiamo mai risolvere il resto della griglia, indipendentemente dai numeri che abbiamo inserito esso. Ma come si fa a verificare in modo efficiente se una cella ha i suoi n bit impostati senza esaminarli tutti?
Il trucco inizia aggiungendo un singolo bit per cella al numero preso . Invece di quello che è stato mostrato sopra, ora assomiglia a questo:
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
Invece di n ³, ora ci sono n ² ( n + 1) bit in questo numero. La funzione che popola l'attuale riga / colonna / diagonale è stata modificata di conseguenza (in realtà, completamente riscritta per essere onesti). Quella funzione popolerà comunque solo n bit per cella, quindi il bit extra che abbiamo appena aggiunto sarà sempre 0
.
Ora, supponiamo di essere a metà del calcolo, abbiamo appena inserito una 1
nella cella centrale e il numero preso assomiglia a questo:
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
Come puoi vedere, la cella in alto a sinistra (indice 0) e la cella in mezzo a sinistra (indice 10) sono ora impossibili. Come lo determiniamo nel modo più efficiente?
Considera un numero in cui è impostato il 0 ° bit di ogni cella, ma solo fino all'indice corrente. Tale numero è facile da calcolare usando la formula familiare:
Cosa otterremmo se sommassimo questi due numeri insieme?
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
Il risultato è:
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
Come puoi vedere, l'aggiunta trabocca nel bit extra che abbiamo aggiunto, ma solo se tutti i bit per quella cella sono impostati! Pertanto, tutto ciò che resta da fare è mascherare quei bit (stessa formula di cui sopra, ma << n ) e verificare se il risultato è 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 non è zero, la griglia è impossibile e possiamo fermarci.
- Schermata che mostra la soluzione e il tempo di esecuzione per n = da 4 a 7.