Il modo più efficiente per trovare le prime K parole frequenti in una grande sequenza di parole


86

Input: un intero positivo K e un testo grande. Il testo può effettivamente essere visualizzato come sequenza di parole. Quindi non dobbiamo preoccuparci di come scomporlo in sequenza di parole.
Risultato: le K parole più frequenti nel testo.

Il mio pensiero è così.

  1. usa una tabella hash per registrare la frequenza di tutte le parole mentre attraversi l'intera sequenza di parole. In questa fase la chiave è "word" e il valore è "word-frequency". Questo richiede O (n) tempo.

  2. ordinare la coppia (parola, parola-frequenza); e la chiave è "parola-frequenza". Questo richiede tempo O (n * lg (n)) con il normale algoritmo di ordinamento.

  3. Dopo l'ordinamento, prendiamo solo le prime K parole. Questo richiede tempo O (K).

Per riassumere, il tempo totale è O (n + n lg (n) + K) , Poiché K è sicuramente più piccolo di N, quindi è effettivamente O (n lg (n)).

Possiamo migliorare questo. In realtà, vogliamo solo le prime K parole. La frequenza di altre parole non ci interessa. Quindi, possiamo usare "l'ordinamento parziale di Heap". Per i passaggi 2) e 3), non ci limitiamo a ordinare. Invece, lo cambiamo per essere

2 ') crea un mucchio di coppie (parola, parola-frequenza) con "parola-frequenza" come chiave. Ci vuole O (n) tempo per costruire un mucchio;

3 ') estrae le prime K parole dall'heap. Ogni estrazione è O (lg (n)). Quindi, il tempo totale è O (k * lg (n)).

Per riassumere, questa soluzione è costata tempo O (n + k * lg (n)).

Questo è solo il mio pensiero. Non ho trovato il modo per migliorare il passaggio 1).
Spero che alcuni esperti di recupero delle informazioni possano far luce su questa domanda.


Useresti merge sort o quicksort per l'ordinamento O (n * logn)?
impegnato e

1
Per usi pratici, la risposta di Aaron Maenpaa di contare su un campione è la migliore. Non è che le parole più frequenti si nascondano dal tuo campione. Per i fanatici della complessità, è O (1) poiché la dimensione del campione è fissa. Non ottieni i conteggi esatti, ma non li stai nemmeno chiedendo.
Nikana Reklawyks

Se quello che vuoi è una revisione della tua analisi di complessità, allora è meglio menzionare: se n è il numero di parole nel tuo testo em è il numero di parole diverse (tipi, le chiamiamo), il passaggio 1 è O ( n ), ma il passaggio 2 è O ( m .lg ( m )) e m << n (potresti avere miliardi di parole e non raggiungere un milione di tipi, provalo). Quindi, anche con un algoritmo fittizio, è ancora O ( n + m lg ( m )) = O ( n ).
Nikana Reklawyks

1
Si prega di aggiungere alla domanda che abbiamo memoria principale sufficiente per contenere tutte le parole del testo grande. Sarebbe interessante vedere approcci per trovare k = 100 parole da file da 10 GB (cioè tutte le parole non entreranno in 4 GB di RAM) !!
KGhatak

@KGhatak come lo faremmo se superasse la dimensione della RAM?
user7098526

Risposte:


67

Questo può essere fatto in tempo O (n)

Soluzione 1:

Passaggi:

  1. Contare le parole e hash, che finiranno nella struttura in questo modo

    var hash = {
      "I" : 13,
      "like" : 3,
      "meow" : 3,
      "geek" : 3,
      "burger" : 2,
      "cat" : 1,
      "foo" : 100,
      ...
      ...
    
  2. Attraversa l'hash e trova la parola usata più di frequente (in questo caso "foo" 100), quindi crea l'array di quella dimensione

  3. Quindi possiamo attraversare nuovamente l'hash e utilizzare il numero di occorrenze di parole come indice dell'array, se non c'è niente nell'indice, creare un array altrimenti aggiungerlo all'array. Quindi finiamo con un array come:

      0   1      2            3                  100
    [[ ],[cat],[burger],[like, meow, geek],[]...[foo]]
    
  4. Quindi attraversa l'array dalla fine e raccogli le k parole.

Soluzione 2:

Passaggi:

  1. Come sopra
  2. Usa min heap e mantieni la dimensione del min heap su k, e per ogni parola nell'hash confrontiamo le occorrenze delle parole con il min, 1) se è maggiore del valore min, rimuovi il min (se la dimensione del min heap è uguale a k) e inserisci il numero nel min heap. 2) riposare in condizioni semplici.
  3. Dopo aver attraversato l'array, convertiamo semplicemente l'heap minimo in array e restituiamo l'array.

16
La soluzione (1) è un ordinamento per bucket O (n) che sostituisce un ordinamento per confronto standard O (n lg n). Il tuo approccio richiede spazio aggiuntivo per la struttura del bucket, ma è possibile eseguire ordinamenti di confronto sul posto. La soluzione (2) viene eseguita nel tempo O (n lg k), ovvero O (n) per iterare su tutte le parole e O (lg k) per aggiungerle ciascuna nell'heap.
stackoverflowuser2010

4
La prima soluzione richiede più spazio, ma è importante sottolineare che è infatti O (n) nel tempo. 1: frequenze hash digitate per parola, O (n); 2: Attraversa hash di frequenza, crea un secondo hash con chiave per frequenza. Questo è O (n) per attraversare l'hash e O (1) per aggiungere una parola all'elenco di parole a quella frequenza. 3: Attraversa l'hash dalla frequenza massima fino a quando non premi k. Al massimo, O (n). Totale = 3 * O (n) = O (n).
BringMyCakeBack

3
In genere, quando si contano le parole, il numero di bucket nella soluzione 1 è ampiamente sovrastimato (perché la parola numero uno più frequente è molto più frequente della seconda e della terza migliore), quindi la matrice è scarsa e inefficiente.
Nikana Reklawyks

La tua soluzione n. 1 non funziona quando k (il numero di parole frequenti) è inferiore al numero di occorrenze della parola più frequente (es. 100 in questo caso) Naturalmente, ciò potrebbe non accadere in pratica, ma si dovrebbe non dare per scontato!
Uno due tre il

@OneTwoThree la soluzione proposta è solo un esempio. Il numero sarà basato sulla richiesta.
Chihung Yu

22

Non otterrai un runtime generalmente migliore della soluzione che hai descritto. Devi fare almeno O (n) lavoro per valutare tutte le parole, e poi O (k) lavoro extra per trovare i primi k termini.

Se il tuo set di problemi è davvero grande, puoi usare una soluzione distribuita come map / reduce. Fai in modo che n lavoratori della mappa contino le frequenze su 1 / nesimo del testo ciascuno e, per ogni parola, invialo a uno degli m lavoratori riduttori calcolati in base all'hash della parola. I riduttori quindi sommano i conteggi. Unisci l'ordinamento sugli output dei riduttori ti darà le parole più popolari in ordine di popolarità.


13

Una piccola variazione sulla tua soluzione produce un algoritmo O (n) se non ci interessa classificare il primo K e una soluzione O (n + k * lg (k)) se lo facciamo. Credo che entrambi questi limiti siano ottimali entro un fattore costante.

L'ottimizzazione qui viene di nuovo dopo aver eseguito l'elenco, inserendolo nella tabella hash. Possiamo usare l' algoritmo della mediana delle mediane per selezionare il K-esimo elemento più grande nell'elenco. Questo algoritmo è dimostrabile O (n).

Dopo aver selezionato il K-esimo elemento più piccolo, partizioniamo l'elenco attorno a quell'elemento proprio come in quicksort. Questo è ovviamente anche O (n). Qualunque cosa sul lato "sinistro" del perno è nel nostro gruppo di elementi K, quindi abbiamo finito (possiamo semplicemente buttare via tutto il resto mentre procediamo).

Quindi questa strategia è:

  1. Passa attraverso ogni parola e inseriscila in una tabella hash: O (n)
  2. Seleziona il K-esimo elemento più piccolo: O (n)
  3. Partizione attorno a quell'elemento: O (n)

Se si desidera classificare gli elementi K, è sufficiente ordinarli con un ordinamento di confronto efficiente nel tempo O (k * lg (k)), ottenendo un tempo di esecuzione totale di O (n + k * lg (k)).

Il limite di tempo O (n) è ottimale all'interno di un fattore costante perché dobbiamo esaminare ogni parola almeno una volta.

Anche il limite di tempo O (n + k * lg (k)) è ottimale perché non esiste un metodo basato sul confronto per ordinare k elementi in un tempo inferiore a k * lg (k).


Quando selezioniamo il K-esimo elemento più piccolo, ciò che viene selezionato è il K-esimo hash-key più piccolo. Non è necessario che ci siano esattamente K parole nella partizione sinistra del Passaggio 3.
Prakash Murali

2
Non sarai in grado di eseguire "mediane di mediane" sulla tabella hash in quanto esegue gli scambi. Dovresti copiare i dati dalla tabella hash a un array temporaneo. Quindi, lo spazio di archiviazione O (n) sarà richiesto.
user674669

Non capisco come puoi selezionare l'elemento più piccolo Kth in O (n)?
Michael Ho Chum

Controlla questo per l'algoritmo per trovare l'elemento più piccolo Kth in O (n) - wikiwand.com/en/Median_of_medians
Piyush

La complessità è la stessa anche se usi hash table + min heap. non vedo alcuna ottimizzazione.
Vinay

8

Se il tuo "elenco di parole grandi" è abbastanza grande, puoi semplicemente campionare e ottenere stime. Altrimenti, mi piace l'aggregazione di hash.

Modifica :

Per campione intendo scegliere un sottoinsieme di pagine e calcolare la parola più frequente in quelle pagine. A condizione che tu selezioni le pagine in modo ragionevole e selezioni un campione statisticamente significativo, le tue stime delle parole più frequenti dovrebbero essere ragionevoli.

Questo approccio è davvero ragionevole solo se si dispone di così tanti dati che elaborarli tutti è solo un po 'sciocco. Se hai solo pochi mega, dovresti essere in grado di analizzare i dati e calcolare una risposta esatta senza sudare piuttosto che preoccuparti di calcolare una stima.


A volte devi farlo molte volte, ad esempio se stai cercando di ottenere l'elenco delle parole frequenti per sito Web o per argomento. In tal caso, "senza sudare" non basta davvero. Devi ancora trovare un modo per farlo nel modo più efficiente possibile.
itsadok

1
+1 per una risposta pratica che non affronta le questioni di complessità irrilevante. @itsadok: per ogni esecuzione: se è abbastanza grande, provalo; se non lo è, allora ottenere un fattore logaritmico è irrilevante.
Nikana Reklawyks

2

È possibile ridurre ulteriormente il tempo partizionando utilizzando la prima lettera di parole, quindi partizionando il set di più parole più grande utilizzando il carattere successivo fino a ottenere k set di parole singole. Usereste una sorta di albero a 256 vie con elenchi di parole parziali / complete sui fogli. Dovresti stare molto attento a non causare copie di stringhe ovunque.

Questo algoritmo è O (m), dove m è il numero di caratteri. Evita quella dipendenza da k, il che è molto utile per grandi k [dal modo in cui il tempo di esecuzione pubblicato è sbagliato, dovrebbe essere O (n * lg (k)) e non sono sicuro di cosa sia m].

Se esegui entrambi gli algoritmi fianco a fianco otterrai quello che sono abbastanza sicuro sia un algoritmo O (min (m, n * lg (k))) asintoticamente ottimale, ma il mio dovrebbe essere in media più veloce perché non coinvolge hashing o ordinamento.


7
Quello che stai descrivendo è chiamato "trie".
Nick Johnson

Ciao Strilanc. Puoi spiegare il processo di partizione in dettaglio?
Morgan Cheng,

1
come fa questo non coinvolgere l'ordinamento? una volta che hai il trie, come fai a estrarre le k parole con le frequenze più grandi. non ha alcun senso
ordinario

2

Hai un bug nella tua descrizione: il conteggio richiede O (n) tempo, ma l'ordinamento richiede O (m * lg (m)), dove m è il numero di parole uniche . Questo di solito è molto più piccolo del numero totale di parole, quindi probabilmente dovrebbe solo ottimizzare il modo in cui viene creato l'hash.



2

Se quello che stai cercando è l'elenco delle k parole più frequenti nel tuo testo per qualsiasi k pratico e per qualsiasi lingua naturale, la complessità del tuo algoritmo non è rilevante.

Basta campionare , diciamo, qualche milione di parole dal tuo testo, elaborarlo con qualsiasi algoritmo in pochi secondi e i conteggi più frequenti saranno molto accurati.

Come nota a margine, la complessità dell'algoritmo fittizio (1. conta tutto 2. ordina i conteggi 3. prendi il meglio) è O (n + m * log (m)), dove m è il numero di parole diverse nel tuo testo. log (m) è molto più piccolo di (n / m), quindi rimane O (n).

In pratica, il passo lungo conta.


2
  1. Utilizza una struttura dati efficiente in termini di memoria per memorizzare le parole
  2. Usa MaxHeap, per trovare le prime K parole frequenti.

Ecco il codice

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.PriorityQueue;

import com.nadeem.app.dsa.adt.Trie;
import com.nadeem.app.dsa.adt.Trie.TrieEntry;
import com.nadeem.app.dsa.adt.impl.TrieImpl;

public class TopKFrequentItems {

private int maxSize;

private Trie trie = new TrieImpl();
private PriorityQueue<TrieEntry> maxHeap;

public TopKFrequentItems(int k) {
    this.maxSize = k;
    this.maxHeap = new PriorityQueue<TrieEntry>(k, maxHeapComparator());
}

private Comparator<TrieEntry> maxHeapComparator() {
    return new Comparator<TrieEntry>() {
        @Override
        public int compare(TrieEntry o1, TrieEntry o2) {
            return o1.frequency - o2.frequency;
        }           
    };
}

public void add(String word) {
    this.trie.insert(word);
}

public List<TopK> getItems() {

    for (TrieEntry trieEntry : this.trie.getAll()) {
        if (this.maxHeap.size() < this.maxSize) {
            this.maxHeap.add(trieEntry);
        } else if (this.maxHeap.peek().frequency < trieEntry.frequency) {
            this.maxHeap.remove();
            this.maxHeap.add(trieEntry);
        }
    }
    List<TopK> result = new ArrayList<TopK>();
    for (TrieEntry entry : this.maxHeap) {
        result.add(new TopK(entry));
    }       
    return result;
}

public static class TopK {
    public String item;
    public int frequency;

    public TopK(String item, int frequency) {
        this.item = item;
        this.frequency = frequency;
    }
    public TopK(TrieEntry entry) {
        this(entry.word, entry.frequency);
    }
    @Override
    public String toString() {
        return String.format("TopK [item=%s, frequency=%s]", item, frequency);
    }
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + frequency;
        result = prime * result + ((item == null) ? 0 : item.hashCode());
        return result;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        TopK other = (TopK) obj;
        if (frequency != other.frequency)
            return false;
        if (item == null) {
            if (other.item != null)
                return false;
        } else if (!item.equals(other.item))
            return false;
        return true;
    }

}   

}

