11

単語を表す循環リンク リスト データ構造を設定しました。リスト内の各要素は単語の文字です。私の質問の一番下には、リストのクラス定義とリストの要素があります。

リストデータ構造の目的は、循環ワードを比較できるようにすることです。つまり...「picture」と「turepic」は同じ循環語なので、2 つのリストは等しくなります。

だから私equals()は 2 つのリストを比較するときにオーバーライドしequals()ますhashCode()。しかし、私はそれを行う方法について本当に良い考えを持っていません。

設定したものに対して適切な hashCode を定義するにはどうすればよいですか? どのようなことを考慮する必要がありますか? 「picture」と「turepic」の例では、2 つのリストが等しいため、hashCode が同じである必要があります。何か案は?

ありがとう、フリスト

public class Letter {
 char value;
 Letter theNextNode;

 /**
  * Default constructor for an element of the list.
  * 
  * @param theCharacter - the value for this node.
  */
 Letter(char theCharacter) {
  this.value = theCharacter;
 }
}


public class CircularWord {

 /*
  * Class Variables
  */
 Letter head;
 Letter tail;
 Letter theCurrentNode;

 int iNumberOfElements;


 /**
  * Default Constructor. All characters that make up 'theWord' are stored in a 
  * circular linked list structure where the tail's NEXT is the head. 
  */
 public CircularWord(String theWord) {

  char[] theCharacters = theWord.toCharArray();

  for (int iIndex = 0; iIndex < theCharacters.length; iIndex++) {
   this.addElement(theCharacters[iIndex]);
  }

  this.theCurrentNode = head;
  this.iNumberOfElements = theCharacters.length;
 }
}
4

7 に答える 7

15

したがって、"picture" と "turepic" で同じ結果が得られるハッシュコード計算が必要ですが、(できれば) "eruptic" などのハッシュコードとは異なります。したがって、単語に含まれる文字のハッシュコードを単純に合計するだけでは十分ではありません。位置情報も必要ですが、単語の実際の順列とは無関係である必要があります。「等価クラス」を定義し、クラスの各メンバーに対して常に同じハッシュコードを計算する必要があります。

これを実現する最も簡単な方法は、同値クラスの特定のメンバーを選択し、すべての同値語に対して常にそのバリエーションのハッシュコードを使用することです。たとえば、最初のバリアントをアルファベット順に選択します(簡潔にまとめてくれた@Michaelに感謝します)。「picture」などの場合は、「cturepi」になります。「picture」と「turepic」の両方 (および他のすべての同等のバリエーション) は、「cturepi」のハッシュ コードを返す必要があります。そのハッシュ コードは、標準の LinkedList メソッドまたはその他の推奨される方法で計算できます。

この計算は非常にコストがかかると言う人もいるかもしれません。確かにそうですが、最初の計算だけがコストがかかるように、結果をキャッシュすることはできます。そして、最初のアルファベット順のバリアントの選択は、一般的なケースではかなり最適化できると思います(特定の等価クラスですべての順列を生成し、それらを並べ替えて最初のものを選択するという簡単な解決策と比較して)。

たとえば、多くの単語では、アルファベット順の最初の文字が一意です (「picture」はその 1 つです。アルファベット順の最初の文字は 'c' で、'c' は 1 つしかありません)。したがって、それを見つけて、そこからハッシュコードを計算するだけです。一意でない場合は、違いが見つかるまで (またはロールオーバーするまで)、その後の 2 番目、3 番目などの文字を比較する必要があります。

更新 2 - 例

  • 「abracadabra」には 5 つの「a」が含まれています。「a」の後の 2 番目の文字は、それぞれ「b」、「c」、「d」、「b」、および「a」です。したがって、2 回目の比較では、辞書編集的に最小のバリエーションは「aabracadabr」であると結論付けることができます。
  • 「abab」には 2 つの 'a' と、それぞれの後に 'b' が含まれています (その後、ロールオーバーして再び 'a' に到達すると、クエストはそこで終了します)。したがって、辞書編集的に最小の2つの同一のバリエーションがあります。しかし、それらは同一であるため、明らかに同じハッシュコードを生成します。

