Ecco una possibile soluzione utilizzando Apache Lucene. Non ho usato l'ultima versione ma lo 3.6.2 one, poiché questo è quello che conosco meglio. Oltre allo /lucene-core-x.x.x.jar
, non dimenticare di aggiungere lo /contrib/analyzers/common/lucene-analyzers-x.x.x.jar
dall'archivio scaricato al progetto: contiene gli analizzatori specifici della lingua (in particolare quello inglese nel tuo caso).
Si noti che questo sarà solo trovare le frequenze delle parole di testo di input in base alla rispettiva radice. Confrontando queste frequenze con le statistiche della lingua inglese deve essere fatto in seguito (this answer può aiutare a proposito).
Il modello di dati
Una parola chiave per uno stelo. Parole diverse possono avere la stessa radice, quindi il set terms
. La frequenza della parola chiave viene incrementata ogni volta che viene trovato un nuovo termine (anche se è già stato trovato - un set rimuove automaticamente i duplicati).
public class Keyword implements Comparable<Keyword> {
private final String stem;
private final Set<String> terms = new HashSet<String>();
private int frequency = 0;
public Keyword(String stem) {
this.stem = stem;
}
public void add(String term) {
terms.add(term);
frequency++;
}
@Override
public int compareTo(Keyword o) {
// descending order
return Integer.valueOf(o.frequency).compareTo(frequency);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
} else if (!(obj instanceof Keyword)) {
return false;
} else {
return stem.equals(((Keyword) obj).stem);
}
}
@Override
public int hashCode() {
return Arrays.hashCode(new Object[] { stem });
}
public String getStem() {
return stem;
}
public Set<String> getTerms() {
return terms;
}
public int getFrequency() {
return frequency;
}
}
Utilità
Per arginare una parola:
public static String stem(String term) throws IOException {
TokenStream tokenStream = null;
try {
// tokenize
tokenStream = new ClassicTokenizer(Version.LUCENE_36, new StringReader(term));
// stem
tokenStream = new PorterStemFilter(tokenStream);
// add each token in a set, so that duplicates are removed
Set<String> stems = new HashSet<String>();
CharTermAttribute token = tokenStream.getAttribute(CharTermAttribute.class);
tokenStream.reset();
while (tokenStream.incrementToken()) {
stems.add(token.toString());
}
// if no stem or 2+ stems have been found, return null
if (stems.size() != 1) {
return null;
}
String stem = stems.iterator().next();
// if the stem has non-alphanumerical chars, return null
if (!stem.matches("[a-zA-Z0-9-]+")) {
return null;
}
return stem;
} finally {
if (tokenStream != null) {
tokenStream.close();
}
}
}
Per cercare in una collezione (saranno utilizzati da parte l'elenco dei potenziali parole chiave):
public static <T> T find(Collection<T> collection, T example) {
for (T element : collection) {
if (element.equals(example)) {
return element;
}
}
collection.add(example);
return example;
}
Nucleo
Ecco il metodo di alimentazione principale:
public static List<Keyword> guessFromString(String input) throws IOException {
TokenStream tokenStream = null;
try {
// hack to keep dashed words (e.g. "non-specific" rather than "non" and "specific")
input = input.replaceAll("-+", "-0");
// replace any punctuation char but apostrophes and dashes by a space
input = input.replaceAll("[\\p{Punct}&&[^'-]]+", " ");
// replace most common english contractions
input = input.replaceAll("(?:'(?:[tdsm]|[vr]e|ll))+\\b", "");
// tokenize input
tokenStream = new ClassicTokenizer(Version.LUCENE_36, new StringReader(input));
// to lowercase
tokenStream = new LowerCaseFilter(Version.LUCENE_36, tokenStream);
// remove dots from acronyms (and "'s" but already done manually above)
tokenStream = new ClassicFilter(tokenStream);
// convert any char to ASCII
tokenStream = new ASCIIFoldingFilter(tokenStream);
// remove english stop words
tokenStream = new StopFilter(Version.LUCENE_36, tokenStream, EnglishAnalyzer.getDefaultStopSet());
List<Keyword> keywords = new LinkedList<Keyword>();
CharTermAttribute token = tokenStream.getAttribute(CharTermAttribute.class);
tokenStream.reset();
while (tokenStream.incrementToken()) {
String term = token.toString();
// stem each term
String stem = stem(term);
if (stem != null) {
// create the keyword or get the existing one if any
Keyword keyword = find(keywords, new Keyword(stem.replaceAll("-0", "-")));
// add its corresponding initial token
keyword.add(term.replaceAll("-0", "-"));
}
}
// reverse sort by frequency
Collections.sort(keywords);
return keywords;
} finally {
if (tokenStream != null) {
tokenStream.close();
}
}
}
Esempio
Utilizzando il metodo guessFromString
sulla Java wikipedia article introduction part, ecco le prime 10 parole chiave più frequenti (cioè steli) che sono stati trovati:
java x12 [java]
compil x5 [compiled, compiler, compilers]
sun x5 [sun]
develop x4 [developed, developers]
languag x3 [languages, language]
implement x3 [implementation, implementations]
applic x3 [application, applications]
run x3 [run]
origin x3 [originally, original]
gnu x3 [gnu]
iterare la lista di output per sapere che erano l'originale trovato parole per ogni stelo da ottenere le terms
set (visualizzati tra parentesi [...]
nell'esempio di cui sopra).
Quali sono le prospettive
Confronta i frequenza staminali/frequenze riassumono rapporti con le statistiche di lingua inglese quelli, e mi tengono in loop se il vostro riusciti: ho potuto essere molto interessato troppo :)
Quindi, come ho capito, il codice deve essere eseguito su un server Apache. Cosa succede se il mio software dovrebbe essere locale? – Shay
@Shay Perché sarebbe necessario un server Apache? Ho appena messo 'KeywordsGuesser.guessFromString (" input ")' in un metodo 'public static void main (String [] args)' per fare l'esempio. – sp00m
Non conosco Lucene e vedo che il codice è fortemente basato su di esso, quindi ho pensato che fosse così. Qualche idea su dove trovare questo dizionario inglese di stems? – Shay