Trova le parole minime che useranno tutte le lettere date

2

Con una lista di migliaia di parole e un piccolo elenco di lettere sto cercando di trovare il minor numero di parole per fare uso di tutte le lettere date, supponendo che il mio dizionario di parole copra tutte le lettere.

Il primo passo è ovviamente quello di rimuovere tutte le parole senza nessuna delle lettere. Poi mi sono avvicinato a questo calcolando la rarità relativa di ogni lettera per le parole rimanenti e ordinando le parole per combinazione della rarità relativa delle lettere che contengono in relazione alla loro lunghezza. Quindi questo metterebbe prima le parole con lettere relativamente rare, supponendo che sarà sempre più facile soddisfare le lettere rimanenti, più comuni. Dopo aver selezionato la parola migliore con la maggior "copertura" di lettere, rimuovo quelli dall'elenco di lettere, quindi esegui il ciclo per ricalcolare le parole rarità per le lettere rimanenti fino a quando tutte le lettere sono soddisfatte.

Certamente funziona, ma a volte ottengo risultati strani che mostrano come questo non sia l'ideale. Ad esempio, un requisito di 5 lettere trova ancora una corrispondenza migliore per tutte le lettere, ma aggiungendo una sesta lettera, all'improvviso ottengo 4 parole restituite, a causa della sequenza di parole restituite e la rarità si sposta in modo sfavorevole.

Quale strategia potrei usare per migliorare questo algoritmo? Inoltre attualmente eseguo l'iterazione su tutte le parole più volte per ciclo, contando la rarità ecc., Che diventa sempre più costoso con un vasto dizionario. Qualsiasi suggerimento benvenuto:)

    
posta kontur 01.02.2018 - 18:41
fonte

3 risposte

3

Sebbene possa esserci una soluzione più ideale là fuori, penso che ti avvicinerà. In questo momento il tuo algoritmo classifica le parole che hanno lettere "rare" più importanti della copertura generale per la lista di lettere. Probabilmente il modo migliore per ridurre il numero di parole che utilizzi sarà quello di scegliere parole che contengono la maggior parte delle lettere che ti servono ancora.

Ad esempio, supponi di cercare il minor numero di parole che, insieme, contenga le seguenti lettere:

Letters
--------
a
b
c
d
e
f

Words
--------
ace
bread
fork
whistle
crab
monkey
car
fork
dog
diamond

Un modo semplice per capire quanto sia utile ogni parola sarebbe guardare ogni parola e calcolare quale delle lettere desiderate contiene. Possiamo farlo usando una serie di flag booleani per ogni parola.

Words    | a | b | c | d | e | f
---------------------------------
ace      | T | F | T | F | T | F
bread    | T | T | F | T | T | F
fear     | T | F | F | F | T | T
whistle  | F | F | F | F | T | F
crab     | T | T | T | F | F | F
monkey   | F | F | F | F | T | F
car      | T | F | T | F | F | F
fork     | F | F | F | F | F | T
dog      | F | F | F | T | F | F
diamond  | T | F | F | T | F | F
coffee   | F | F | T | F | T | T
---------------------------------
Total    | 6 | 2 | 4 | 3 | 6 | 3

Da quando abbiamo appena iniziato, abbiamo bisogno di tutto. Quindi lascia solo dare ad ogni parola un punteggio in base a quante lettere ha che abbiamo ancora bisogno.

Used Words: 
Letters Remaining: a, b, c, d, e, f

Words    | a | b | c | d | e | f | Score
-----------------------------------------
ace      | T | F | T | F | T | F | 3
bread    | T | T | F | T | T | F | 4
fear     | T | F | F | F | T | T | 3
whistle  | F | F | F | F | T | F | 1
crab     | T | T | T | F | F | F | 3
monkey   | F | F | F | F | T | F | 1
car      | T | F | T | F | F | F | 2
fork     | F | F | F | F | F | T | 1
dog      | F | F | F | T | F | F | 1
diamond  | T | F | F | T | F | F | 2
coffee   | F | F | T | F | T | T | 3

Quindi prendiamo quello che mette fuori più lettere. In questo caso "pane". Il che significa che le uniche lettere che abbiamo lasciato da usare sono "c, f". Diamo ad ogni parola un nuovo punteggio basato sulle lettere che ci servono.

Used Words: bread
Letters Remaining: c, f

