2016-01-08 15 views
15

Sto costruendo una parola riordinatore (php/mysql) che accetta l'input dell'utente tra 2 e 8 lettere e restituisce parole tra 2 e 8 lettere che possono essere fatte da quelle lettere, non necessariamente utilizzando tutte le lettere, ma sicuramente non includendo più lettere di quelle fornite.Come faccio a fare in modo che la mia parola riordinatore restituisca risultati più pertinenti

L'utente immetterà qualcosa come MSIKE o MSIKEI (due i), o qualsiasi combinazione di lettere o più occorrenze di una lettera.

La query di seguito troverà tutte le occorrenze di parole che contengono M, S, I, K, o E.

Tuttavia, la query di seguito riporta anche le parole che hanno più occorrenze di lettere non richiesti. Ad esempio, la parola meek verrebbe restituita, anche se ha due ee l'utente non ha inserito due e, o la parola kiss, anche se l'utente non ha inserito s due volte.

SELECT word 
FROM words 
WHERE word REGEXP '[msike]' 
AND has_a=0 
AND has_b=0 
AND has_c=0 
AND has_d=0 
(we skip e) or we could add has_e=1 
AND has_f=0 
...and so on...skipping letters m, s, i, k, and e 
AND has_w=0 
AND has_x=0 
AND has_y=0 
AND has_z=0 

Annotare il colonne has_a, has_b, ecc sono o 1 se la lettera si verifica nella parola o 0 in caso contrario.

Sono aperto a tutte le modifiche allo schema della tabella.

Questo sito: http://grecni.com/texttwist.php è un buon esempio di ciò che sto tentando di emulare.

La domanda è come modificare la query per non restituire parole con più occorrenze di una lettera, a meno che l'utente non abbia inserito specificatamente una lettera più volte. Raggruppare in base alla lunghezza della parola sarebbe un ulteriore vantaggio.

Grazie mille.


EDIT: ho alterato il dB per il suggerimento di @awei, The has_ ​​{lettera} è ora count_ {letter} e memorizza il numero di occorrenze di rispettiva lettera nella rispettiva word. Questo potrebbe essere utile quando un utente inserisce una lettera più volte. esempio: l'utente inserisce MSIKES (due s).

Inoltre, ho abbandonato l'approccio REGEXP come mostrato nell'istruzione SQL originale. Lavorando per fare la maggior parte del lavoro sul lato PHP, ma molti ostacoli sono ancora nel modo.


EDIT: Incluso prime 10 righe dalla tabella

id word  alpha  otcwl ospd csw sowpods dictionary enable vowels consonants start_with end_with end_with_ing end_with_ly end_with_xy count_a count_b count_c count_d count_e count_f count_g count_h count_i count_j count_k count_l count_m count_n count_o count_p count_q count_r count_s count_t count_u count_v count_w count_x count_y count_z q_no_u letter_count scrabble_points wwf_points status date_added 
1 aa   aa   1  0  0 1  1   1  aa     a   a   0    0   0   2  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  2    2    2   1  2015-11-12 05:39:45 
2 aah   aah   1  0  0 1  0   1  aa  h   a   h   0    0   0   2  0  0  0  0  0  0  1  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  3    6    5   1  2015-11-12 05:39:45 
3 aahed  aadeh  1  0  0 1  0   1  aae  hd   a   d   0    0   0   2  0  0  1  1  0  0  1  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  5    9    8   1  2015-11-12 05:39:45 
4 aahing  aaghin  1  0  0 1  0   1  aai  hng   a   g   1    0   0   2  0  0  0  0  0  1  1  1  0  0  0  0  1  0  0  0  0  0  0  0  0  0  0  0  0  0  6    10    11   1  2015-11-12 05:39:45 
5 aahs  aahs  1  0  0 1  0   1  aa  hs   a   s   0    0   0   2  0  0  0  0  0  0  1  0  0  0  0  0  0  0  0  0  0  1  0  0  0  0  0  0  0  0  4    7    6   1  2015-11-12 05:39:45 
6 aal   aal   1  0  0 1  0   1  aa  l   a   l   0    0   0   2  0  0  0  0  0  0  0  0  0  0  1  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  3    3    4   1  2015-11-12 05:39:45 
7 aalii  aaiil  1  0  0 1  1   1  aaii l   a   i   0    0   0   2  0  0  0  0  0  0  0  2  0  0  1  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  5    5    6   1  2015-11-12 05:39:45 
8 aaliis  aaiils  1  0  0 1  0   1  aaii ls   a   s   0    0   0   2  0  0  0  0  0  0  0  2  0  0  1  0  0  0  0  0  0  1  0  0  0  0  0  0  0  0  6    6    7   1  2015-11-12 05:39:45 
9 aals  aals  1  0  0 1  0   1  aa  ls   a   s   0    0   0   2  0  0  0  0  0  0  0  0  0  0  1  0  0  0  0  0  0  1  0  0  0  0  0  0  0  0  4    4    5   1  2015-11-12 05:39:45 
10 aardvark aaadkrrv 1  0  0 1  1   1  aaa  rdvrk  a   k   0    0   0   3  0  0  1  0  0  0  0  0  0  1  0  0  0  0  0  0  2  0  0  0  1  0  0  0  0  0  8    16    17   1  2015-11-12 05:39:45 
+5