Ecco gli unit test

@Test
public void test() {
    TopKFrequentItems stream = new TopKFrequentItems(2);

    stream.add("hell");
    stream.add("hello");
    stream.add("hello");
    stream.add("hello");
    stream.add("hello");
    stream.add("hello");
    stream.add("hero");
    stream.add("hero");
    stream.add("hero");
    stream.add("hello");
    stream.add("hello");
    stream.add("hello");
    stream.add("home");
    stream.add("go");
    stream.add("go");
    assertThat(stream.getItems()).hasSize(2).contains(new TopK("hero", 3), new TopK("hello", 8));
}

Per maggiori dettagli fare riferimento a questo caso di test


1
  1. usa una tabella hash per registrare la frequenza di tutte le parole mentre attraversi l'intera sequenza di parole. In questa fase la chiave è "word" e il valore è "word-frequency". Questo richiede O (n) tempo. Questo è lo stesso di tutti quelli spiegati sopra

  2. Durante l'inserimento in hashmap, mantieni il Treeset (specifico per java, ci sono implementazioni in ogni lingua) di dimensione 10 (k = 10) per mantenere le prime 10 parole frequenti. Fino a quando la dimensione è inferiore a 10, continua ad aggiungerla. Se la dimensione è uguale a 10, se l'elemento inserito è maggiore dell'elemento minimo cioè il primo elemento. Se sì, rimuoverlo e inserire un nuovo elemento