Words    | a | b | c | d | e | f | Score | Score 2 
--------------------------------------------------
ace      | T | F | T | F | T | F | 3     | 1
bread    | T | T | F | T | T | F | 4     | -
fear     | T | F | F | F | T | T | 1     | 1
whistle  | F | F | F | F | T | F | 1     | 1
crab     | T | T | T | F | F | F | 3     | 0
monkey   | F | F | F | F | T | F | 1     | 1
car      | T | F | T | F | F | F | 2     | 1
fork     | F | F | F | F | F | T | 1     | 1
dog      | F | F | F | T | F | F | 1     | 0
diamond  | T | F | F | T | F | F | 2     | 0
coffee   | F | F | T | F | T | T | 3     | 2

Ora possiamo prendere "caffè" che ha un punteggio di 2 e che utilizza tutte le lettere che volevamo usare. Ovviamente questo ha alcuni difetti. In primo luogo, le tue scelte successive dipendono da quale sia stata la tua prima scelta. Se hai scelto una parola che ha ottenuto molte lettere comuni ma non ha colpito quelle più rare, probabilmente finirai con una parola che colpisce molte lettere e un gruppo che ne riceve solo una o due. Questo potrebbe essere risolto pesando i punteggi in modo che le lettere più rare valgano di più (un po 'come giocare a Scrabble).

Una cosa che potresti aver notato è dover ricalcolare il punteggio su ogni passaggio. E sarà un dolore. Ma cosa succede se abbiamo usato alcuni trucchi intelligenti per renderlo più facile? E se invece di una serie di booleani per ogni parola, l'abbiamo trasformata in una sequenza di bit? (La stessa cosa, ma è più facile lavorare con questo.)

Quindi, se abbiamo riscritto la tabella dove come bit, potrebbe apparire come questa:

Words    | Bits (a|b|c|d|e|f)
---------------------------------
ace      | 101010
bread    | 110110
fear     | 100011
whistle  | 000010
crab     | 111000
monkey   | 000010
car      | 101000
fork     | 000001
dog      | 000100
diamond  | 100100
coffee   | 001011

Il calcolo del punteggio diventa ora un semplice XOR con una maschera per le lettere che ti servono (all'inizio, 111111 poi successivamente 001001 ), quindi contati solo i bit reali. Ricalcolo piuttosto semplice.

Se vuoi pesare l'algoritmo, diventa un po 'più facile da eseguire ma più difficile da configurare. Se hai preso il conteggio di ogni lettera per trovare quelli più rari (come ho fatto nella prima parte), potresti disporre che la maschera di bit non sia più in ordine alfabetico ma in ordine di rarità (la cosa più rara è la prima). In questo caso la tua maschera di bit sembrerebbe (b|d|f|c|a|e) . Quindi i confronti del punteggio sono solo ordinati dal numero intero più alto dopo l'applicazione della maschera. (Nota: si incontrano alcuni problemi quando due lettere sono legate per rarità. Devi solo essere consapevole che può distorcere i tuoi risultati. Speriamo che non ci siano molti casi di questo in elenchi di parole più grandi.)

Questo non è perfetto, ma è un buon inizio.

    
risposta data 01.02.2018 - 19:37
fonte
4

Se elimini tutte le lettere e le parole non interessanti, quindi riduci ogni parola al sottoinsieme di lettere interessanti contenute, hai un Set problema di copertura . Questo è NP-completo. Se non vuoi fare una ricerca esaustiva, il meglio che puoi fare è ripetutamente prendere la parola che contiene il maggior numero di lettere non utilizzate.

    
risposta data 01.02.2018 - 21:57
fonte
0

Avrei pre-elaborato ogni parola nel dizionario per mappare la parola alla sua lista di lettere univoche. Quindi cerca la voce della mappa con le lettere desiderate.

In one-liner perl:

perl -MList::Util=uniq -slne '
    ($word = $_) =~ s/\W//g;     # remove punctuation, etc
    $key = join "", uniq sort split //, lc $word; 
    push @{$map{$key}}, $_;
  } END {
    print join "\n", @{$map{$letters}};
' -- -letters=aehilpst  /usr/share/dict/words
philatelist
philatelist's
philatelists
shapeliest
slaphappiest
splashiest
    
risposta data 01.02.2018 - 21:37
fonte

Leggi altre domande sui tag