web-dev-qa-db-fra.com

Quelle structure de données utiliseriez-vous: TreeMap ou HashMap? (Java)

Description | Un programme Java pour lire un fichier texte et imprimer chacun des mots uniques dans l'ordre alphabétique, en indiquant le nombre d'occurrences de Word dans le texte. 

Le programme doit déclarer une variable de type Map<String, Integer> pour stocker les mots et la fréquence d’occurrence correspondante. Quel type de béton, cependant? TreeMap<String, Number> ou HashMap<String, Number>?

L'entrée doit être convertie en minuscule.

Un mot ne contient aucun de ces caractères: \t\t\n]f.,!?:;\"()'

Exemple de sortie |  

 Word            Frequency
  a                 1
  and               5
  appearances       1
  as                1
         .
         .
         .

Remarque | Je sais, j'ai vu des solutions élégantes à cela en Perl avec environ deux lignes de code. Cependant, je veux le voir en Java. 

Edit: Oh ouais, il serait utile de montrer une implémentation en utilisant l’une de ces structures (en Java). 

53
JohnZaj

TreeMap semble une évidence pour moi - simplement en raison de l'exigence "dans l'ordre alphabétique". HashMap n'a aucun ordre lorsque vous parcourez-le; TreeMap itère dans l'ordre des clés naturelles.

EDIT: Je pense que le commentaire de Konrad a peut-être suggéré "utilisez HashMap, puis triez". C'est bien parce que même si nous aurons initialement N itérations, nous aurons K <= N clés à la fin en raison des doublons. Nous pourrions tout aussi bien économiser le tri coûteux jusqu'à la fin, lorsque nous aurons moins de clés que le simple, mais non constant, qui consiste à garder le tri en ordre continu.

Cela dit, je m'en tiens à ma réponse pour le moment: parce que c'est la la plus simple façon d'atteindre l'objectif. Nous ne savons pas vraiment que le PO est particulièrement préoccupé par les performances, mais la question implique qu'il est préoccupé par l'élégance et la brièveté. Utiliser une TreeMap rend cela incroyablement bref, ce qui me plaît. Je soupçonne que si les performances sont réellement un problème, il existe peut-être un meilleur moyen de l'attaquer que TreeMap ou HashMap :)

59
Jon Skeet

TreeMap bat HashMap car TreeMap est déjà trié pour vous.

Cependant, vous pouvez envisager d’utiliser une structure de données plus appropriée, un sac. Voir Commons Collections - et le TreeBag class:

Ceci a une structure interne et une API optimisées par Nice:

bag.add("big")
bag.add("small")
bag.add("big")
int count = bag.getCount("big")

EDIT: La question de la performance de HashMap vs TreeMap a été répondue par Jon - HashMap et le tri peut être plus rapide (essayez!), Mais TreeBag est plus facile. La même chose est vraie pour les sacs. Il y a un HashBag ainsi qu'un TreeBag. Sur la base de la mise en œuvre (utilise un entier mutable), un sac doit être plus performant que la carte simple équivalente d'Integer. Le seul moyen de savoir avec certitude est de tester, comme pour toute question de performance.

18
JodaStephen

Je vois pas mal de gens dire "La recherche TreeMap prend O(n log n)" !! Comment venir? 

Je ne sais pas comment cela a été mis en œuvre, mais dans ma tête, cela prend O(log n)

En effet, la recherche dans un arbre peut être effectuée dans O(log n). Vous ne triez pas l'intégralité de l'arborescence chaque fois que vous y insérez un élément. C'est l'idée d'utiliser un arbre!

Ainsi, pour revenir à la question initiale, les chiffres à comparer se révèlent être les suivants:

Approche HashMap: O(n + k log k) cas moyen, le pire des cas pourrait être beaucoup plus important

Approche TreeMap: O(k + n log k) pire des cas

où n = nombre de mots dans le texte, k = nombre de mots distincts dans le texte.

11
saurabh

La carte de hachage devrait être beaucoup plus rapide. Vous ne devez pas choisir un conteneur en fonction de la manière dont vous souhaitez organiser les éléments à terme; Il suffit de trier la liste des paires (Word, fréquence) à la fin. Il y aura généralement moins de paires à classer que de mots dans les fichiers, donc les performances asymptotiques (et réelles) avec une carte de hachage seront meilleures.

3
hashlover

"Lorsqu'une clé existe déjà, elle a les mêmes performances qu'un HashMap." - C'est tout simplement faux. HashMap a l'insertion O(1) et TreeMap O (n log n). Il faudra au moins n journaux et vérifications pour savoir s’il est dans la table!

2
coderz

Vous ne pouvez pas affecter un TreeMap<String,Number> à une variable de type Map<String,Integer>. Double, Long, etc. peuvent être "placés" dans un TreeMap<String,Number>. Quand "j'obtiens" une valeur d'un Map<String,Integer>, il doit s'agir d'une Integer.

En ignorant complètement les problèmes liés à i18n, les contraintes de mémoire et le traitement des erreurs, voici ce qui suit:

class Counter {

