2012-11-10 8 views
9

Ho scritto un piccolo programma che cerca di trovare una connessione tra due parole inglesi di uguale lunghezza. La Parola A si trasformerà in Parola B cambiando una lettera alla volta, ogni parola appena creata deve essere una parola inglese.Un modo più efficiente di trovare parole inglesi distanti una lettera dall'altra

Ad esempio:

Word A = BANG 
Word B = DUST 

Risultato:

BANG -> BUNG ->BUNT -> DUNT -> DUST 

Il mio processo:

  1. Caricare un lista di parole inglese (costituiti da 109582 parole) in un Map<Integer, List<String>> _wordMap = new HashMap();, chiave sarà la lunghezza della parola.

  2. Utente inserito in 2 parole.

  3. createGraph crea un grafico.

  4. calcolare il percorso più breve tra questi 2 nodi

  5. stampa il risultato.

Tutto funziona perfettamente bene, ma non sono soddisfatto con il tempo impiegato nel passaggio 3.

See:

Completely loaded 109582 words! 
CreateMap took: 30 milsecs 
CreateGraph took: 17417 milsecs 
(HOISE : HORSE) 
(HOISE : POISE) 
(POISE : PRISE) 
(ARISE : PRISE) 
(ANISE : ARISE) 
(ANILE : ANISE) 
(ANILE : ANKLE) 
The wholething took: 17866 milsecs 

non sono soddisfatto con il tempo necessario creare il grafico in punto 3, ecco il mio codice per esso (sto usando JGraphT per il grafico):

private List<String> _wordList = new ArrayList(); // list of all 109582 English words 
private Map<Integer, List<String>> _wordMap = new HashMap(); // Map grouping all the words by their length() 
private UndirectedGraph<String, DefaultEdge> _wordGraph = 
     new SimpleGraph<String, DefaultEdge>(DefaultEdge.class); // Graph used to calculate the shortest path from one node to the other. 


private void createGraph(int wordLength){ 

    long before = System.currentTimeMillis(); 
    List<String> words = _wordMap.get(wordLength); 
    for(String word:words){ 
     _wordGraph.addVertex(word); // adds a node 
     for(String wordToTest : _wordList){ 
      if (isSimilar(word, wordToTest)) {   
       _wordGraph.addVertex(wordToTest); // adds another node 
       _wordGraph.addEdge(word, wordToTest); // connecting 2 nodes if they are one letter off from eachother 
      } 
     }    
    }   

    System.out.println("CreateGraph took: " + (System.currentTimeMillis() - before)+ " milsecs"); 
} 


private boolean isSimilar(String wordA, String wordB) { 
    if(wordA.length() != wordB.length()){ 
     return false; 
    }   

    int matchingLetters = 0; 
    if (wordA.equalsIgnoreCase(wordB)) { 
     return false; 
    } 
    for (int i = 0; i < wordA.length(); i++) { 

     if (wordA.charAt(i) == wordB.charAt(i)) { 
      matchingLetters++; 
     } 
    } 
    if (matchingLetters == wordA.length() - 1) { 
     return true; 
    } 
    return false; 
} 

La mia domanda:

Come posso migliorare il mio algoritmo simmetrico per accelerare il processo?

Per qualsiasi redattore che sta leggendo questo, sì l'ho creato dopo aver visto il thread da/r/askreddit ieri.

+3

Qualsiasi motivo per non utilizzare la distanza di Levenshtein? http://en.wikipedia.org/wiki/Levenshtein_distance –

+0

Stai solo cercando di trovare il numero minimo di trasformazioni per creare Word B da Word A? Word A e Word B sono i parametri di input (cioè KNOWN in anticipo)? In tal caso, dovresti usare la distanza di Levenshtein come menzionato sopra. Tuttavia, sembra che tu voglia tradurre una lettera alla volta e che ogni parola intermedia debba anche essere una parola inglese? Tuttavia, se ciò è vero e considerando il tuo esempio di "BANG -> BUNG -> BUNT -> DUNT -> DUST" ... sono BUNG e BUNT parole davvero inglesi? Oppure .. è solo Word A il parametro di input e Word B è il risultato della trasformazione? Così confuso ... – GreenieMeanie

+0

@GreenieMeanie Puoi cercare quelle due parole su un dizionario se non hai familiarità con loro, quelle 2 sono parole inglesi effettive. E usare la distanza di Levenshtein non ha nulla a che fare con la mia domanda, il collo di bottiglia con il mio programma sta estraendo tutti i dati da un file di testo e poi li colleghi tutti in un grafico, che Jon mi ha già aiutato a risolvere, come calcolare il percorso più breve non fa parte della mia domanda (e non ho problemi a risolvere quella parte da sola). Ho solo chiesto come rompere il collo della bottiglia, se questo non ti era chiaro nella mia domanda iniziale, è quello che intendevo in realtà. – 16dots

risposta

17

Ecco un pensiero di partenza:

Crea a Map<String, List<String>> (oppure a Multimap<String, String> se usi Guava), e per ogni parola, "cancella" una lettera alla volta e aggiungi la parola originale all'elenco per quella parola cancellata. Così ci si finisce con:

.ORSE => NORSE, HORSE, GORSE (etc) 
H.RSE => HORSE 
HO.SE => HORSE, HOUSE (etc) 

A quel punto, dato una parola, si può facilmente trovare tutte le parole è simile a - basta passare attraverso lo stesso processo di nuovo, ma invece di aggiungere alla mappa , basta recuperare tutti i valori per ogni versione "cancellata".

+0

Oh, questo è molto interessante, non ci ho mai pensato, provatelo, grazie. – 16dots

+0

Questo è incredibile! 17866 ms prima, 2381 ms dopo, sei un dio. – 16dots

0

Probabilmente è necessario eseguirlo attraverso un profiler per vedere dove viene presa la maggior parte del tempo, soprattutto dal momento che si utilizzano classi di libreria, altrimenti si potrebbe impiegare molto impegno ma non vedere miglioramenti significativi.

È possibile mettere in minuscolo tutte le parole prima di iniziare, per evitare equalsIgnoreCase() ad ogni confronto. Di fatto, si tratta di un'incoerenza nel codice: inizialmente si utilizza equalsIgnoreCase(), ma si confrontano i caratteri in modo sensibile alla distinzione tra maiuscole e minuscole: if (wordA.charAt(i) == wordB.charAt(i)). Potrebbe valere la pena eliminare completamente il controllo equalsIgnoreCase(), poiché in pratica si tratta della stessa cosa del seguente ciclo charAt.

È possibile modificare il ciclo di confronto in modo che termini presto quando trova più di una lettera diversa, piuttosto che confrontare tutte le lettere e solo dopo aver verificato quante corrispondenze o differenze.

(Aggiornamento: questa risposta è di circa l'ottimizzazione del codice corrente mi rendo conto, rileggendo la tua domanda, che si può essere chiedendo algoritmi alternativi.!)

0

È possibile ordinare l'elenco di parole della stessa lunghezza e quindi eseguire un annidamento di loop del tipo for (int i = 0; i < n; ++i) for (int j = i + 1; j < n; ++j) { }.

E in isSimilar conta le differenze e su 2 restituisce false.

Problemi correlati