Per limitare le dimensioni del set di alberi vedere questo collegamento


0

Supponiamo di avere una sequenza di parole "annuncio" "annuncio" "ragazzo" "grande" "cattivo" "com" "vieni" "freddo". E K = 2. come hai detto "partizionamento utilizzando la prima lettera di parole", abbiamo ottenuto ("ad", "ad") ("boy", "big", "bad") ("com" "come" "cold") "quindi partizionare il set composto da più parole utilizzando il carattere successivo fino a quando non si hanno k set di parole singole. " partirà ("boy", "big", "bad") ("com" "come" "cold"), la prima partizione ("ad", "ad") viene persa, mentre "ad" è in realtà il parola più frequente.

Forse fraintendo il tuo punto. Puoi dettagliare il tuo processo sulla partizione?


0

Credo che questo problema possa essere risolto da un algoritmo O (n). Potremmo fare lo smistamento al volo. In altre parole, l'ordinamento in questo caso è un problema secondario del problema dell'ordinamento tradizionale poiché solo un contatore viene incrementato di uno ogni volta che si accede alla tabella hash. Inizialmente, l'elenco è ordinato poiché tutti i contatori sono zero. Mentre continuiamo ad aumentare i contatori nella tabella hash, conserviamo un altro array di valori hash ordinati per frequenza come segue. Ogni volta che incrementiamo un contatore, controlliamo il suo indice nell'array classificato e controlliamo se il suo conteggio supera il suo predecessore nell'elenco. In tal caso, scambiamo questi due elementi. Come tale otteniamo una soluzione che è al massimo O (n) dove n è il numero di parole nel testo originale.


