【问题标题】:Efficient algorithm to remove any map that is contained in another map from a collection of maps从地图集合中删除另一个地图中包含的任何地图的有效算法
【发布时间】:2009-11-23 18:13:12
【问题描述】:

我已经设置了一组唯一的地图(目前是 Java HashMaps),并希望从中删除任何完全由该集中其他地图包含的地图(即,如果 m.entrySet() 是一个n.entrySet() 的子集,用于 s 中的其他 n。)

我有一个 n^2 算法,但它太慢了。有没有更有效的方法来做到这一点?

编辑:

如果有帮助的话,可能的键集很小。

这是一个低效的参考实现:

public void removeSubmaps(Set<Map> s) {
    Set<Map> toRemove = new HashSet<Map>();
    for (Map a: s) {
        for (Map b : s) {
            if (a.entrySet().containsAll(b.entrySet()))
                toRemove.add(b);
        }
    }
    s.removeAll(toRemove);    
}

【问题讨论】:

  • 如果ms 的严格子集,您只想从s 中删除m 的映射?
  • 如果我的输入是 {{a:1, b:1},{a:1}} 我想要输出 {{a:1, b:1}} 因为 {a:1}是 {a:1, b:1} 的子图
  • 这更像是 O(n^2*m),其中 m 是最大 Map 的大小
  • 如果输入是{{a: 1, b: 1}, {c: 1}, {b: 1, c: 1}},你只想要{{a: 1, b : 1} , {c: 1}} 还是全部 3 个?如果是前者,你到底需要这个做什么?
  • 输入 {{a: 1, b: 1}, {c: 1}, {b: 1, c: 1}} 应该给出 {{a: 1, b: 1} , {b: 1, c: 1}} 因为 {c: 1} 是 {b: 1, c: 1} 的子图

标签: algorithm map set


【解决方案1】:

除了 n^2 算法之外,我不确定我是否可以做任何事情,但我有一个捷径可以让它更快。使用每个地图的长度制作地图列表并对其进行排序。地图的正确子集必须更短或等于您要比较的地图 - 永远不需要与列表中更高的地图进行比较。

【讨论】:

  • 谢谢 - 我虽然有这个,但它并没有多大帮助。我现在正在查看的一个典型案例有 10000 个大小为 3 的地图和 40000 个大小为 4 的地图。所以我仍然需要进行 400m 比较。优于 2500m 比较,但还不够好......
【解决方案2】:

这是另一个尝试。

将所有地图分解为键、值、地图编号的列表。按键和值对列表进行排序。浏览列表,对于每组键/值匹配,创建所有映射编号对的排列 - 这些都是潜在的子集。当你有最终的配对列表时,按地图编号排序。浏览第二个列表,并计算每对出现的次数 - 如果数字与其中一张地图的大小匹配,则您找到了一个子集。

【讨论】:

  • 这看起来应该可以解决问题。我要编码一下看看。好主意,马克!
  • 我会在接受答案之前对其进行编码,但谢谢!我应该指出,这将具有非常糟糕的最坏情况性能。我想到了另一种优化:而不是地图编号对的列表,而是使用这些对作为键来生成地图。每次生成一对时,增加该键的值。完成后无需对结果进行排序。
  • 啊,你说得对——我暂时不接受。我认为您所指的问题是当键/值组很大时 - 这会导致在最坏的情况下每个键/值最多生成 n^2 对。
  • 我添加了一个我自己的答案,在实践中似乎效果很好。我不确定复杂性是什么,但与我的幼稚解决方案相比,它花费的时间很少。感谢您为我指明按值索引的方向。
【解决方案3】:

编辑:我对问题的原始解释不正确,这是基于我重新阅读问题的新答案。

您可以为 HashMap 创建一个自定义散列函数,该函数返回其条目的所有散列值的乘积。对哈希值列表进行排序并从最大值开始循环,并从较小的哈希值中找到所有除数,这些是此哈希图的可能子集,在将它们标记为删除之前使用 set.containsAll() 进行确认。

