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

Koe*_*err 9 java collections performance key data-structures

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

String key = 4 + "," + 12;
Run Code Online (Sandbox Code Playgroud)

地图声明为Map<String, Object>.

我觉得这太糟了!我喜欢用别的东西String作为钥匙!我想要以最快的方式创建这些密钥.

谁有个好主意?

Sin*_*hot 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>();
Run Code Online (Sandbox Code Playgroud)

如果您更喜欢数学方法,请参阅此StackOverflow答案.

  • 好的 - 所以现在这显然是一个功课问题.这里的*正确*解决方案是使用一个类.该类的hashcode()方法的实现是性能发挥作用的地方. (4认同)

Mig*_*noz 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
Run Code Online (Sandbox Code Playgroud)

如果按照0到14的顺序执行hashCodes,您将看到该模式.

这是生成此hashCode的代码:

public int hashCode() {
    int sum = width + height;
    return sum * (sum + 1)/2 + width;
}
Run Code Online (Sandbox Code Playgroud)

您可能会在最后一行中识别出三角形数字的公式.这就是为什么表格的第一列包含所有三角形数字的原因.

对于速度,您应该在构造函数中计算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; }
}
Run Code Online (Sandbox Code Playgroud)

当然,如果你可能需要一个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;
  }
}
Run Code Online (Sandbox Code Playgroud)

我们将此限制为正值,因为负值将产生一些重复的哈希码.但是有了这个限制,这些是可以编写的最快的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;
}
Run Code Online (Sandbox Code Playgroud)

但这是踢球者.你根本不需要这个课程.由于公式为每对数字提供了一个唯一的整数,因此您可以将该整数用作地图关键字.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;
}
Run Code Online (Sandbox Code Playgroud)


Dav*_*les 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)));
  }
Run Code Online (Sandbox Code Playgroud)

打印:

true
true
false
false
false
Run Code Online (Sandbox Code Playgroud)


ZZ *_*der 6

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

   long n = (l << 32) | (r & 0XFFFFFFFFL);
Run Code Online (Sandbox Code Playgroud)

或者你可以使用以下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();
    }
} 
Run Code Online (Sandbox Code Playgroud)

  • 我会为这个漂亮的int - > long解决方案提供支持,但我也会将它用于使`Pair`变为可变. (2认同)