web-dev-qa-db-fra.com

Klythes Kassen Kassen Kassen

Entrée: Un entier positif K et un gros texte. Le texte peut en réalité être vu comme une séquence Word. Nous n’avons donc pas à nous demander comment le décomposer en séquence Word.
Sortie: Les K mots les plus fréquents dans le texte.

Ma pensée est comme ça. 

  1. utilisez une table de hachage pour enregistrer la fréquence de tous les mots tout en parcourant toute la séquence de Word. Dans cette phase, la clé est "Word" et la valeur est "Word-frequency". Cela prend O(n) temps. 

  2. trier la paire (mot, fréquence de mot); et la clé est "Word-frequency". Cela prend O (n * lg (n)) temps avec l’algorithme de tri normal. 

  3. Après le tri, nous prenons juste les premiers mots K. Cela prend O(K) temps. 

Pour résumer, le temps total est O (n + n lg (n) + K) , Puisque K est sûrement inférieur à N, il est donc en réalité O (n lg (n)).

Nous pouvons améliorer cela. En fait, nous voulons juste les mots K principaux. La fréquence des autres mots ne nous concerne pas. Nous pouvons donc utiliser le "tri partiel du tas". Pour les étapes 2) et 3), nous ne faisons pas que du tri. Au lieu de cela, nous le changeons pour être

2 ') construisez un tas de paires (Word, Word-frequency) avec "Word-frequency" comme clé. Il faut O(n) temps pour construire un tas;

3 ') extraire les K meilleurs mots du tas. Chaque extraction est O (lg (n)). Donc, le temps total est O (k * lg (n)).

Pour résumer, cette solution a coûté du temps O (n + k * lg (n)).

Ceci est juste ma pensée. Je n'ai pas trouvé le moyen d'améliorer l'étape 1).
J'espère que certains experts en recherche d'informations pourront éclairer davantage cette question.

73
Morgan Cheng

Cela peut être fait en O(n)

Solution 1:

Pas:

  1. Compter les mots et les hacher, ce qui aboutira dans la structure comme celle-ci

    var hash = {
      "I" : 13,
      "like" : 3,
      "meow" : 3,
      "geek" : 3,
      "burger" : 2,
      "cat" : 1,
      "foo" : 100,
      ...
      ...
    
  2. Parcourez le hachage et trouvez le mot le plus fréquemment utilisé (ici "foo" 100), puis créez le tableau de cette taille

  3. Ensuite, nous pouvons parcourir à nouveau le hachage et utiliser le nombre d'occurrences de mots comme index de tableau. S'il n'y a rien dans l'index, créez un tableau, puis ajoutez-le au tableau. Ensuite, nous nous retrouvons avec un tableau comme:

      0   1      2            3                100
    [[ ],[ ],[burger],[like, meow, geek],[]...[foo]]
    
  4. Ensuite, parcourez le tableau de la fin et récupérez les k mots.

Solution 2:

Pas:

  1. Comme ci-dessus
  2. Utilisez min heap et conservez la taille min heap à k, et pour chaque mot du hachage, comparez les occurrences de mots avec min, 1) si elle est supérieure à la valeur min, supprimez min (si la taille min. heap est égal à k) et insère le nombre dans le tas min. 2) reposez des conditions simples.
  3. Après avoir parcouru le tableau, il suffit de convertir le tas min en tableau et de renvoyer le tableau.
54
Chihung Yu

L’exécution ne sera généralement pas meilleure que la solution que vous avez décrite. Vous devez effectuer au moins O(n) travail pour évaluer tous les mots, puis O(k) travail supplémentaire pour trouver les k premiers termes.

Si votre ensemble de problèmes est vraiment grand, vous pouvez utiliser une solution distribuée telle que mapper/réduire. Faites en sorte que n utilisateurs de la carte comptent chacun les fréquences sur 1/nème du texte et, pour chaque mot, envoyez-les à un des m réducteurs calculés en fonction du hachage de la parole. Les réducteurs additionnent ensuite les comptes. Fusionner le tri sur les sorties des réducteurs vous donnera les mots les plus populaires par ordre de popularité.

20
Nick Johnson

Une petite variation de votre solution donne un algorithme O(n) si nous ne nous soucions pas de classer les K premiers, et un O (n + k * lg (k)) solution si nous le faisons. Je crois que ces deux limites sont optimales dans un facteur constant.

L'optimisation intervient de nouveau après avoir parcouru la liste, en l'insérant dans la table de hachage. Nous pouvons utiliser l’algorithme de la médiane pour sélectionner le Kème élément le plus important de la liste. Cet algorithme est prouvablement O (n).

Après avoir sélectionné le Kth plus petit élément, nous partitionnons la liste autour de cet élément, comme dans le tri rapide. C'est évidemment aussi O (n). Tout ce qui se trouve du côté "gauche" du pivot se trouve dans notre groupe de K éléments. Nous avons donc terminé (nous pouvons simplement jeter tout le reste au fur et à mesure).

Donc, cette stratégie est:

  1. Parcourez chaque mot et insérez-le dans une table de hachage: O (n)
  2. Sélectionnez le Kème plus petit élément: O (n)
  3. Partition autour de cet élément: O (n)

