Разобранный хеш-код - PullRequest
       72

Разобранный хеш-код

1 голос
/ 24 августа 2011

У меня следующая ситуация: у меня много BST s, и я хочу объединить изоморфные поддеревья для экономии места.

Я хэширую узлы дерева двоичного поиска в «уникальную таблицу» - в основном хеш узлов BST.

Узлы, которые имеют одинаковые левый и правый дочерний элемент и один и тот же ключ, имеют одинаковый хэш-код, и я соответствующим образом переопределил equals для класса узла.

Все работает, за исключением того, что вычисление хэша обходится дорого - это включает вычисление хэша для дочерних узлов.

Я хотел бы кэшировать хэшированное значение для узла. Проблема, с которой я столкнулся, заключается в том, что естественный способ сделать это - HashMap из узлов в целые числа, сам будет вызывать хеш-функцию на узлах.

Я справился с этим, объявив новое поле в узлах, которое я использую для хранения хеш-кода. Однако я чувствую, что это не правильное решение.

Что я действительно хочу, так это сопоставить узлы с их хеш-кодами, используя хеш, который использует адрес узла. Я думал, что смогу сделать это, сделав HashMap и приведя узлы к объекту, который затем вызовет метод hashCode для объектов, но это не сработало (вставки в хеш по-прежнему вызывают хеш-функции узлов и функции равенства.

Буду признателен за лучший способ реализации узла для кэширования хеш-кода. Я приложил код ниже, иллюстрирующий то, что происходит ниже.

import java.util.Set;
import java.util.HashSet;
import java.util.Map;
import java.util.HashMap;

class Bst {

  int key;
  String name;
  Bst left;
  Bst right;

  public Bst( int k, String name, Bst l, Bst r ) {
    this.key = k;
    this.name = name;
    this.left = l;
    this.right = r;
  }

  public String toString() {
    String l = "";
    String r = "";
    if ( left != null ) {
      l = left.toString();
    }
    if ( right != null ) {
      r = right.toString();
    }
    return key + ":" + name + ":" + l + ":" + r;
  }

 @Override
  public boolean equals( Object o ) {
    System.out.println("calling Bst's equals");
    if ( o == null ) {
      return false;
    }
    if ( !(o instanceof Bst) ) {
      return false;
    }
    Bst n = (Bst) o;

    if ( n == null || n.key != key ) {
      return false;
    } else if ( n.left != null && left == null || n.right != null && right == null ||
                n.left == null & left != null || n.right == null && right != null ) {
      return false;
    } else if ( n.left != null && n.right == null ) {
      return n.left.equals( left );
    } else if ( n.left != null && n.right != null ) {
      return n.left.equals( left ) && n.right.equals( right );
    } else if ( n.left == null && n.right != null ) {
      return n.right.equals( right );
    } else {
      return true;
    }
  }

  @Override
  public int hashCode() {
    // the real hash function is more complex, entails
    // calling hashCode on children if they are not null
    System.out.println("calling Bst's hashCode");
    return key;
  }
}

public class Hashing {

  static void p(String s) { System.out.println(s); }

  public static void main( String [] args ) {
    Set<Bst> aSet = new HashSet<Bst>();
    Bst a = new Bst(1, "a", null, null );
    Bst b = new Bst(2, "b", null, null );
    Bst c = new Bst(3, "c", null, null );
    Bst d = new Bst(1, "d", null, null );

    a.left = b;
    a.right = c;
    d.left = b;
    d.right = c;

    aSet.add( a );
    if ( aSet.contains( d ) ) {
      p("d is a member of aSet");
    } else {
      p("d is a not member of aSet");
    }

    if ( a.equals( d ) ) {
      p("a and d are equal");
    } else {
      p("a and d are not equal");
    }

    // now try casts to objects to avoid calling Bst's HashCode and equals
    Set<Object> bSet = new HashSet<Object>();
    Object foo = new Bst( a.key, a.name, a.left, a.right );
    Object bar = new Bst( a.key, a.name, a.left, a.right );
    bSet.add( foo );
    p("added foo");
   if ( bSet.contains( bar ) ) {
      p("bar is a member of bSet");
    } else {
      p("bar is a not member of bSet");
    }
  }
}

Ответы [ 4 ]

2 голосов
/ 24 августа 2011

Java встроенный IdentityHashMap делает то, что вы описываете.

Тем не менее, ответ Джона Скита больше похож на правильный путь.

2 голосов
/ 24 августа 2011

Хранение хеша в поле в узле кажется мне правильным решением. Это также то, что java.lang.String использует для собственного хеш-кода. Помимо всего прочего, это означает, что вы не можете получить записи в кеше для объектов, которые можно собирать и т. Д.

Если вы действительно хотите значение hashCode, которое будет возвращено реализацией в Object, вы можете использовать System.identityHashCode. Вы не должны полагаться на то, что это или любой другой хэш-код уникален.

Еще один момент: ваше дерево на данный момент изменчиво благодаря полям, к которым относится пакетный доступ. Если вы кешируете хеш-код при первом вызове, вы не «заметите», изменился ли он из-за изменения полей. По сути, вы не должны менять узел после того, как вы использовали его хеш-код.

2 голосов
/ 24 августа 2011

хранение хеша в поле фактически может быть эквивалентно «кэшированию» значения, так что его не нужно пересчитывать слишком часто.

Это не обязательно плохая практика, но вы должны убедиться, что вы корректно очищаете / перерасчитываете его всякий раз, когда происходит изменение, что может быть пугающим, если вам нужно уведомлять об изменении вверх или вниз по сложному графику или дереву. .

Если вы хотите использовать хеш-код, вычисленный JVM (примерно на основе «адреса RAM» объекта, даже если его значение зависит от реализации), вы можете использовать System.identityHashCode (x), который точно выполняет это, и именно то, что делает Object.hashCode.

1 голос
/ 24 августа 2011

Что я действительно хочу, так это сопоставить узлы с их хеш-кодами, используя хеш, который использует адрес узла.

Что вы подразумеваете под адресом узла? В Java нет такого понятия, и нет уникального идентификатора для известных мне объектов, таких как физический адрес в языках, не основанных на ВМ, например, C ++. Ссылки на Java не являются адресами памяти, и объекты могут перемещаться в память в любое время с помощью GC.

Я думал, что смогу сделать это, создав HashMap и приведя узлы к объекту, который затем вызовет метод hashCode для объектов, но это не сработало

Действительно, поскольку hashCode является виртуальным и переопределяется в вашем классе узлов, поэтому всегда будет вызываться реализация подкласса, независимо от того, какой у вас статический тип ссылки.

Я боюсь, что любая попытка использовать карту для кеширования хеш-значений наталкивается на ту же проблему с курицей и яйцами, что - как вы упоминаете - карте сначала нужно само значение хеш-функции.

Я не вижу лучшего способа, чем кэширование хеш-значений в узлах, как вы это делали. Однако необходимо убедиться, что кэшированные значения становятся недействительными при каждом изменении дочерних узлов. Неверно - как указывает ответ Джона, изменение хэш-кода объекта после его сохранения в карте нарушает внутреннюю целостность карты, так что этого не должно быть.

...