Как использовать два числа в качестве ключа карты - PullRequest
9 голосов
/ 16 сентября 2009

У меня есть два числа, и я хочу использовать их вместе как ключ в Map. В настоящее время я объединяю их строковые представления. Например, предположим, что номера клавиш 4 и 12. Я использую:

String key = 4 + "," + 12;

Карта объявлена ​​как Map<String, Object>.

Я думаю, это так плохо! Мне нравится использовать что-то кроме String в качестве ключа! Я хочу самый быстрый способ создать эти ключи.

У кого есть хорошая идея?

Ответы [ 8 ]

16 голосов
/ 16 сентября 2009

Создайте объект, который содержит два числа и используйте его в качестве ключа. Например:

class Coordinates {

  private int x;
  private int y;

  public Coordinates(int x, int y) {
     ...
  }

  // getters

  // equals and hashcode using x and y
}

Map<Coordinates, Location> locations = new HashMap<Coordinates, Location>();

Если вы предпочитаете математический подход, см. этот ответ StackOverflow .

14 голосов
/ 08 мая 2013

Вы должны использовать java.awt.Dimension в качестве ключа.

Ключ измерения = новое измерение (4, 12);

Dimension имеет очень хороший метод hashCode (), который создает разные hashCode для каждой пары натуральных чисел, так что hashCodes для (4, 12) и (12, 4) различны. Таким образом, они быстро создаются и создают очень хорошие хэш-коды.

Хотелось бы, чтобы они сделали класс неизменным, но вы можете сделать свой собственный неизменный класс по образцу Dimension.

Вот таблица, показывающая hashCode для различных значений ширины и высоты:

     0   1   2   3   4  <-- width
  +--------------------
0 |  0   2   5   9  14
1 |  1   4   8  13
2 |  3   7  12
3 |  6  11
4 | 10

^
|
height

Если вы будете следовать хеш-кодам в порядке от 0 до 14, вы увидите шаблон.

Вот код, который создает этот хэш-код:

public int hashCode() {
    int sum = width + height;
    return sum * (sum + 1)/2 + width;
}

Вы можете распознать формулу для треугольного числа внутри последней строки. Вот почему первый столбец таблицы содержит все треугольные числа.

Для скорости вы должны вычислить hashCode в конструкторе. Таким образом, весь ваш класс может выглядеть так:

public class PairHash {
  private final int hash;
  public PairHash(int a, int b) {
    int sum = a+b;
    hash = sum * (sum+1)/2 + a;
  }
  public int hashCode() { return hash; }
}

Конечно, если вам, вероятно, понадобится метод equals, но вы ограничиваете себя целыми положительными числами, которые не переполняются, вы можете добавить очень быстрый:

public class PairHash {
  // PAIR_LIMIT is 23170
  // Keeping the inputs below this level prevents overflow, and guarantees
  // the hash will be unique for each pair of positive integers. This
  // lets you use the hashCode in the equals method.
  public static final int PAIR_LIMIT = (int) (Math.sqrt(Integer.MAX_VALUE))/2;
  private final int hash;

  public PairHash(int a, int b) {
    assert a >= 0;
    assert b >= 0;
    assert a < PAIR_LIMIT;
    assert b < PAIR_LIMIT;
    int sum = a + b;
    hash = sum * (sum + 1) / 2 + a;
  }

  public int hashCode() { return hash; }

  public boolean equals(Object other) {
    if (other instanceof PairHash){
      return hash == ((PairHash) other).hash;
    }
    return false;
  }
}

Мы ограничиваем это положительными значениями, потому что отрицательные значения приведут к дублированию хеш-кодов. Но с этим ограничением это самые быстрые методы hashCode () и equals (), которые могут быть написаны. (Конечно, вы можете написать hashCodes так же быстро в любом неизменяемом классе, вычислив hashCode в конструкторе.)

Если вы не можете жить с этими ограничениями, вам просто нужно сохранить параметры.