Si vous souhaitez classer les K éléments, il vous suffit de les trier avec un tri de comparaison efficace en temps O (k * lg (k)), ce qui donne un temps total d'exécution de O (n + k * lg (k)).

La limite de temps O(n) est optimale dans un facteur constant car nous devons examiner chaque mot au moins une fois. 

La limite temporelle O (n + k * lg (k)) est également optimale car il n'existe pas de moyen de comparaison permettant de trier k éléments dans moins de k * lg (k). 

13
Andrew

Si votre "grande liste de mots" est assez grande, vous pouvez simplement échantillonner et obtenir des estimations. Sinon, j'aime l'agrégation de hachage.

Modifier :

Par exemple, je veux dire choisir un sous-ensemble de pages et calculer le mot le plus fréquent dans ces pages. Si vous sélectionnez les pages de manière raisonnable et sélectionnez un échantillon statistiquement significatif, vos estimations des mots les plus fréquents doivent être raisonnables.

Cette approche n’est vraiment raisonnable que si vous avez suffisamment de données pour que tout traiter soit idiot. Si vous n'avez que quelques Mo, vous devriez être capable de déchirer les données et de calculer une réponse exacte sans casser la sueur plutôt que de prendre la peine de calculer une estimation.

8
Aaron Maenpaa
  1. Utiliser une structure de données efficace en mémoire pour stocker les mots
  2. Utilisez MaxHeap pour trouver les K mots les plus fréquents.

Voici le code

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;
    }

}   

}

Voici les tests unitaires

@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));
}

Pour plus de détails, voir ce cas de test

2
craftsmannadeem

Votre problème est le même que ceci- http://www.geeksforgeeks.org/find-the-k-most-frequent-words-france-from-a-file/

Utilisez Trie and min heap pour le résoudre efficacement.

2
Jitendra Rathor

Vous avez un bug dans votre description: Compter prend O(n), mais le tri prend O (m * lg (m)), où m est le nombre de unique mots. Ce nombre étant généralement beaucoup plus petit que le nombre total de mots, vous devez probablement probablement optimiser la manière dont le hachage est construit.

2
martinus

Si ce que vous recherchez, c'est la liste des mots k les plus fréquents de votre texte pour tous les travaux pratiques k et pour tous les langages naturels, la complexité de votre algorithme n'est pas pertinente. 

Juste exemple , par exemple, quelques millions de mots de votre texte, traitez cela avec n'importe quel algorithme en quelques secondes, et les comptages les plus fréquents seront très précis.

Notons que la complexité de l’algorithme factice (1. compter tous 2. trier les comptes 3. prendre le meilleur) est O (n + m * log (m)), où m est le nombre de mots différents dans votre texte. log (m) est beaucoup plus petit que (n/m), il reste donc O (n). 

Pratiquement, le long pas compte.

2

Vous pouvez réduire davantage le temps en partitionnant en utilisant la première lettre de mots, puis en partitionnant le jeu de plusieurs mots le plus grand en utilisant le caractère suivant jusqu'à obtenir k ensembles de mots uniques. Vous utiliseriez une sorte d'arborescence à 256 voies avec des listes de mots partiels/complets au niveau des feuilles. Vous devez faire très attention à ne pas créer de copies de chaînes partout.

Cet algorithme est O (m), où m est le nombre de caractères. Cela évite cette dépendance à k, ce qui est très gentil pour les gros k [vu que votre temps de course affiché est faux, il devrait être de O m].

Si vous exécutez les deux algorithmes côte à côte, vous obtiendrez ce que je suis presque sûr est un algorithme asymptotiquement optimal O (min (m, n * lg (k))). hachage ou tri.

2
Strilanc
  1. utilisez une table de hachage pour enregistrer la fréquence de tous les mots tout en parcourant toute la séquence de Word. Dans cette phase, la clé est "Word" et la valeur est "Word-frequency". Cela prend O(n) temps. C'est la même chose que toutes les explications précédentes.

  2. Lors de l’insertion dans hashmap, conservez le Treeset (spécifique à Java, il existe des implémentations dans toutes les langues) de taille 10 (k = 10) pour conserver les 10 mots les plus fréquents. Jusqu'à ce que la taille soit inférieure à 10, continuez à l'ajouter. Si la taille est égale à 10, si l’élément inséré est supérieur au minimum, c’est-à-dire au premier élément. Si oui, supprimez-le et insérez un nouvel élément

Pour restreindre la taille du jeu d’arbres, voir ce lien

1
M Sach