Questa è generalmente una buona direzione, ma ha un difetto. quando il conteggio aumenta, non controlleremo solo "il suo predecessore", ma dovremo controllare i "predecessori". per esempio, c'è una grande possibilità che l'array sia [4,3,1,1,1,1,1,1,1,1,1] - gli 1 possono essere tanti - questo lo renderà meno efficiente poiché dovremo guardare indietro a tutti i predecessori per trovare quello giusto da scambiare.
Shawn

Questo in effetti non sarebbe molto peggio di O (n)? Più simile a O (n ^ 2) in quanto è essenzialmente un ordinamento piuttosto inefficiente?
dcarr622

Ciao Shawn. Si sono d'accordo con te. Ma sospetto che il problema che hai menzionato sia fondamentale per il problema. Infatti, se invece di mantenere solo un array ordinato di valori, potessimo andare avanti e mantenere un array di coppie (valore, indice), dove l'indice punta alla prima occorrenza dell'elemento ripetuto, il problema dovrebbe essere risolvibile in O (n) tempo. Ad esempio, [4,3,1,1,1,1,1,1,1,1,1] sarà simile a [(4,0), (3,1), (1,2), (1 , 2), (1,2, ..., (1,2)]; gli indici partono da 0.
Aly Farahat

0

Stavo lottando anche con questo e sono stato ispirato da @aly. Invece di ordinare in seguito, possiamo semplicemente mantenere un elenco di parole preordinate ( List<Set<String>>) e la parola sarà nel set alla posizione X dove X è il conteggio corrente della parola. In generale, ecco come funziona:

  1. per ogni parola, memorizzala come parte della mappa della sua occorrenza: Map<String, Integer> .
  2. quindi, in base al conteggio, rimuoverlo dal set di conteggio precedente e aggiungerlo al nuovo set di conteggio.

Lo svantaggio di questo è che l'elenco potrebbe essere grande - può essere ottimizzato utilizzando un file TreeMap<Integer, Set<String>> - ma questo aggiungerà un po 'di overhead. In definitiva possiamo utilizzare un mix di HashMap o la nostra struttura dati.

Il codice

public class WordFrequencyCounter {
    private static final int WORD_SEPARATOR_MAX = 32; // UNICODE 0000-001F: control chars
    Map<String, MutableCounter> counters = new HashMap<String, MutableCounter>();
    List<Set<String>> reverseCounters = new ArrayList<Set<String>>();