public class PairHash {
  private final int a, b, hash;
  public PairHash(int a, int b) {
    this.a = a;
    this.b = b;
    int sum = a+b;
    hash = sum * (sum+1)/2 + a;
  }
  public int hashCode() { return hash; }
  public boolean equals(Object other) {
    if (other instanceof PairHash) {
      PairHash otherPair = (PairHash)other;
      return a == otherPair.a && b == otherPair.b;
    }
    return false;
}

Но вот кикер. Вам не нужен этот класс вообще. Поскольку формула дает вам уникальное целое число для каждой пары чисел, вы можете просто использовать это целое число в качестве ключа карты. Класс Integer имеет собственные быстрые методы equals () и hashCode, которые будут работать нормально. Этот метод генерирует хеш-ключ из двух коротких значений. Ограничение состоит в том, что ваши входные данные должны быть положительными короткими значениями. Это гарантированно не переполняет, и, приведя промежуточную сумму к длинной, она имеет более широкий диапазон, чем предыдущий метод: она работает со всеми положительными короткими значениями.

static int hashKeyFromPair(short a, short b) {
  assert a >= 0;
  assert b >= 0;
  long sum = (long) a + (long) b;
  return (int) (sum * (sum + 1) / 2) + a;
}
9 голосов
/ 16 сентября 2009

Если вы используете решение для объекта, убедитесь, что ваш ключевой объект неизменен .

В противном случае, если кто-то изменяет значение, оно не только больше не будет равно другим явно идентичным значениям, но и хеш-код, сохраненный на карте, больше не будет совпадать с возвращаемым методом hashCode(). В этот момент вы в основном SOL.

Например, используя java.awt.Point - который выглядит, на бумаге, точно так, как вы хотите - следующее:

  public static void main(String[] args) {
    Map<Point, Object> map = new HashMap<Point, Object>();

    Point key = new Point(1, 3);
    Object val = new Object();

    map.put(key, val);

    System.out.println(map.containsKey(key));
    System.out.println(map.containsKey(new Point(1, 3)));

    // equivalent to setLeft() / setRight() in ZZCoder's solution,
    // or setX() / setY() in SingleShot's
    key.setLocation(2, 4);

    System.out.println(map.containsKey(key));
    System.out.println(map.containsKey(new Point(2, 4)));
    System.out.println(map.containsKey(new Point(1, 3)));
  }

печать:

true
true
false
false
false
6 голосов
/ 16 сентября 2009

Вы можете хранить два целых числа в длинном, как это,

   long n = (l << 32) | (r & 0XFFFFFFFFL);

Или вы можете использовать следующий Pair<Integer, Integer> класс,

public class Pair<L, R> {

    private L l;
    private R r;

    public Pair() {
    }

    public Pair(L l, R r) {
        this.l = l;
        this.r = r;
    }

    public L getLeft() {
        return l;
    }

    public R getRight() {
        return r;
    }

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof Pair)) {
            return false;
        }
        Pair obj = (Pair) o;
        return l.equals(obj.l) && r.equals(obj.r);
    }

    @Override
    public int hashCode() {
        return l.hashCode() ^ r.hashCode();
    }
} 
1 голос
/ 13 мая 2010

Практический ответ на этот вопрос:

hashCode = a + b * 17;

... где a, b и hashCode - это целые числа. 17 - это произвольное простое число. Ваш хэш не будет уникальным, но это нормально. Подобные вещи используются во всей стандартной библиотеке Java.

0 голосов
/ 30 сентября 2009

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

Если вы не работаете в чрезвычайно ограниченной вычислительной среде, накладные расходы на создание и хеширование строк не должны быть заметно больше, чем на создание экземпляра вашего пользовательского класса.

Я полагаю, что самым быстрым способом было бы просто упаковать целые в один Long, как предложил ZZ Coder, но в любом случае я не ожидаю, что прирост скорости будет существенным.

0 голосов
/ 16 сентября 2009

Другой подход будет использовать вложенные карты:

Map<Integer,Map<Integer,Object>>

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

0 голосов
/ 16 сентября 2009

Вам нужно написать правильные методы eqauls и hashcode или создать несколько ошибок.

...