【问题标题】:Hashtable key within integer interval整数区间内的哈希表键
【发布时间】:2012-06-26 16:14:22
【问题描述】:

我不知道这是否可能,但我正在尝试创建一个哈希表,其中 Interval 是一个具有 2 个整数/长值、一个开始和一个结束的类,我想做这样的事情:

Hashtable<Interval, WhateverObject> test = new Hashtable<Interval, WhateverObject>();
test.put(new Interval(100, 200), new WhateverObject());
test.get(new Interval(150, 150)) // returns the new WhateverObject i created above because 150 is betwwen 100 and 200
test.get(new Interval(250, 250)) // doesn't find the value because there is no key that contains 250 in it's interval

所以基本上我想要的是 Interval 对象中一系列值之间的键给出对应的 WhatObject。我知道我必须在区间对象中覆盖 equals() 和 hashcode(),我认为的主要问题是让所有值在 100 到 200 之间(在这个特定示例中)以提供相同的哈希。

如果可以的话,有什么想法吗?

谢谢

【问题讨论】:

  • 一张地图中的间隔是否总是相同的?
  • 基本上我正在尝试做一个字幕系统,其中范围将是每个提示点的开始和结束,所以我希望中间值获得相同的字幕对象。间隔不会改变,但它们在字幕之间有孔。
  • 那么你的Hashtable键是一个整数还是代表一个区间的两个整数?
  • 是的,间隔的开始和结束将匹配字幕提示点的开始和结束。没有间隔碰撞,它们之间只有空洞。

标签: java hashtable


【解决方案1】:

OldCurmudgeon 的解决方案非常适合我,但初始化速度很慢(70k 条目需要 20 分钟)。 如果您知道传入的项目列表已经排序(升序)并且只有非重叠间隔,则可以通过添加和使用以下构造函数使其以毫秒为单位进行初始化:

public IntervalTree(List<T> intervals, boolean constructorFlagToIndicateOrderedNonOverlappingIntervals) {
    if (intervals == null) throw new NullPointerException();

    int centerPoint = intervals.size() / 2;
    T centerInterval = intervals.get(centerPoint);
    this.intervals = new ArrayList<T>();
    this.intervals.add(centerInterval);
    this.uBound = centerInterval.getEnd();
    this.lBound = centerInterval.getStart();
    this.center = (this.uBound + this.lBound) / 2;
    List<T> toTheLeft = centerPoint < 1 ? Collections.<T>emptyList() : intervals.subList(0, centerPoint);
    this.left = toTheLeft.isEmpty() ? null : new IntervalTree<T>(toTheLeft, true);
    List<T> toTheRight = centerPoint >= intervals.size() ? Collections.<T>emptyList() : intervals.subList(centerPoint+1, intervals.size());
    this.right = toTheRight.isEmpty() ? null : new IntervalTree<T>(toTheRight, true);
}