Questo sarebbe probabilmente notevolmente più semplice se hai appena implementato in php. – pvg

+1

@pvg Più semplice forse. Ma sicuramente non più veloce di interrogarlo dallo stesso SQL. – choz

+3

@choz dipende probabilmente dalla complessità della query :) –

risposta

10

Pensa di aver già svolto il duro lavoro con lo schema modificato. Tutto quello che devi fare ora è modificare la query per cercare <= il numero di conteggi di ogni lettera, come specificato dall'utente.

E.g. se l'utente ha immesso "ALIAS":

SELECT word 
FROM words 
WHERE count_a <= 2 
    AND count_b <= 0 
    AND count_c <= 0 
    AND count_d <= 0 
    AND count_e <= 0 
    AND count_f <= 0 
    AND count_g <= 0 
    AND count_h <= 0 
    AND count_i <= 1 
    AND count_j <= 0 
    AND count_k <= 0 
    AND count_l <= 1 
    AND count_m <= 0 
    AND count_n <= 0 
    AND count_o <= 0 
    AND count_p <= 0 
    AND count_q <= 0 
    AND count_r <= 0 
    AND count_s <= 1 
    AND count_t <= 0 
    AND count_u <= 0 
    AND count_v <= 0 
    AND count_w <= 0 
    AND count_x <= 0 
    AND count_y <= 0 
    AND count_z <= 0 
ORDER BY CHAR_LENGTH(word), word; 

Nota: Come richiesto, questo è ordinamento per lunghezza di parola, quindi in ordine alfabetico. Ho usato <= anche per <= 0 solo per rendere più facile la modifica manuale per altre lettere.

Restituisce "aa", "aal" e "aals" (ma non "aalii" o "aaliis" poiché entrambi hanno due "i").

Vedere SQL Fiddle Demo.

+0

Questa sembra essere la soluzione più semplice e funziona bene. Simile a quello che stavo cercando di fare, ma non riuscivo a mettere insieme la logica per farlo. – Mark

+0

Felice di aver funzionato - a volte tutto ciò che serve per finire qualcosa è un altro paio di occhi. –

3

Dal momento che si dispone di due diverse esigenze, vi suggerisco di implementare entrambe le due diverse soluzioni.

Dove non si cura delle lettere doppie, creare un tipo di dati SET con le 26 lettere. Compila i bit in base a ciò che la parola ha. Questo ignora le lettere duplicate. Ciò facilita anche la ricerca di parole con un sottoinsieme delle lettere: (the_set & ~the_letters) = 0.

Dove si cura i duplicati, ordinare le lettere nella parola e memorizzarle come chiave. "msike" diventa "eikms".

costruire una tabella che contiene 3 colonne:

eikms -- non unique index on this 
msike -- the real word - probably good to have this as the PRIMARY KEY 
SET('m','s','i',','k','e') -- for the other situation. 

msikei e mite deve essere inserito come

eikms 
msikei 
SET('m','s','i',','k','e') -- (or, if more convenient: SET('m','i','s','i',','k','e') 

ekm 
meek 
SET('e','k','m') 

REGEXP è non pratico per il vostro compito.

Modifica 1

penso che è necessario anche una colonna che indica se ci sono tutte le lettere della parola raddoppiato. In questo modo, è possibile distinguere che kiss è consentito per msikes ma per msike.

Edit 2

Un SET o un INT UNSIGNED può contenere 1 bit per ciascuna delle 26 lettere - 0 per non presente, 1 per il presente.

msikes e msike entrerebbero entrambi nel set con esattamente 5 bit attivati. Il valore a INSERT sarebbe 'm,s,i,k,e,s' per msikes. Dal momento che il resto deve coinvolgere l'aritmetica booleana, forse sarebbe meglio usare INT UNSIGNED. Quindi ...

a is 1 (1 << 0) 
b is 2 (1 << 1) 
c is 4 (1 << 2) 
d is 8 (1 << 3) 
... 
z is (1 << 25) 

Per INSERT si utilizza l'operatore |. bad diventa

(1 << 1) | (1 << 0) | (1 << 3) 

Nota come i bit sono disposti, con 'A' in basso:

SELECT BIN((1 << 1) | (1 << 0) | (1 << 3)); ==> 1011 

Allo stesso modo 'annuncio' è 1001. Quindi, fa 'ad' match 'cattivo'? La risposta arriva da

SELECT b'1001' & ~b'1011' = 0; ==> 1 (meaning 'true') 

Ciò significa che tutte le lettere 'ad' (1001) si trovano nel 'cattivo' (1011). Introduciamo "letto", che è 11010.

SELECT b'11010' & ~b'1011' = 0; ==> FALSE because of 'e' (10000) 

Ma 'papà' (1001) funziona bene:

SELECT b'1001' & ~b'1011' = 0; ==> TRUE 

Così, ora arriva il flag "dup". Dal momento che "papà" ha una duplicazione, ma "cattiva" no, le tue regole dicono che non è una corrispondenza. Ma ci è voluto il "dup" per completare la decisione.

Se non hai seguito un corso di aritmetica booleana, ho appena presentato il primo paio di capitoli. Se lo coprivo troppo velocemente, trova un libro di matematica su questo e salta dentro. "Non è scienza missilistica".

Quindi, tornando a ciò che il codice è necessario per decidere se my_word ha un sottoinsieme di lettere e se è permesso di avere le lettere duplicati:

SELECT $my_mask & ~tbl.mask = 0, dup FROM tbl; 

poi fare lo adatta e/o tra per finire la logica .

+0

Scusa, ma non sto proprio afferrando cosa vuoi che faccia della risposta. Ho già una colonna che riflette le lettere della parola, in ordine alfabetico. msike e msikei non sono parole, ma lettere inserite da un utente. Il risultato restituito dovrebbe fornire tutte le parole che possono essere formate utilizzando almeno due delle lettere e utilizzando solo le lettere inviate dall'utente. Si noti che l'utente può inviare due o più della stessa lettera, ad esempio, msikes, dove la s è elencata due volte, un risultato valido sarebbe me, è, sci, semi, bacio, mike e altre parole di lunghezza variabile. – Mark

+0

Mostrami alcune righe del tavolo. –

+0

Il 'SET' è una stringa di bit da usare per decidere che' sci' proviene da un sottoinsieme delle lettere in 'msikes'. –

1

Con il supporto Regex limitato su MySQL, il meglio che posso fare è uno script PHP per generare la query, presumendo che includa solo lettere inglesi. Sembra che un'espressione per escludere parole non valide sia più semplice di una che li include.

<?php 
$inputword = str_split('msikes'); 
$counter = array(); 
for ($l = 'a'; $l < 'z'; $l++) { 
    $counter[$l] = 0; 
} 
foreach ($inputword as $l) { 
    $counter[$l]++; 
} 
$nots = ''; 
foreach ($counter as $l => $c) { 
    if (!$c) { 
     $nots .= $l; 
     unset($counter[$l]); 
    } 
} 
$conditions = array(); 
if(!empty($nots)) { 
    // exclude words that have letters not given 
    $conditions[] = "[" . $nots . "]'"; 
} 
foreach ($counter as $l => $c) { 
    $letters = array(); 
    for ($i = 0; $i <= $c; $i++) { 
     $letters[] = $l; 
    } 
    // exclude words that have the current letter more times than given 
    $conditions[] = implode('.*', $letters); 
} 
$sql = "SELECT word FROM words WHERE word NOT RLIKE '" . implode('|', $conditions) . "'"; 
echo $sql; 
+0

Grazie, ma ora mi sto appoggiando a una soluzione non regex. Ho qualche codice PHP ora che elimina le parole ovvie che non combaciano. – Mark

1

Qualcosa di simile potrebbe funzionare per voi:

// Input Word 
$WORD = strtolower('msikes'); 

// Alpha Array 
$Alpha = range('a', 'z'); 

// Turn it into letters. 
$Splited = str_split($WORD); 
$Letters = array(); 
// Count occurrence of each letter, use letter as key to make it unique 
foreach($Splited as $Letter) { 
    $Letters[$Letter] = array_key_exists($Letter, $Letters) ? $Letters[$Letter] + 1 : 1; 
} 

// Build a list of letters that shouldn't be present in the word 
$ShouldNotExists = array_filter($Alpha, function ($Letter) use ($Letters) { 
    return ! array_key_exists($Letter, $Letters); 
}); 

#### Building SQL Statement 
// Letters to skip 
$SkipLetters = array(); 
foreach($ShouldNotExists as $SkipLetter) { 
    $SkipLetters[] = "`has_{$SkipLetter}` = 0"; 
} 
// count condition (for multiple occurrences) 
$CountLetters = array(); 
foreach($Letters as $K => $V) { 
    $CountLetters[] = "`count_{$K}` <= {$V}"; 
} 

$SQL = 'SELECT `word` FROM `words` WHERE '.PHP_EOL; 
$SQL .= '('.implode(' AND ', $SkipLetters).')'.PHP_EOL; 
$SQL .= ' AND ('.implode(' AND ', $CountLetters).')'.PHP_EOL; 
$SQL .= ' ORDER BY LENGTH(`word`), `word`'.PHP_EOL; 

echo $SQL; 
Problemi correlati