  public static void main(String... argv)
    throws Exception
  {
    FileChannel fc = new FileInputStream(argv[0]).getChannel();
    ByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());
    CharBuffer cb = Charset.defaultCharset().decode(bb);
    Pattern p = Pattern.compile("[^ \t\r\n\f.,!?:;\"()']+");
    Map<String, Integer> counts = new TreeMap<String, Integer>();
    Matcher m = p.matcher(cb);
    while (m.find()) {
      String Word = m.group();
      Integer count = counts.get(Word);
      count = (count == null) ? 1 : count + 1;
      counts.put(Word, count);
    }
    fc.close();
    for (Map.Entry<String, Integer> e : counts.entrySet()) {
      System.out.printf("%s: %d%n", e.getKey(), e.getValue());
    }
  }

}
2
erickson
import Java.io.BufferedReader;
import Java.io.DataInputStream;
import Java.io.FileInputStream;
import Java.io.FileNotFoundException;
import Java.io.IOException;
import Java.io.InputStreamReader;
import Java.io.ObjectInputStream.GetField;
import Java.util.Iterator;
import Java.util.Map;
import Java.util.StringTokenizer;
import Java.util.TreeMap;

public class TreeMapExample {

    public static void main (String args[]){
        Map<String,Integer> tm = new TreeMap<String,Integer>();
        try {

            FileInputStream fis = new FileInputStream("Test.txt");
            DataInputStream in = new DataInputStream(fis);
            BufferedReader br = new BufferedReader(new InputStreamReader(in));
            String line;
            int countValue = 1;
            while((line = br.readLine())!= null ){
                line = line.replaceAll("[-+.^:;,()\"\\[\\]]","");
                StringTokenizer st = new StringTokenizer(line, " ");    
                while(st.hasMoreTokens()){
                    String nextElement = (String) st.nextElement();

                    if(tm.size()>0 && tm.containsKey(nextElement)){
                        int val = 0;
                        if(tm.get(nextElement)!= null){
                        val = (Integer) tm.get(nextElement);
                        val = val+1;
                        }
                        tm.put(nextElement, val);
                    }else{
                    tm.put(nextElement, 1);
                    }

                }
            }
            for(Map.Entry<String,Integer> entry : tm.entrySet()) {
            System.out.println(entry.getKey() + " : " + entry.getValue());
            }

        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}
2
Balu

Pour cette façon, à mon avis, mieux utiliser HashBag from Collections Apache Commons ou HashMultiset from Guava ou HashBag from Collections Eclipse (officiellement GS Collections ) ou toute classe suivante:

    Order    |  Guava           |   Apache  | Eclipse(GS) | JDK analog
─────────────┼──────────────────┼───────────┼─────────────┼─────────────
Not define   | HashMultiset     |   HashBag | HashBag     | HashMap<String, Integer>
─────────────┼──────────────────┼───────────┼─────────────┼─────────────
Sorted       | TreeMultiset     |   TreeBag | TreeBag     | TreeMap<String, Integer>
─────────────┼──────────────────┼───────────┼─────────────┼─────────────
Linked       |LinkedHashMultiset|     -     |     -       | LinkedHashMap<String, Integere>
─────────────┼──────────────────┼───────────┼─────────────┼─────────────
Concurrent & | ConcurrentHash-  |Synchroniz-|Synchroniz-  | Collections.synchronizedMap(
not define   | Multiset         |   edBag   | edBag       |       HashMap<String, Integer>)
─────────────┼──────────────────┼───────────┼─────────────┼─────────────
Concurrent   |         -        |Synchroniz-|Synchroniz-  | Collections.synchronizedSorted-
and sorted   |                  |edSortedBag| edSortedBag |       Map(TreeMap<>))
─────────────┼──────────────────┼───────────┼─────────────┼─────────────
Immutable and| ImmutableMultiset|Unmodifiab-|Unmodifiab-  | Collections.unmodifiableMap(
not define   |                  |   leBag   | leBag       | HashMap<String, Integer>)
─────────────┼──────────────────┼───────────┼─────────────┼─────────────
Immutable and| ImmutableSorted- |Unmodifiab-|Unmodifiab-  | Collections.unmodifiableSorted-
sorted       | Multiset         |leSortedBag| leSortedBag | Map(TreeMap<String, Integer>))
────────────────────────────────────────────────────────────────────────

Exemples:

1. Utilisation de SynchronizedSortedBag d'Apache :

    // Parse text to separate words
    String INPUT_TEXT = "Hello World! Hello All! Hi World!";
    // Create Multiset
    Bag bag = SynchronizedSortedBag.synchronizedBag(new TreeBag(Arrays.asList(INPUT_TEXT.split(" "))));

    // Print count words
    System.out.println(bag); // print [1:All!,2:Hello,1:Hi,2:World!]- in natural (alphabet) order
    // Print all unique words
    System.out.println(bag.uniqueSet());    // print [All!, Hello, Hi, World!]- in natural (alphabet) order


    // Print count occurrences of words
    System.out.println("Hello = " + bag.getCount("Hello"));    // print 2
    System.out.println("World = " + bag.getCount("World!"));    // print 2
    System.out.println("All = " + bag.getCount("All!"));    // print 1
    System.out.println("Hi = " + bag.getCount("Hi"));    // print 1
    System.out.println("Empty = " + bag.getCount("Empty"));    // print 0

    // Print count all words
    System.out.println(bag.size());    //print 6

    // Print count unique words
    System.out.println(bag.uniqueSet().size());    //print 4

