2016-06-04 9 views
0

Ich habe diese HashMap: HashMap<NGram, Integer> countMap = new HashMap<>();
ich es zu füllen bin versucht (Snippet ist in einer Schleife):
Objekt nicht erkannt in einer Karte

NGram ng = new NGram(param); 
if (countMap.containsKey(ng)){ // or if(countMap.get(ng) != null){ 
    int counter = countMap.get(ng); 
    countMap.put(ng, ++counter); 
} 
else{ 
    countMap.put(ng, 1); 
} 

Aber countMap.containsKey(ng) nie gibt true zurück (wenn es sein soll) , so wird die else-Anweisung immer ausgeführt! Ich habe equals() und hashCode() von NGram überschrieben und sie funktionieren gut. Bei primitiven Datentypen funktioniert das Snippet.

Meine Frage ist also: Wie überzeuge ich Java, dass es schon das Objekt in der HashMap gibt?

Hier ist der Code von toString(), hashCode() und equals():

public String toString(){ 
    String ret = ""; 
    for (String s : previousToken){ 
     ret += s + " "; 
    } 
    ret += token; 
    return ret; 
} 

public boolean equals(NGram other){ 
    if(this.toString().equals(other.toString())){ 
     return true; 
    } 
    else{ 
     return false; 
    } 
} 

public int hashCode(){ 
    String ts = this.toString(); 
    int result = 3; 
    for(int i = 0; i < ts.length(); i++){ 
     result = 2 * result + ((int)ts.charAt(i)); 
    } 
    return result; 
} 
+1

Können Sie uns den Code aus den Methoden 'equals' und' getHashCode' zeigen? –

+0

Vielleicht haben Sie noch nie ein 'NGram' Objekt hinzugefügt, so dass Sie nie in den Code innerhalb des' if' gehen werden? –

+0

@KevinWallis OP hat gesagt * "Snippet ist in einer Schleife" *, also ist es wahrscheinlicher, dass 'param' in' equals() 'verwendet wird und immer eindeutig ist, z. es ist die Schleifenvariable. – Andreas

Antwort

0

Hier ist ein Beispiel dafür, was Ihre drei Methoden sein sollte.
Dies ist, was ein genannt wird MCVE(Minimal, Complete, und prüfbare Beispiel).

Das primäre Konzept ist, dass das Konstruieren einer neuen kombinierten Zeichenfolge durch Aufrufen von toString() nicht der richtige Weg ist, equals() und hashCode() zu implementieren.

Dies ist vor allem aus Leistungsgründen. Mit 18 million objects hinzugefügt, um die Karte, Leistung von equals() und hashCode() Angelegenheiten.

Ein anderer Grund, es ist eine schlechte Idee, ist, dass toString() möglicherweise nicht alle Werte des Objekts anzeigen, oder dass zwei verschiedene Objekte immer noch das gleiche toString() Ergebnis, z. wenn Token Leerzeichen enthalten könnte, dann Verketten würde sie nicht in der Lage sein A B, zu unterscheiden C von A, B C.

Für das Beispiel wird ein String[] für das Feld previousToken verwendet, und ein Varargs-Konstruktor wird verwendet, um das Testen zu vereinfachen. Null-Token sind nicht erlaubt. Deines ist wahrscheinlich etwas anders, aber das zeigt die Konzepte.

public class NGram { 
    private String token; 
    private String[] previousToken; // could also be List<String> 
    public NGram(String ... allTokens) { 
     if (allTokens.length == 0) 
      throw new IllegalArgumentException("At least one token is required"); 
     for (String s : allTokens) 
      if (s == null) 
       throw new NullPointerException(); 
     this.token = allTokens[allTokens.length - 1]; 
     this.previousToken = Arrays.copyOfRange(allTokens, 0, allTokens.length - 1); 
    } 
    @Override 
    public String toString() { 
     StringBuilder buf = new StringBuilder(); 
     for (String s : this.previousToken) 
      buf.append(s).append(' '); 
     return buf.append(this.token).toString(); 
    } 
    @Override 
    public boolean equals(Object obj) { 
     if (this == obj) 
      return true; 
     if (obj == null || getClass() != obj.getClass()) 
      return false; 
     NGram that = (NGram) obj; 
     return this.token.equals(that.token) && 
       Arrays.equals(this.previousToken, that.previousToken); 
    } 
    @Override 
    public int hashCode() { 
     return this.token.hashCode() + 31 * Arrays.hashCode(this.previousToken); 
    } 
} 

TEST

Dies zeigt eine schnellere, kompaktere Version des Codes ein neues Objekt zu der Karte hinzuzufügen. Es ist schneller, weil es nur eine Lookup pro Add führt und kompakter gemacht durch einen ternären Operator anstelle eines if Anweisung, die Kombination der beiden put() Anrufe.

Es ist für eine einfacheren Tests in einer Hilfsmethode isoliert.

public static void main(String[] args) { 
    Map<NGram, Integer> countMap = new HashMap<>(); 
    add(countMap, new NGram("Foo")); 
    add(countMap, new NGram("Bar")); 
    add(countMap, new NGram("Foo", "Bar")); 
    add(countMap, new NGram("This", "is", "a", "test")); 
    add(countMap, new NGram("Bar")); 
    System.out.println(countMap); 
} 
private static void add(Map<NGram, Integer> countMap, NGram ng) { 
    Integer counter = countMap.get(ng); 
    countMap.put(ng, (counter != null ? counter + 1 : 1)); 
} 

OUTPUT

{Bar=2, Foo=1, This is a test=1, Foo Bar=1} 

Wie Sie die doppelte Bar Wert zweimal gezählt sehen können, ist.


Wenn Sie mit vielen doppelten Objekten am Ende, das heißt mit einem hohen Zählwerte, dann ist ein unveränderliches Integer Objekt als Zähler verwendet für die Leistung nicht groß. Sie können eine int[1] verwenden, aber eine veränderbare Helferklasse ist besser, da sie eine gute toString() Implementierung bereitstellen kann.

private static void add(Map<NGram, Counter> countMap, NGram ng) { 
    Counter counter = countMap.get(ng); 
    if (counter == null) 
     countMap.put(ng, new Counter(1)); 
    else 
     counter.increment(); 
} 
private static final class Counter { 
    private int count; 
    public Counter(int count) { 
     this.count = count; 
    } 
    public void increment() { 
     this.count++; 
    } 
    public int get() { 
     return this.count; 
    } 
    @Override 
    public String toString() { 
     return Integer.toString(this.count); 
    } 
} 
+0

Ja, das funktioniert ... also werde ich in die Ecke gehen und mich selbst beschämen.- – ThatsMe

Verwandte Themen