    private static class MutableCounter {
        int i = 1;
    }

    public List<String> countMostFrequentWords(String text, int max) {
        int lastPosition = 0;
        int length = text.length();
        for (int i = 0; i < length; i++) {
            char c = text.charAt(i);
            if (c <= WORD_SEPARATOR_MAX) {
                if (i != lastPosition) {
                    String word = text.substring(lastPosition, i);
                    MutableCounter counter = counters.get(word);
                    if (counter == null) {
                        counter = new MutableCounter();
                        counters.put(word, counter);
                    } else {
                        Set<String> strings = reverseCounters.get(counter.i);
                        strings.remove(word);
                        counter.i ++;
                    }
                    addToReverseLookup(counter.i, word);
                }
                lastPosition = i + 1;
            }
        }

        List<String> ret = new ArrayList<String>();
        int count = 0;
        for (int i = reverseCounters.size() - 1; i >= 0; i--) {
            Set<String> strings = reverseCounters.get(i);
            for (String s : strings) {
                ret.add(s);
                System.out.print(s + ":" + i);
                count++;
                if (count == max) break;
            }
            if (count == max) break;
        }
        return ret;
    }

    private void addToReverseLookup(int count, String word) {
        while (count >= reverseCounters.size()) {
            reverseCounters.add(new HashSet<String>());
        }
        Set<String> strings = reverseCounters.get(count);
        strings.add(word);
    }

}

0

Ho appena scoperto l'altra soluzione per questo problema. Ma non sono sicuro che sia giusto. Soluzione:

  1. Usa una tabella hash per registrare la frequenza di tutte le parole T (n) = O (n)
  2. Scegli i primi k elementi della tabella hash e ripristinali in un buffer (il cui spazio = k). T (n) = O (k)
  3. Ogni volta, per prima cosa, dobbiamo trovare l'elemento min corrente del buffer e confrontare semplicemente l'elemento min del buffer con gli elementi (n - k) della tabella hash uno per uno. Se l'elemento della tabella hash è maggiore di questo elemento minimo del buffer, rilascia il valore minimo del buffer corrente e aggiungi l'elemento della tabella hash. Quindi ogni volta che troviamo quello minimo nel buffer è necessario T (n) = O (k), e attraversare l'intera tabella hash è necessario T (n) = O (n - k). Quindi l'intera complessità temporale per questo processo è T (n) = O ((nk) * k).
  4. Dopo aver attraversato l'intera tabella hash, il risultato è in questo buffer.
  5. L'intera complessità temporale: T (n) = O (n) + O (k) + O (kn - k ^ 2) = O (kn + n - k ^ 2 + k). Poiché, in generale, k è veramente minore di n. Quindi per questa soluzione, la complessità temporale è T (n) = O (kn) . Questo è il tempo lineare, quando k è veramente piccolo. È giusto? Non ne sono davvero sicuro.

0

Prova a pensare a una struttura dati speciale per affrontare questo tipo di problemi. In questo caso un tipo speciale di albero come trie per memorizzare le stringhe in modo specifico, molto efficiente. O un secondo modo per costruire la tua soluzione come contare le parole. Immagino che questo TB di dati sarebbe in inglese, quindi abbiamo circa 600.000 parole in generale, quindi sarà possibile memorizzare solo quelle parole e contando quali stringhe verranno ripetute + questa soluzione avrà bisogno di regex per eliminare alcuni caratteri speciali. La prima soluzione sarà più veloce, ne sono abbastanza sicuro.

http://en.wikipedia.org/wiki/Trie



0

Codice più semplice per ottenere l'occorrenza della parola usata più di frequente.

 function strOccurence(str){
    var arr = str.split(" ");
    var length = arr.length,temp = {},max; 
    while(length--){
    if(temp[arr[length]] == undefined && arr[length].trim().length > 0)
    {
        temp[arr[length]] = 1;
    }
    else if(arr[length].trim().length > 0)
    {
        temp[arr[length]] = temp[arr[length]] + 1;

    }
}
    console.log(temp);
    var max = [];
    for(i in temp)
    {
        max[temp[i]] = i;
    }
    console.log(max[max.length])
   //if you want second highest
   console.log(max[max.length - 2])
}