2. Utilisation de TreeBag d'Eclipse (GC) :

    // Parse text to separate words
    String INPUT_TEXT = "Hello World! Hello All! Hi World!";
    // Create Multiset
    MutableSortedBag<String> bag =  TreeBag.newBag(Arrays.asList(INPUT_TEXT.split(" ")));

    // Print count words
    System.out.println(bag); // print [All!, Hello, Hello, Hi, World!, World!]- in natural order
    // Print all unique words
    System.out.println(bag.toSortedSet());    // print [All!, Hello, Hi, World!]- in natural order

    // Print count occurrences of words
    System.out.println("Hello = " + bag.occurrencesOf("Hello"));    // print 2
    System.out.println("World = " + bag.occurrencesOf("World!"));    // print 2
    System.out.println("All = " + bag.occurrencesOf("All!"));    // print 1
    System.out.println("Hi = " + bag.occurrencesOf("Hi"));    // print 1
    System.out.println("Empty = " + bag.occurrencesOf("Empty"));    // print 0

    // Print count all words
    System.out.println(bag.size());    //print 6

    // Print count unique words
    System.out.println(bag.toSet().size());    //print 4

3. Utilisation de LinkedHashMultiset from Guava :

    // Parse text to separate words
    String INPUT_TEXT = "Hello World! Hello All! Hi World!";
    // Create Multiset
    Multiset<String> multiset = LinkedHashMultiset.create(Arrays.asList(INPUT_TEXT.split(" ")));

    // Print count words
    System.out.println(multiset); // print [Hello x 2, World! x 2, All!, Hi]- in predictable iteration order
    // Print all unique words
    System.out.println(multiset.elementSet());    // print [Hello, World!, All!, Hi] - in predictable iteration order

    // Print count occurrences of words
    System.out.println("Hello = " + multiset.count("Hello"));    // print 2
    System.out.println("World = " + multiset.count("World!"));    // print 2
    System.out.println("All = " + multiset.count("All!"));    // print 1
    System.out.println("Hi = " + multiset.count("Hi"));    // print 1
    System.out.println("Empty = " + multiset.count("Empty"));    // print 0

    // Print count all words
    System.out.println(multiset.size());    //print 6

    // Print count unique words
    System.out.println(multiset.elementSet().size());    //print 4

Plus d'exemples que vous pouvez trouver dans mes projets github

2

Je choisirais certainement une TreeMap:

  • TreeMap trie automatiquement les nouvelles clés lors de l'insertion, aucun tri ultérieur n'est nécessaire.
  • Lorsqu'une clé existe déjà, elle a les mêmes performances qu'un HashMap.

Un TreeSet utilise en interne un TreeMap, alors pourquoi ne pas utiliser TreeMap directement.

1
Chris

En fonction des exigences de vitesse, vous pouvez également utiliser un Trie . Mais il ne sert à rien d’en implémenter une si TreeMap est assez rapide.

0
CAdaker

considérer la fréquence d'ajout ou de suppression à la structure de données. TreeMap ne serait pas idéal s'il est élevé. Outre la recherche d'une entrée existante nLn, il subit également un rééquilibrage fréquent.

par contre, les structures de hachage sont un peu flamboyantes en mémoire (sur-alloue). Si vous pouvez mordre la balle, optez pour la structure de hachage et triez-la si nécessaire.

0
G Kumar

Voici l'exemple de Java pour lire un fichier texte, en effectuant un tri par clé, puis par valeur; en fonction du nombre d'occurrences d'un mot dans le fichier.

public class SortFileWords {

    public static void main(String[] args) {
        HashMap<String, Integer> map = new HashMap<String, Integer>();
        ValueCompare vc = new ValueCompare(map);
        TreeMap<String, Integer> sorted_map = new TreeMap<String, Integer>(map);
        List<String> list = new ArrayList<>();
        Scanner sc;
        try {
            sc = new Scanner(new File("c:\\ReadMe1.txt"));
            while (sc.hasNext()) {
                list.add(sc.next());
            }
            sc.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

        for (String s : list) {
            if (map.containsKey(s)) {
                map.put(s, map.get(s) + 1);
            } else
                map.put(s, 1);
        }

        System.out.println("Unsorted map: " + map);
        sorted_map.putAll(map);
        System.out.println("Sorted map on keys: " + sorted_map);

        TreeMap<String, Integer> sorted_value_map = new TreeMap<>(vc);
        sorted_value_map.putAll(map);
        System.out.println("Sorted map on values: " + sorted_value_map);
    }
}

class ValueCompare implements Comparator<String> {

    Map<String, Integer> map;

    public ValueCompare(Map<String, Integer> map) {
        this.map = map;
    }

    @Override
    public int compare(String s1, String s2) {
        if (map.get(s1) >= map.get(s2))
            return -1;
        else
            return 1;
    }
}
0
hardeep thakur