这有效地将问题转换为从集合中找到可能除数的数学问题。并且您可以应用所有公约数搜索优化。

复杂度为 O(n^2),但如果许多哈希图是其他哈希图的子集,则实际花费的时间会好很多,在最佳情况下接近 O(n)(如果所有哈希图都是一个哈希图的子集) .但即使在最坏的情况下,除法计算也会比 set.containsAll() 快得多,set.containsAll() 本身是 O(n^2),其中 n 是哈希图中的项目数。

您可能还想为 hashmap 条目对象创建一个简单的哈希函数,以返回较小的数字以提高乘法/除法性能。

【讨论】:

  • 这里似乎溢出是个问题。除法和排序都会被溢出破坏。似乎可以使用布隆过滤器来实现类似的效果。
  • 可以通过为返回相对较小的数字的hashmap条目创建自定义散列函数来避免溢出;布隆过滤器看起来确实很有趣,稍微修改的版本可能会更好,好点!
【解决方案4】:

这是一个从一组集合中找到最大集合的二次 (O(N**2 / log N)) 算法:An Old Sub-Quadratic Algorithm for Finding Extremal Sets

但是,如果您了解自己的数据分布,那么在一般情况下,您可以做得更好。

【讨论】:

  • 感谢您找到这个,它看起来很相关。至少我现在知道这个算法叫什么......不幸的是,它是一个昂贵的算法。我添加的解决方案似乎对我的数据取得了很好的效果。
【解决方案5】:

这就是我最终所做的。它在我的情况下效果很好,因为通常只有少数地图共享一些价值。感谢 Mark Ransom 将我推向这个方向。

散文:按键/值对索引映射,以便每个键/值对与一组映射相关联。然后,对于每个地图: 找到与其中一个键/值对关联的最小集合;这个集合对于我的数据来说通常很小。这组地图中的每一个都是潜在的“超级地图”;没有其他地图可以是“超级地图”,因为它不包含此键/值对。在这个集合中搜索一个超级地图。最后从原始集合中删除所有已识别的子图。

private <K, V>  void removeSubmaps(Set<Map<K, V>> maps) {
    // index the maps by key/value
    List<Map<K, V>> mapList = toList(maps);
    Map<K, Map<V, List<Integer>>> values = LazyMap.create(HashMap.class, ArrayList.class);
    for (int i = 0, uniqueRowsSize = mapList.size(); i < uniqueRowsSize; i++) {
        Map<K, V> row = mapList.get(i);
        Integer idx = i;
        for (Map.Entry<K, V> entry : row.entrySet()) 
            values.get(entry.getKey()).get(entry.getValue()).add(idx);
    }

    // find submaps
    Set<Map<K, V>> toRemove = Sets.newHashSet();
    for (Map<K, V> submap : mapList) {
        // find the smallest set of maps with a matching key/value
        List<Integer> smallestList = null;
        for (Map.Entry<K, V> entry : submap.entrySet()) {
            List<Integer> list = values.get(entry.getKey()).get(entry.getValue());
            if (smallestList  == null || list.size() < smallestList.size())
                smallestList = list;
        }

        // compare with each of the maps in that set
        for (int i : smallestList) {
            Map<K, V> map = mapList.get(i);
            if (isSubmap(submap, map))
                toRemove.add(submap);
        }
    }

    maps.removeAll(toRemove);
}

private <K,V> boolean isSubmap(Map<K, V> submap, Map<K,V> map){
    if (submap.size() >= map.size())
        return false;
    for (Map.Entry<K,V> entry : submap.entrySet()) {
        V other = map.get(entry.getKey());
        if (other == null)
            return false;
        if (!other.equals(entry.getValue()))
            return false;
    }
    return true;
}

【讨论】:

  • 嗯,如果您的两张地图相同,则可能存在错误:我认为两者都会被删除。为读者练习...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-15
  • 2015-10-03
  • 1970-01-01
  • 2017-08-30
  • 1970-01-01
相关资源
最近更新 更多