如何使用两个数字作为Map键

时间:2009-09-16 03:40:38

标签: java performance data-structures collections key

我有两个号码,我想将它们一起用作Map中的键。目前,我正在连接他们的字符串表示。例如,假设密钥号是4和12.我使用:

String key = 4 + "," + 12;

地图声明为Map<String, Object>

我认为这太糟糕了!我喜欢使用String之外的其他东西作为关键!我想要以最快的方式创建这些密钥。

谁有个好主意?

8 个答案:

答案 0 :(得分:16)

创建一个包含两个数字的对象并将其用作键。例如:

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>();

如果您更喜欢数学方法,请参阅this StackOverflow answer

答案 1 :(得分:14)

您应该使用java.awt.Dimension作为密钥。

尺寸键=新尺寸(4,12);

Dimension有一个非常好的hashCode()方法,它为每对正整数产生不同的hashCode,因此(4,12)和(12,4)的hashCodes是不同的。因此,这些可以快速实例化并生成非常好的hashCodes。

我希望他们让这个类成为不可变的,但你可以在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的顺序执行hashCodes,您将看到模式。

以下是生成此hashCode的代码:

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()方法。 (当然,通过计算构造函数中的hashCode,您可以在任何不可变类中快速编写hashCodes。)

如果你不能忍受这些限制,你只需要保存参数。

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方法,可以正常工作。此方法将从两个短值生成散列键。限制是您的输入需要是正值短值。这保证不会溢出,并且通过将中间和转换为long,它具有比前一个方法更宽的范围:它适用于所有正的短值。

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;
}

答案 2 :(得分:9)

如果您使用对象解决方案,确保您的密钥对象是不可变的

否则,如果某人改变了该值,它不仅不再等于其他明显相同的值,而且存储在地图中的哈希码将不再与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

答案 3 :(得分:6)

你可以像这样存储两个整数,

   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();
    }
} 

答案 4 :(得分:1)

这个问题的实际答案是:

hashCode = a + b * 17;

...其中a,b和hashCode都是整数。 17只是一个任意的素数。你的哈希值不是唯一的,但没关系。在Java标准库中使用了那种东西。

答案 5 :(得分:0)

另一种方法是使用嵌套地图:

Map<Integer,Map<Integer,Object>>

这里没有创建密钥的开销。但是,您需要更多的开销来正确创建和检索条目,并且您需要始终使用map-accesses来查找您要查找的对象。

答案 6 :(得分:0)

为什么要编写所有额外的代码来创建一个你不需要的完整的类比使用简单的String更好?计算该类实例的哈希码会比String更快吗?我不这么认为。

除非您在极其有限的计算能力环境中运行,否则制作和散列字符串的开销不应明显大于实例化自定义类的开销。

我想最快的方法就是像ZZ Coder建议的那样简单地将整数打包成一个Long,但无论如何,我不认为速度增长是巨大的。

答案 7 :(得分:-5)

您需要编写正确的equals和hashcode方法,否则会产生一些错误。