检查两个集合是否包含至少一个相同元素的快速方法

时间:2015-02-03 13:25:42

标签: java set treemap

我有两个TreeMaps,我想检查它们是否至少包含一个相同的键(键是字符串)。 所以我使用两个循环进行比较:

boolean found = false;
for(String key1 : map1.keySet()){
    for(String key2 : map2.keySet()){
        if(key1.equals(key2)){
            found = true;
            break;
        }
    }
    if(found){
        break;
    }
}
if(found){
    someFunction(map1, map2);
}

由于我有500,000个TreeMaps(每个大约有1000个键)并且我想要检查每个地图与其他地图,这需要很长时间。有谁知道更快的解决方案?

*编辑:我想在每次找到两张同一把钥匙的地图时调用" someFunction()" -method。我认为在所有案例中{> 1%} {> 1}

3 个答案:

答案 0 :(得分:4)

您可以尝试的一种方法是制作关键字>地图的多重映射,即迭代所有500k地图并为它们包含的每个关键字添加它们。

然后再次遍历键,如果有一个键的两个或多个地图,那些地图会共享它。

通过这种方法,复杂性应从O(n² * m)降至O(n * m)n是地图数量,m是关键数量。

粗略轮廓:

Multimap<Key, Map<Key, Value>> mapsContainingKey = ... ;//could be a Guava Multimap
//O(n * m) complexity
for(Map<Key, Value> m : largeSetOfTreeMaps ) {
  for(Key k : m.keySet() ) {
    mapsContainingKey.put( k, m );
  }
}

//O(m)
for( Entry<Key, Map<Key, Value>> entry : mapsContainingKey.entries() ) {
  Key key = entry.getKey();
  Collection<Map<Key, Value>> mapsWithSameKey = entry.getValue();
  if( mapsWithSameKey.size() > 1 ) {
    //all maps in that collection share this key
  }
}

<强>更新 我跑了一个快速的基准测试,虽然没有优化,但是有一个明显的趋势:

“天真”方法循环遍历所有地图并检查所有后续地图,以便每对只检查一次。此外,我应用了Holger建议用于比较两张地图的内容。

我在这里发布了“地图”方法。

在我的机器上获得1000张地图的结果,每张地图都有100个长度为10的随机字符串键:

naive: 11656 ms
map:     235 ms

更新2 :使用不同尺寸的更多结果:

1000张不同长度的100张地图(按键越长,碰撞越少)

key length   1        2         3         4         5        10        20
naive      417 ms  3221 ms  10937 ms  11273 ms  11357 ms  11383 ms  11706 ms
map         16 ms    43 ms     86 ms    224 ms    245 ms    210 ms    154 ms

1000个地图,每个地图的密钥数量不同,密钥长度为10(密钥越多,冲突越多)

key count    50       100       500
naive      4865 ms  11368 ms  81280 ms 
map          64 ms    206 ms    913 ms

每个1000个密钥和密钥长度10的地图数量不同(地图越多,碰撞越多)

map count    500     1000      2000
naive      6323 ms  12766 ms  47798 ms 
map         139 ms    206 ms    333 ms

正如您所看到的,地图数量对此的影响最大,其次是按键数量。

答案 1 :(得分:2)

您没有说明排序,但我假设所有TreeMap具有相同的排序。在这种情况下,您可以通过使用第二个映射的边界来减少外部迭代范围。您的内部迭代完全过时,因为您可以简单地询问地图是否包含密钥。

for(String s: map1.navigableKeySet().subSet(map2.firstKey(), true, map2.lastKey(), true)) {
    if(map2.containsKey(s)) {
        someFunction(map1, map2);
        break;
    }
}

说明:

假设您有以下地图键:

map2:    D, E, F, G, H
         |           |
       first        last
map1: A,    E,    G,   I
            |<--->|
          subset("D", true, "H", true)

此处,map2的第一个元素是"D",其最后一个元素是"H"。将这些元素作为包含边界传递给map1的navigableKeySet().subSet(…)方法时,我们会将最接近的内部集合["E", "G"]作为搜索范围,因此我们排除了"A""I"之前的排序甚至开始我们的线性搜索(请记住,这些只是示例占位符,它们可能代表大量的键)。


通过考虑更多,您可以在比较时跳过两个地图中的任意范围:

public static boolean haveCommonKeys(TreeMap<String,?> map1, TreeMap<String,?> map2) {
    if(map1.isEmpty()) return false;
    for(String s=map1.firstKey(); s!=null; ) {
        String s2=map2.ceilingKey(s);
        if(s2==null) break;
        if(s2.equals(s)) return true;
        s=map1.ceilingKey(s2);
        if(s2.equals(s)) return true;
    }
    return false;
}

在此解决方案中,我们从地图的第一个(最小)键开始,并向每个地图询问一个与我们在另一个地图中找到的值相同或更大的键。这样,我们将跳过地图的所有连续键,其他地图不包含中间键。

答案 2 :(得分:0)

创建自己的地图,其中包含一组对象的每个键。如果你在一个键上调用getter,你将得到一组对象。如果你在这个集合上调用size(),你就知道是否有多个对象映射到这个键。但你不应该将所有数据都放在一张地图上,因为这会减慢它的速度。如果可以,你可以更好地对钥匙进行排序就像在一张地图中由数字组成的所有键一样,所有键都由一个地图中的字母组成,其余部分由第三个地图组成。然后你可以检查密钥,获取属于它的地图并使用它。像这样:

public class MyMap{

private Map<String key, Set<Object>> stuff;

 public MyMap(){
  stuff = new HashMap<String key, Set<Object>>(); // Or any other map instead of HashMap
 }

 public void put(final String pKey, final Object pObject){
  Set<Object> objects = stuff.get(pKey);
  if(objects!=null)
   objects.add(pObject);
  else{
   Set<Object> objects = new HashSet<Object>();
   objects.add(pObject);
   stuff.put(pKey, objects);
  }
 }

 public Set<Object> get(String pKey){
  return stuff.get(pKey);
 }

 public void remove(String pKey){
  stuff.remove(pKey);
 }

}

但是,如果你有这么多地图,这个可能会破坏你的表现。你必须将键分开以使其更快:)你也可以使用任何其他地图/集。我使用HashSet因为我认为如果你想像你告诉我们那样进行检查,你不想将同一个对象添加到同一个密钥两次。

希望我能提供帮助:)