更新:最終的には、ハッシュコードが実際にどれだけ必要かということになります。つまり、循環リストを or のような連想コレクションに入れる予定がありますSetMap? そうでない場合は、単純な、または簡単なハッシュ メソッドを使用できます。しかし、連想コレクションを頻繁に使用すると、単純なハッシュ実装では多くの衝突が発生するため、最適なパフォーマンスが得られません。この場合、このハッシュ方式を実装して、パフォーマンスに見合うかどうかを測定してみる価値があります。

更新 3: サンプル コード

Letterは基本的に上記と同じままです。フィールドのみを作成し、名前をprivateに変更theNextNodenext、必要に応じてゲッター/セッターを追加しました。

ではさらにCircularWordいくつか変更を加えtailましtheCurrentNodelast.next == head。コンストラクターtoStringequalsは、ハッシュコードの計算には関係ないため、簡単にするために省略されています。

public class CircularWord {
    private final Letter head;
    private final int numberOfElements;
    
    // constructor, toString(), equals() omitted

    @Override
    public int hashCode() {
        return hashCodeStartingFrom(getStartOfSmallestRotation());
    }

    private Letter getStartOfSmallestRotation() {
        if (head == null) {
            return null;
        }
        Set<Letter> candidates = allLetters();
        int counter = numberOfElements;

        while (candidates.size() > 1 && counter > 0) {
            candidates = selectSmallestSuccessors(candidates);
            counter--;
        }
        return rollOverToStart(counter, candidates.iterator().next());
    }

    private Set<Letter> allLetters() {
        Set<Letter> letters = new LinkedHashSet<Letter>();
        Letter letter = head;

        for (int i = 0; i < numberOfElements; i++) {
            letters.add(letter);
            letter = letter.getNext();
        }
        return letters;
    }

    private Set<Letter> selectSmallestSuccessors(Set<Letter> candidates) {
        Set<Letter> smallestSuccessors = new LinkedHashSet<Letter>();

        char min = Character.MAX_VALUE;
        for (Letter letter : candidates) {
            Letter nextLetter = letter.getNext();
            if (nextLetter.getValue() < min) {
                min = nextLetter.getValue();
                smallestSuccessors.clear();
            }
            if (nextLetter.getValue() == min) {
                smallestSuccessors.add(nextLetter);
            }
        }
        return smallestSuccessors;
    }

    private Letter rollOverToStart(int counter, Letter lastCandidate) {
        for (; counter >= 0; counter--) {
            lastCandidate = lastCandidate.getNext();
        }
        return lastCandidate;
    }

    private int hashCodeStartingFrom(Letter startFrom) {
        int hash = 0;
        Letter letter = startFrom;
        for (int i = 0; i < numberOfElements; i++) {
            hash = 31 * hash + letter.getValue();
            letter = letter.getNext();
        }
        return hash;
    }

}

単語の辞書編集的に最小のローテーションを見つけるために実装されたアルゴリズムgetStartOfSmallestRotationは、基本的には上記で説明したものです。各ローテーションの辞書編集的に最小の 1 番目、2 番目、3 番目などの文字を比較して選択し、残りの候補が 1 つだけになるまで大きい文字を削除します。 、または単語をロールオーバーします。リストは循環しているため、カウンターを使用して無限ループを回避します。

最終的に、候補が 1 つ残っている場合、それは単語の途中にある可能性があり、最小の単語ローテーションの開始点を取得する必要があります。ただし、これは片方向リストであるため、後ろに下がるのは面倒です。幸いなことに、カウンターは私をうまく助けてくれます。これまでに比較した文字数が記録されていますが、循環リストでは、ロールオーバーする前に前方に移動できる文字数に相当します。したがって、探している最小限の単語のローテーションの先頭に戻るために何文字進むべきかがわかります。

これが誰かの役に立てば幸いです-少なくとも書くのは楽しかったです:-)

于 2010-09-19T19:52:31.547 に答える
5

実際に hashCodes を使用する必要がありますか? オブジェクト メンバーをいかなる種類のハッシュ構造にも配置するつもりがない場合は、問題を無視できます。

public int hashCode() {
    return 5;
}

これは、等しいインスタンスが等しいハッシュ コードを持つという要件を満たします。より良いハッシュ分散が必要だとわかっていない限り、これはおそらく私自身のニーズには十分に機能するでしょう。

