C, 2765 (ottimale)
modificare
Ora tutto in un unico file C. Questo trova solo tutte le soluzioni ottimali. Tutti devono avere 6 parole di 15 lettere e una di 10 lettere composta da 8 lettere di valore 1 e due spazi vuoti. Per questo ho solo bisogno di caricare una frazione del dizionario e non devo cercare parole di 15 lettere con spazi vuoti. Il codice è una semplice ricerca approfondita in profondità.
#include <stdio.h>
#include <stdint.h>
#include <string.h>
struct w {
struct lc { uint64_t hi,lo; } lc;
char w[16];
} w15[6000], w10[40000];
int n15,n10;
struct lc pool = { 0x12122464612, 0x8624119232c4229 };
int pts[27] = {0,1,3,3,2,1,4,2,4,1,8,5,1,3,1,1,3,10,1,1,1,1,4,4,8,4,10};
int f[27],fs[26], w15c[27],w15l[27][6000];
int count(struct lc a, int l) { return (l < 16 ? a.lo << 4 : a.hi) >> 4*(l&15) & 15; }
int matches_val(uint64_t a, uint64_t b) {
uint64_t mask = 0x1111111111111111ll;
return !((a - b ^ a ^ b) & mask);
}
int matches(struct lc all, struct lc a) { return matches_val(all.hi,a.hi) && matches_val(all.lo,a.lo); }
int picks[10];
void try(struct lc cur, int used, int level) {
int c, i, must;
if (level == 6) {
for (i = 0; i<27; i++) if (count(cur, i) && pts[i]>1) return;
for (i = 0; i < n10; i++) if(!(used & (1 << (w10[i].w[0] & 31))) && matches(w10[i].lc, cur)) {
for (c = 0; c<level; c++) printf("%s ",w15[picks[c]].w);
printf("%s\n",w10[i].w);
}
return;
}
for (i = 0; i < 26;i++) if (count(cur,fs[i])) break;
must = fs[i];
for (c = 0; c < w15c[must]; c++) { i = w15l[must][c]; if(!(used & (1 << (w15[i].w[0] & 31))) && matches(cur, w15[i].lc)) {
struct lc b = { cur.hi - w15[i].lc.hi, cur.lo - w15[i].lc.lo };
picks[level] = i;
try(b, used + (1 << (w15[i].w[0] & 31)), level+1);
}}
}
int cmpfs(int *a, int *b){return f[*a]-f[*b];}
void ins(struct w*w, char *s, int c) {
int i;
strcpy(w->w,s);
for (;*s;s++)
if (*s&16) w->lc.hi += 1ll << 4*(*s&15); else w->lc.lo += 1ll << 4*(*s&15) - 4;
if (c) for (i = 0; i < 27;i++) if (count(w->lc,i)) f[i]++, w15l[i][w15c[i]++] = w-w15;
}
int main() {
int i;
char s[20];
while(scanf("%s ",s)>0) {
if (strlen(s) == 15) ins(w15 + n15++,s,1);
if (strlen(s) == 10) ins(w10 + n10++,s,0);
}
for (i = 0; i < 26;i++) fs[i] = i+1;
qsort(fs, 26, sizeof(int), cmpfs);
try(pool, 0, 0);
}
Uso:
$time ./scrab <sowpods.txt
cc -O3 scrab.c -o scrab
JUXTAPOSITIONAL DEMISEMIQUAVERS ACKNOWLEDGEABLY WEATHERPROOFING CONVEYORIZATION FEATHERBEDDINGS LAURUSTINE
JUXTAPOSITIONAL DEMISEMIQUAVERS ACKNOWLEDGEABLY WEATHERPROOFING CONVEYORIZATION FEATHERBEDDINGS LUXURIATED
JUXTAPOSITIONAL DEMISEMIQUAVERS ACKNOWLEDGEABLY WEATHERPROOFING CONVEYORIZATION FEATHERBEDDINGS LUXURIATES
JUXTAPOSITIONAL DEMISEMIQUAVERS ACKNOWLEDGEABLY WEATHERPROOFING CONVEYORIZATION FEATHERBEDDINGS ULTRAQUIET
JUXTAPOSITIONAL DEMISEMIQUAVERS ACKNOWLEDGEABLY WEATHERPROOFING CONVEYORIZATION FEATHERBEDDINGS UTRICULATE
JUXTAPOSITIONAL DEMISEMIQUAVERS WEATHERPROOFING ACKNOWLEDGEABLY CONVEYORIZATION FEATHERBEDDINGS LAURUSTINE
JUXTAPOSITIONAL DEMISEMIQUAVERS WEATHERPROOFING ACKNOWLEDGEABLY CONVEYORIZATION FEATHERBEDDINGS LUXURIATED
JUXTAPOSITIONAL DEMISEMIQUAVERS WEATHERPROOFING ACKNOWLEDGEABLY CONVEYORIZATION FEATHERBEDDINGS LUXURIATES
JUXTAPOSITIONAL DEMISEMIQUAVERS WEATHERPROOFING ACKNOWLEDGEABLY CONVEYORIZATION FEATHERBEDDINGS ULTRAQUIET
JUXTAPOSITIONAL DEMISEMIQUAVERS WEATHERPROOFING ACKNOWLEDGEABLY CONVEYORIZATION FEATHERBEDDINGS UTRICULATE
OVERADJUSTMENTS QUODLIBETARIANS ACKNOWLEDGEABLY WEATHERPROOFING EXEMPLIFICATIVE HYDROGENIZATION RUBIACEOUS
OVERADJUSTMENTS QUODLIBETARIANS WEATHERPROOFING ACKNOWLEDGEABLY EXEMPLIFICATIVE HYDROGENIZATION RUBIACEOUS
real 0m1.754s
user 0m1.753s
sys 0m0.000s
Nota che ogni soluzione viene stampata due volte perché quando si aggiunge una parola "W" di 15 lettere vengono creati 2 ordini perché sono presenti 2 riquadri "W".
La prima soluzione trovata con la suddivisione del punto:
JUXTAPOSITIONAL 465
DEMISEMIQUAVERS 480
ACKNOWLEDGEABLY 465
WEATHERPROOFING 405
CONVEYORIZATION 480
FEATHERBEDDINGS 390
LAURUSTINE (LAURU?TI?E) 80
no tiles left
Modifica: spiegazione
Cosa rende possibile la ricerca dell'intero spazio? Durante l'aggiunta di una nuova parola prendo in considerazione solo le parole che hanno la lettera rimanente più rara. Questa lettera deve essere comunque in qualche parola (e una parola di 15 lettere poiché questa sarà una lettera senza valore 1, anche se non lo controllo). Quindi comincio con le parole contenenti J, Q, W, W, X, Z
quali contano in giro 50, 100, 100, 100, 200, 500
. Ai livelli più bassi ottengo più cutoff perché alcune parole sono eliminate dalla mancanza di lettere. Larghezza dell'albero di ricerca ad ogni livello:
0: 1
1: 49
2: 3046
3: 102560
4: 724040
5: 803959
6: 3469
Naturalmente si ottiene molto taglio non controllando soluzioni non ottimali (spazi vuoti in parole di 15 lettere o parole più brevi). Quindi è fortunato che la soluzione 2765 possa essere raggiunta con questo dizionario (ma era vicino, solo 2 combinazioni di parole di 15 lettere forniscono un avanzo ragionevole). D'altra parte è facile modificare il codice per trovare combinazioni di punteggio più basse in cui non tutte le 10 lettere rimanenti hanno un valore 1, anche se sarebbe più difficile dimostrare che questa sarebbe una soluzione ottimale.
Anche il codice mostra il classico caso di ottimizzazione prematura. Questa versione della matches
funzione rende il codice solo più lento del 30%:
int matches(struct lc all, struct lc a) {
int i;
for (i = 1; i < 27; i++) if (count(a, i) > count(all, i)) return 0;
return 1;
}
Ho anche capito come rendere il confronto parallelo bit magic ancora più breve rispetto al mio codice originale (in questo caso non è possibile utilizzare lo stuzzichino più alto, ma questo non è un problema, poiché ho bisogno solo di 26 stuzzichini su 32):
int matches_val(uint64_t a, uint64_t b) {
uint64_t mask = 0x1111111111111111ll;
return !((a - b ^ a ^ b) & mask);
}
Ma non dà alcun vantaggio.
modificare
Scrivendo la spiegazione sopra ho realizzato che la maggior parte del tempo è dedicato alla scansione dell'elenco di parole per coloro che contengono una lettera specifica non presente nella matches
funzione. Il calcolo delle liste in anticipo ha dato uno speedup di 10 volte.