Java: conta il numero di occorrenze di parole nella stringa

Nodo di origine: 1719850

Introduzione

Il conteggio del numero di occorrenze di parole in una stringa è un compito abbastanza semplice, ma ha diversi approcci per farlo. Devi anche tenere conto dell'efficienza del metodo, dal momento che in genere vorrai utilizzare strumenti automatizzati quando non desideri eseguire lavori manuali, ad esempio quando lo spazio di ricerca è ampio.

In questa guida imparerai come contare il numero di occorrenze di parole in una stringa in Java:

String searchText = "Your body may be chrome, but the heart never changes. It wants what it wants.";
String targetWord = "wants";

Cercheremo il numero di occorrenze di targetWord, utilizzando String.split(), Collections.frequency() ed Espressioni Regolari.

Conta le occorrenze di parole in una stringa con String.split()

Il modo più semplice per contare l'occorrenza di una parola di destinazione in una stringa è dividere la stringa su ogni parola e scorrere l'array, incrementando un wordCount su ogni partita. Nota che quando una parola ha un qualsiasi tipo di punteggiatura attorno ad essa, ad esempio wants. alla fine della frase, la semplice divisione a livello di parola tratterà correttamente wants ed wants. come parole separate!

Per ovviare a questo problema, puoi rimuovere facilmente tutta la punteggiatura dalla frase prima dividendolo:

String[] words = searchText.replaceAll("p{Punct}", "").split(" ");

int wordCount = 0;
for (int i=0; i < words.length; i++)
    if (words[i].equals(targetWord))
        wordCount++;
System.out.println(wordCount);

Nel for loop, ripetiamo semplicemente l'array, controllando se l'elemento in ogni indice è uguale a targetWord. Se lo è, incrementiamo il wordCount, che al termine dell'esecuzione stampa:

2

Conta le occorrenze di parole in una stringa con Raccolte.frequenza()

I Collections.frequency() fornisce un'implementazione molto più pulita e di livello superiore, che astrae un semplice for loop e controlla entrambe le identità (se un oggetto is un altro oggetto) e l'uguaglianza (se un oggetto è uguale a un altro oggetto, a seconda delle caratteristiche qualitative di quell'oggetto).

I frequency() accetta un elenco in cui cercare e l'oggetto di destinazione e funziona anche per tutti gli altri oggetti, dove il comportamento dipende da come l'oggetto stesso implementa equals(). Nel caso di stringhe, equals() controlli per il contenuto della stringa:


searchText = searchText.replaceAll("p{Punct}", "");

int wordCount = Collections.frequency(Arrays.asList(searchText.split(" ")), targetWord);
System.out.println(wordCount);

Qui, abbiamo convertito l'array ottenuto da split() in un Java ArrayList, utilizzando l'assistente asList() metodo del Arrays classe. L'operazione di riduzione frequency() restituisce un numero intero che denota la frequenza di targetWord nell'elenco e risulta in:

2

Occorrenze di parole in una stringa con Matcher (espressioni regolari – RegEx)

Infine, puoi utilizzare le espressioni regolari per cercare modelli e contare il numero di modelli abbinati. Le espressioni regolari sono fatte per questo, quindi è molto naturale per l'attività. In Java, il Pattern la classe viene utilizzata per rappresentare e compilare espressioni regolari e la Matcher la classe viene utilizzata per trovare e abbinare i modelli.

Usando RegEx, possiamo codificare l'invarianza della punteggiatura nell'espressione stessa, quindi non è necessario formattare esternamente la stringa o rimuovere la punteggiatura, che è preferibile per testi di grandi dimensioni in cui memorizzare un'altra versione modificata in memoria potrebbe essere costoso:

Pattern pattern = Pattern.compile("b%s(?!w)".format(targetWord));

Pattern pattern = Pattern.compile("bwants(?!w)");
Matcher matcher = pattern.matcher(searchText);

int wordCount = 0;
while (matcher.find())
    wordCount++;

System.out.println(wordCount);

Ciò comporta anche:

2

Indice di efficienza

Allora, qual è il più efficiente? Eseguiamo un piccolo benchmark:

int runs = 100000;

long start1 = System.currentTimeMillis();
for (int i = 0; i < runs; i++) {
    int result = countOccurencesWithSplit(searchText, targetWord);
}

long end1 = System.currentTimeMillis();
System.out.println(String.format("Array split approach took: %s miliseconds", end1-start1));

long start2 = System.currentTimeMillis();
  for (int i = 0; i < runs; i++) {
    int result = countOccurencesWithCollections(searchText, targetWord);
}

long end2 = System.currentTimeMillis();
System.out.println(String.format("Collections.frequency() approach took: %s miliseconds", end2-start2));

long start3 = System.currentTimeMillis();
for (int i = 0; i < runs; i++) {
    int result = countOccurencesWithRegex(searchText, targetWord);
}

long end3 = System.currentTimeMillis();
System.out.println(String.format("Regex approach took: %s miliseconds", end3-start3));

Ogni metodo verrà eseguito 100000 volte (maggiore è il numero, minore è la varianza e i risultati dovuti al caso, a causa della legge dei grandi numeri). L'esecuzione di questo codice comporta:

Array split approach took: 152 miliseconds
Collections.frequency() approach took: 140 miliseconds
Regex approach took: 92 miliseconds

Tuttavia, cosa succede se rendiamo la ricerca più dispendiosa dal punto di vista computazionale ingrandendola? Generiamo una frase sintetica:

List possibleWords = Arrays.asList("hello", "world ");
StringBuffer searchTextBuffer = new StringBuffer();

for (int i = 0; i < 100; i++) {
    searchTextBuffer.append(String.join(" ", possibleWords));
}
System.out.println(searchTextBuffer);

Questo crea una stringa con il contenuto:

hello world hello world hello world hello ...

Dai un'occhiata alla nostra guida pratica e pratica per l'apprendimento di Git, con le migliori pratiche, gli standard accettati dal settore e il cheat sheet incluso. Smetti di cercare su Google i comandi Git e in realtà imparare esso!

Ora, se dovessimo cercare "ciao" o "mondo", ci sarebbero molte più corrispondenze delle due di prima. Come si comportano ora i nostri metodi nel benchmark?

Array split approach took: 606 miliseconds
Collections.frequency() approach took: 899 miliseconds
Regex approach took: 801 miliseconds

Ora, la divisione dell'array è più veloce! In generale, i benchmark dipendono da vari fattori, come lo spazio di ricerca, la parola target, ecc. e il tuo caso d'uso personale potrebbe essere diverso dal benchmark.

Consigli: Prova i metodi sul tuo testo, prendi nota dei tempi e scegli quello più efficiente ed elegante per te.

Conclusione

In questa breve guida, abbiamo dato un'occhiata a come contare le occorrenze di parole per una parola di destinazione, in una stringa in Java. Abbiamo iniziato dividendo la stringa e utilizzando un semplice contatore, seguito dall'utilizzo di Collections classe helper e, infine, usando le espressioni regolari.

Alla fine, abbiamo confrontato i metodi e notato che le prestazioni non sono lineari e dipendono dallo spazio di ricerca. Per testi di input più lunghi con molte corrispondenze, la divisione degli array sembra essere la più efficace. Prova tutti e tre i metodi da solo e scegli quello più performante.

Timestamp:

Di più da Impilamento