0

In queste situazioni, consiglio di utilizzare le funzionalità integrate di Java. Da allora, sono già ben testati e stabili. In questo problema, trovo le ripetizioni delle parole utilizzando la struttura dati HashMap. Quindi, spingo i risultati a una matrice di oggetti. Ordino l'oggetto per Arrays.sort () e stampo le prime k parole e le loro ripetizioni.

import java.io.*;
import java.lang.reflect.Array;
import java.util.*;

public class TopKWordsTextFile {

    static class SortObject implements Comparable<SortObject>{

        private String key;
        private int value;

        public SortObject(String key, int value) {
            super();
            this.key = key;
            this.value = value;
        }

        @Override
        public int compareTo(SortObject o) {
            //descending order
            return o.value - this.value;
        }
    }


    public static void main(String[] args) {
        HashMap<String,Integer> hm = new HashMap<>();
        int k = 1;
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("words.in")));

            String line;
            while ((line = br.readLine()) != null) {
                // process the line.
                //System.out.println(line);
                String[] tokens = line.split(" ");
                for(int i=0; i<tokens.length; i++){
                    if(hm.containsKey(tokens[i])){
                        //If the key already exists
                        Integer prev = hm.get(tokens[i]);
                        hm.put(tokens[i],prev+1);
                    }else{
                        //If the key doesn't exist
                        hm.put(tokens[i],1);
                    }
                }
            }
            //Close the input
            br.close();
            //Print all words with their repetitions. You can use 3 for printing top 3 words.
            k = hm.size();
            // Get a set of the entries
            Set set = hm.entrySet();
            // Get an iterator
            Iterator i = set.iterator();
            int index = 0;
            // Display elements
            SortObject[] objects = new SortObject[hm.size()];
            while(i.hasNext()) {
                Map.Entry e = (Map.Entry)i.next();
                //System.out.print("Key: "+e.getKey() + ": ");
                //System.out.println(" Value: "+e.getValue());
                String tempS = (String) e.getKey();
                int tempI = (int) e.getValue();
                objects[index] = new SortObject(tempS,tempI);
                index++;
            }
            System.out.println();
            //Sort the array
            Arrays.sort(objects);
            //Print top k
            for(int j=0; j<k; j++){
                System.out.println(objects[j].key+":"+objects[j].value);
            }


        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

Per ulteriori informazioni, visitare https://github.com/m-vahidalizadeh/foundations/blob/master/src/algorithms/TopKWordsTextFile.java . Spero possa essere d'aiuto.


In che modo questo migliora l'approccio abbozzato nella domanda? (Si prega di non tralasciare commenti dal codice presentato su SE.) ( I recommend to use Java built-in featuresCome i loop foreach e l' elaborazione dei flussi ?)
greybeard

Come sapete, uno dei fattori più importanti nella progettazione di un algoritmo efficiente è la scelta della giusta struttura dati. Quindi, è importante come affronti il ​​problema. Ad esempio, devi attaccare un problema dividendo e conquistando. Devi attaccarne un altro da avido. Come sapete, la società Oracle sta lavorando su Java. Sono una delle migliori aziende tecnologiche al mondo. Ci sono alcuni degli ingegneri più brillanti che lavorano lì sulle funzionalità integrate di Java. Quindi, queste funzionalità sono ben testate e a prova di proiettile. Se possiamo utilizzarli, è meglio usarli secondo me.
Mohammad,

0
**

C ++ 11 Implementazione del pensiero precedente

**

class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {

    unordered_map<int,int> map;
    for(int num : nums){
        map[num]++;
    }

    vector<int> res;
    // we use the priority queue, like the max-heap , we will keep (size-k) smallest elements in the queue
    // pair<first, second>: first is frequency,  second is number 
    priority_queue<pair<int,int>> pq; 
    for(auto it = map.begin(); it != map.end(); it++){
        pq.push(make_pair(it->second, it->first));

        // onece the size bigger than size-k, we will pop the value, which is the top k frequent element value 

        if(pq.size() > (int)map.size() - k){
            res.push_back(pq.top().second);
            pq.pop();
        }
    }
    return res;

}

};

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.