しかし、ハッシュの分散を改善するアイデアがあると思います。擬似コード:

hash = 0
for each rotation
    hash += hash(permutation)
end
hash %= MAX_HASH

hash() は O(n) である可能性が高いため、このアルゴリズムは O(n^2) であり、これは少し遅いですが、ハッシュは等価性テストに使用される方法を反映しており、ハッシュ コードの分布はおそらくかなり適切です。可換な他のカーネル (prod、xor) は、この例で使用されている合計と同様に機能します。

于 2010-09-19T20:47:06.753 に答える
3
int hashcode() {
    int hash = 0;
    for (c in list) {
        hash += c * c;
    }
    return hash;
}

+ は交換可能であるため、等しい単語は等しいハッシュコードを持ちます。ハッシュコードはそれほど差別的ではありません (すべての文字順列が同じハッシュ コードを取得します) が、通常、HashSet に多くの順列を入れない限り、うまく機能するはずです。

注:個別の文字の衝突を少なくするためにc * c、単に追加するのではなく追加します。c

注 2: ハッシュ コードが等しい不等リストは、ハッシュ コードの規約に違反しません。このような「衝突」はパフォーマンスを低下させるため回避する必要がありますが、プログラムの正確性を脅かすものではありません。一般に、衝突は避けられませんが、私の答えよりも衝突を避けることは確かに可能ですが、そうすることでハッシュコードの計算コストが高くなり、パフォーマンスの向上以上のものになる可能性があります。

于 2010-09-19T20:02:01.990 に答える
0

ハッシュコードは一意ではないことに注意してください。2 つの異なるオブジェクトがまったく同じ値にハッシュされる可能性があります。したがって、ハッシュコードは同等性を判断するには不十分です。equals() で実際の比較を行う必要があります。[投機的なコメントは削除されました。ああ、神様]

hashcode() は、常に定数を返すことができます。これはパフォーマンスに影響を与える可能性がありますが、完全に有効です。他のすべてが完了したら、より効率的な hashcode() アルゴリズムに取り組むことができます。

これは良い記事です。「遅延ハッシュコード」セクションに注意してください。

于 2010-09-19T20:40:25.373 に答える
0

リスト内のすべての要素のハッシュコードの合計に任意の値を掛けたものはどうでしょうか?

何かのようなもの

hashCode = 1;
for (char c : myChars) {
    hashCode += 31 * c;
}
于 2010-09-19T19:48:17.533 に答える
0

私はあなたの質問を読み違えました-「picture」と「turepic」に異なるハッシュコードが必要だと思いました。この場合、等しい 2 つのオブジェクトは同じハッシュ コードを持つ必要があるという事実からヒントを得ることができると思いますが、同じハッシュ コードを持つ 2 つのオブジェクトは必ずしも等しいとは限りません。

したがって、「picture」と「turepic」が同じハッシュ コードを持つことを保証する Vivien のソリューションを使用できます。ただし、これは、「picture」と「pitcure」も同じハッシュ コードを持つことを意味します。この場合、あなたのequalsメソッドはよりスマートである必要があり、文字の 2 つのリストが実際に同じ単語を表しているかどうかを判断する必要があります。基本的に、equals メソッドは、「picture」/「turepic」および「pitcure」から得られる衝突を解決するのに役立ちます。

于 2010-09-19T19:54:36.893 に答える
0
  1. equals()hashCode()for を定義しますLetter。フィールドのみを使用してこれを行いcharます。
  2. については、から までを繰り返し、 のそれぞれの値をXOR することによってCircularWord実装します。最後に、結果を何らかの定数で XOR します。hashCode()headtailLetter.hashCode

別の方法は、次のように表現して、循環語を正規化することです。

public class CircularWord {

    private static Set<String> canonicalWords = new HashSet<String>();
    private String canonicalWord;
    private int offset;

    public CircularWord(String word) {
        // Looks for an equal cirular word in the set (according to our definition)
        // If found, set canonicalWord to it and calculate the offset.
        // If not found, put the word in the set, set canonical word to our argument and set offset to 0.
    }
    // Implementation of CircularWord methods using
    // canonicalWord and offset
}

次に、実装equals()hashCode()委任することによってString実装します。

于 2010-09-19T20:04:07.053 に答える