Je viens de découvrir l'autre solution à ce problème. Mais je ne suis pas sûr que ce soit juste… .. Solution:

  1. Utilisez une table de hachage pour enregistrer la fréquence de tous les mots T(n) = O (n)
  2. Choisissez les k premiers éléments de la table de hachage et restaurez-les dans un tampon (dont l'espace = k). T(n) = O (k)
  3. Chaque fois, nous devons d’abord trouver l’élément min actuel du tampon et comparer l’élément min du tampon avec les (n - k) éléments de la table de hachage un à un. Si l'élément de la table de hachage est supérieur à cet élément min de la mémoire tampon, alors supprimez la valeur minimale de la mémoire tampon actuelle et ajoutez l'élément de la table de hachage. Ainsi, chaque fois que nous trouvons le minimum dans le tampon besoin T(n) = O (k), et parcourons toute la table de hachage besoin T(n) = O (n - k). La complexité temporelle totale de ce processus est donc T(n) = O((n-k) * k).
  4. Après avoir parcouru toute la table de hachage, le résultat est dans cette mémoire tampon.
  5. Toute la complexité temporelle: T(n) = O(n) + O(k) + O (kn - k ^ 2) = O (kn + n - k ^ 2 + k). Depuis, k est vraiment plus petit que n en général. Donc, pour cette solution, la complexité temporelle est T(n) = O(kn) . C'est du temps linéaire, quand k est vraiment petit. Est ce bien? Je ne suis pas sûr.
0
zproject89

Je me débattais aussi avec cela et je m'inspirais de @aly. Au lieu de trier par la suite, nous pouvons simplement conserver une liste de mots pré-triée (List<Set<String>>) et le mot sera dans l'ensemble à la position X, où X est le nombre actuel du mot. En général, voici comment cela fonctionne:

  1. pour chaque mot, stockez-le dans la carte de son occurrence: Map<String, Integer>.
  2. puis, en fonction du nombre, supprimez-le du jeu de comptage précédent et ajoutez-le au nouveau jeu de comptage.

L'inconvénient de cette liste est que la liste est peut-être trop grosse - elle peut être optimisée à l'aide d'un TreeMap<Integer, Set<String>> - mais cela va ajouter un peu de surcharge. En fin de compte, nous pouvons utiliser une combinaison de HashMap ou de notre propre structure de données.

Le code

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
Shawn

C'est une idée intéressante à rechercher et je pourrais trouver cet article lié à Top-K https://icmi.cs.ucsb.edu/research/tech_reports/reports/2005-23.pd

Il existe également une implémentation de celui-ci ici .

0
Anayag

Essayez de penser à une structure de données spéciale pour aborder ce type de problèmes. Dans ce cas, un type spécial d’arbre permet de stocker les chaînes de manière spécifique, très efficace. Ou une deuxième façon de construire votre propre solution, comme compter les mots. Je suppose que ces TB données sont en anglais, alors nous avons environ 600 000 mots en général, il sera donc possible de stocker uniquement ces mots et de compter les chaînes qui seront répétées + cette solution nécessitera une expression régulière pour en éliminer certains caractères spéciaux. La première solution sera plus rapide, j'en suis sûr. 

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

0
blueberry0xff

Code le plus simple pour obtenir l'occurrence de Word le plus fréquemment utilisé.

 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
ngLover
**

C++ 11 Mise en oeuvre de la pensée ci-dessus

**

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;

}

};

0
asad_nitp

Dans ces situations, il est recommandé d'utiliser les fonctionnalités intégrées à Java. Depuis, ils sont déjà bien testés et stables. Dans ce problème, je trouve les répétitions des mots en utilisant la structure de données HashMap. Ensuite, je Pousser les résultats à un tableau d'objets. Je trie l'objet par Arrays.sort () et affiche les k premiers mots et leurs répétitions. 

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();
        }
    }

}

Pour plus d'informations, visitez le site https://github.com/m-vahidalizadeh/foundations/blob/master/src/algorithms/TopKWordsTextFile.Java . J'espère que ça aide.

0
Vahid

Je crois que ce problème peut être résolu par un algorithme O(n). Nous pourrions faire le tri à la volée. En d'autres termes, le tri dans ce cas est un sous-problème du problème du tri traditionnel car un seul compteur est incrémenté d'un à chaque fois que nous accédons à la table de hachage. Initialement, la liste est triée puisque tous les compteurs sont à zéro. Comme nous continuons à incrémenter les compteurs dans la table de hachage, nous conservons un autre tableau de valeurs de hachage ordonnées par fréquence, comme suit. Chaque fois que nous incrémentons un compteur, nous vérifions son index dans le tableau classé et si son nombre dépasse son prédécesseur dans la liste. Si tel est le cas, nous échangeons ces deux éléments. En tant que tel, nous obtenons une solution qui est au plus O(n), où n est le nombre de mots du texte original.

0
Aly Farahat

Supposons que nous ayons une séquence de mots "ad" "ad" "boy" "big" "bad" "com" "come" "cold". Et K = 2 . Comme vous avez mentionné "partitionner en utilisant la première lettre de mots", nous avons ("Ad", "ad") ("garçon", "grand", "mauvais") (" com "" viens "" froid ") " puis partitionnez le plus grand ensemble de plusieurs mots en utilisant le caractère suivant jusqu'à ce que vous ayez k ensembles de mots uniques. " "bad") ("com" "come" "cold"), la première partition ("ad", "ad") est manquante, alors que "ad" est en fait le mot le plus fréquent.

J'ai peut-être mal compris votre argument. Pouvez-vous détailler votre processus de partition?

0
Morgan Cheng