【讨论】:

    【解决方案2】:

    要让 Hastable 或 HashMap 按预期工作,它不仅是相等的哈希码,而且 equals 方法必须返回 true。您要求的是 Interval(x, y).equals(Interval(m, n)) for m, n 在 x,y 内。由于 Interval 的任何重叠的活实例都必须如此,因此该类必须记录所有这些实例,并且确实需要实现您想要实现的目标。

    所以简而言之,答案是否定的。

    Google guava 库计划提供 RangeSet 和 Map:guava RangeSet

    对于合理的小范围,一种简单的方法是通过放置和获取间隔的各个值来专门化 HashMap。

    【讨论】:

      【解决方案3】:

      简单的 HashTable 在这里是错误的解决方案。覆盖 equals() 方法对您没有任何好处,因为 HashTable 首先通过哈希码比较键条目,而不是 equals() 方法。 equals() 方法仅在哈希码匹配后检查。

      在区间对象上创建一个散列函数很容易,但要为另一个区间内的所有可能区间生成相同的散列码则要困难得多。重写 HashTable 的 get() 方法(例如这里的 https://stackoverflow.com/a/11189075/1261844)完全否定了 HashTable 的优点,即查找时间非常快。在您扫描 HashTable 的每个成员时,您就知道您正在错误地使用 HashTable。

      我会说Using java map for range searcheshttps://stackoverflow.com/a/11189080/1261844 是更好的解决方案,但是HashTable 根本不是解决这个问题的方法。

      【讨论】:

        【解决方案4】:

        无需重新发明轮子,使用NavigableMap。示例代码:

        final NavigableMap<Integer, String> map = new TreeMap<Integer, String>();
        map.put(0, "Cry Baby");
        map.put(6, "School Time");
        map.put(16, "Got a car yet?");
        map.put(21, "Tequila anyone?");
        map.put(45, "Time to buy a corvette");
        
        System.out.println(map.floorEntry(3).getValue());
        System.out.println(map.floorEntry(10).getValue());
        System.out.println(map.floorEntry(18).getValue());
        

        输出:

        哭泣的宝贝
        上课时间
        有车了吗?

        【讨论】:

        • 是的。只要间隔中没有“洞”,这将起作用。
        • @aaronburro 即使在那时它也可以工作,只要您为“孔”插入空值(并添加更多客户端代码)
        【解决方案5】:

        我认为实现一个专门的 get-method 会容易得多。

        新方法可以是 map-wrapper-class 的一部分。

        key-class:(区间为[lower;upper[ )

        public class Interval {
            private int upper;
            private int lower;
        
            public Interval(int upper, int lower) {
                this.upper = upper;
                this.lower = lower;
            }
        
            public boolean contains(int i) {
                return i < upper && i >= lower;
            }
        
            @Override
            public boolean equals(Object obj) {
                if (obj == null) {
                    return false;
                }
                if (getClass() != obj.getClass()) {
                    return false;
                }
                final Interval other = (Interval) obj;
                if (this.upper != other.upper) {
                    return false;
                }
                if (this.lower != other.lower) {
                    return false;
                }
                return true;
            }
        
            @Override
            public int hashCode() {
                int hash = 5;
                hash = 61 * hash + this.upper;
                hash = 61 * hash + this.lower;
                return hash;
            }
        }
        

        地图类:

        public class IntervalMap<T> extends HashMap<Interval, T> {
        
            public T get(int key) {
                for (Interval iv : keySet()) {
                    if (iv.contains(key)) {
                        return super.get(iv);
                    }
                }
                return null;
            }
        }
        

        这只是一个例子,肯定可以优化,也有一些缺陷:

        例如,如果区间重叠,则无法保证知道哪个区间将用于查找,并且不保证区间不重叠!

        【讨论】:

        • 这与我的想法相似。我不明白你的 hashCode 方法。为了使这种类型的实现起作用,hashCode 必须在区间的每个值之间完美地给出相同的散列,但与其他区间不同,以便在散列表中实现最大分布。
        • 这只是普通的哈希码方法。在此示例中,您只需遍历所有间隔并检查给定的整数是否在其中。
        • 那么这与在字幕中设置间隔并制作 List 并迭代列表中的每个项目以检查它是否在间隔内有什么区别?
        【解决方案6】:

        您可以使用IntervalTree。这是我之前做的一个。

        public class IntervalTree<T extends IntervalTree.Interval> {
          // My intervals.
        
          private final List<T> intervals;
          // My center value. All my intervals contain this center.
          private final long center;
          // My interval range.
          private final long lBound;
          private final long uBound;
          // My left tree. All intervals that end below my center.
          private final IntervalTree<T> left;
          // My right tree. All intervals that start above my center.
          private final IntervalTree<T> right;
        
          public IntervalTree(List<T> intervals) {
            if (intervals == null) {
              throw new NullPointerException();
            }
        
            // Initially, my root contains all intervals.
            this.intervals = intervals;
        
            // Find my center.
            center = findCenter();
        
            /*
             * Builds lefts out of all intervals that end below my center.
             * Builds rights out of all intervals that start above my center.
             * What remains contains all the intervals that contain my center.
             */
        
            // Lefts contains all intervals that end below my center point.
            final List<T> lefts = new ArrayList<T>();
            // Rights contains all intervals that start above my center point.
            final List<T> rights = new ArrayList<T>();
        
            long uB = Long.MIN_VALUE;
            long lB = Long.MAX_VALUE;
            for (T i : intervals) {
              long start = i.getStart();
              long end = i.getEnd();
              if (end < center) {
                lefts.add(i);
              } else if (start > center) {
                rights.add(i);
              } else {
                // One of mine.
                lB = Math.min(lB, start);
                uB = Math.max(uB, end);
              }
            }
        
            // Remove all those not mine.
            intervals.removeAll(lefts);
            intervals.removeAll(rights);
            uBound = uB;
            lBound = lB;
        
            // Build the subtrees.
            left = lefts.size() > 0 ? new IntervalTree<T>(lefts) : null;
            right = rights.size() > 0 ? new IntervalTree<T>(rights) : null;
        
            // Build my ascending and descending arrays.
            /** @todo Build my ascending and descending arrays. */
          }
        
          /*
           * Returns a list of all intervals containing the point.
           */
          List<T> query(long point) {
            // Check my range.
            if (point >= lBound) {
              if (point <= uBound) {
                // In my range but remember, there may also be contributors from left or right.
                List<T> found = new ArrayList<T>();
                // Gather all intersecting ones.
                // Could be made faster (perhaps) by holding two sorted lists by start and end.
                for (T i : intervals) {
                  if (i.getStart() <= point && point <= i.getEnd()) {
                    found.add(i);
                  }
                }
        
                // Gather others.
                if (point < center && left != null) {
                  found.addAll(left.query(point));
                }
                if (point > center && right != null) {
                  found.addAll(right.query(point));
                }
        
                return found;
              } else {
                // To right.
                return right != null ? right.query(point) : Collections.<T>emptyList();
              }
            } else {
              // To left.
              return left != null ? left.query(point) : Collections.<T>emptyList();
            }
        
          }
        
          private long findCenter() {
            //return average();
            return median();
          }
        
          protected long median() {
            // Choose the median of all centers. Could choose just ends etc or anything.
            long[] points = new long[intervals.size()];
            int x = 0;
            for (T i : intervals) {
              // Take the mid point.
              points[x++] = (i.getStart() + i.getEnd()) / 2;
            }
            Arrays.sort(points);
            return points[points.length / 2];
          }
        
          /*
           * What an interval looks like.
           */
          public interface Interval {
        
            public long getStart();
        
            public long getEnd();
          }
        
          /*
           * A simple implemementation of an interval.
           */
          public static class SimpleInterval implements Interval {
        
            private final long start;
            private final long end;
        
            public SimpleInterval(long start, long end) {
              this.start = start;
              this.end = end;
            }
        
            public long getStart() {
              return start;
            }
        
            public long getEnd() {
              return end;
            }
        
            @Override
            public String toString() {
              return "{" + start + "," + end + "}";
            }
          }
        
        }
        

        【讨论】:

        • 非常感谢。然而,它的初始化速度非常慢。我花了 20 分钟从大约 70k 个条目中构建了一棵树。如果你知道你的列表是有序的并且不重叠,你可以让它更快。我在下面添加了一个带有附加构造函数的答案,我认为您应该将其添加到您的代码中。然后它会以毫秒为单位运行。
        【解决方案7】:

        这取决于您的 hashCode 实现。您可能有两个具有相同 hashCode 值的对象。
        请使用 eclipse 为你的类生成一个 hashCode 方法(没必要重新发明轮子

        【讨论】:

        • Eclipse 将生成一个hashCode,它甚至还没有开始解决 OP 的问题。这很可能在理论上甚至无法通过哈希映射解决。
        猜你喜欢
        • 2015-09-03
        • 2013-05-02
        • 2011-04-10
        • 2010-11-26
        • 1970-01-01
        • 2011-12-03
        • 1970-01-01
        • 1970-01-01
        • 2020-12-17
        相关资源
        最近更新 更多