【问题标题】:Searching std::map in O(n) for a partial key在 O(n) 中搜索 std::map 以获取部分键
【发布时间】:2021-11-14 10:27:55
【问题描述】:

我有一张 (C++ 14) 地图

using MyTuple = tuple<A, B, C>;
map<MyTuple, MyData>

其中MyTuple 具有明显的operator&lt;(),它首先比较 A,然后是 B,然后是 C。

我想做一个O(ln N) 搜索匹配某个常量(a, b) 的键。显然,如果有的话,它们将是连续的。所以基本上我想要

map<MyTuple, MyData> my_map = GetMyData();
tuple<A, B> my_key = make_tuple(a, b);
auto iter = my_map.lower_bound_if([my_key](const MyTuple& key) {
    if (get<0>(my_key) == get<0>(key) &&
        get<1>(my_key) == get<1>(key)) {
        return true;
    }
    return false;
});
while( /* iter.first is still an (a,b) */) {
    // Do something with iter.
    // Increment iter.
}

但是map::lower_bound_if 没有函数,而map::findmap::lower_bound 却是一个完整的MyTuple。我可以(粗暴地)找到一个比我的数据中的任何值都低的 C 值,尽管随着时间的推移这很脆弱。我可以自己编写函数,尽管它可能取决于我当前的本地实现 std::map

我错过了一个明显的解决方案吗?

更新

我接受的解决方案是在比较函数(透明运算符函子)中使用部分关键数学运算,这是自 C++14 以来的新功能,并且花费的时间比我愿意承认的要长。 This article 是一个很好的心理破冰者,this SO question 在我理解后也很好。

基本见解是考虑一组对象,这些对象按对象的某个键排序。例如,一组员工的排序键为employee.id。我们希望能够搜索员工或整数 id。因此,我们创建了一个 bool operator()() 结构,其中包含我们可能想要比较的各种方式。然后重载决议完成剩下的工作。

在我的例子中,这意味着我可以提供使我的代码工作所需的严格的总排序,而且还提供一个只强制部分排序的比较器,我只用于lower_bound() 查找。因为额外的比较器不提供严格的总排序,所以它不适合,例如,find(),除非我们的意思是“查找其中之一”。

顺便说一句,我在提出我的问题后意识到这在某种程度上有点愚蠢。我想要一个O(ln n) 查找但想使用不同的排序功能。这不能保证有效:它取决于我提供的排序函数,它确实提供了一个排序,它是严格总排序的子排序。如果我不这样做,它显然会失败。这就是为什么没有O(ln n) 函数find_if() 的原因,因为它只能是线性的。

确实,透明运算符仿函数的技术很聪明,但确实取决于程序员提供的不比子排序差。

【问题讨论】:

标签: c++ stl c++14


【解决方案1】:

在c++14中你可以使用search on a partial key的重载:

struct CompareFirstTwo {
    using is_transparent = void;
    bool operator()(const tuple<A, B, C>& lhs, const tuple<A, B, C>& rhs) const ...
    bool operator()(const tuple<A, B>& lhs, const tuple<A, B, C>& rhs) const ...
    bool operator()(const tuple<A, B, C>& lhs, const tuple<A, B>& rhs) const ...
};

在调用equal_range 时使用上面的比较器忽略元组中的第三个字段。

【讨论】:

  • 你的意思是我应该用这个比较器定义std::map? (那么我必须使用多重映射。)映射的其他用途想要区分 C 的不同值。
  • 是的,您必须使用此比较器定义一个映射,但您不需要将其设为多映射,因为比较器可以处理 3 元素与 2 元素的所有合法组合元组。
  • 这让我费了一番功夫才明白,因为这不是我所期望的。对于未来的读者,this article 帮助打破了僵局。
【解决方案2】:

一种方法是为 C 设置“您喜欢的任何值”,使用 lower_bound,并注意您要查找的值可能在 lower_bound 之前和之后,因此您可能需要执行一些操作符 - - 找到第一个,就像使用 operator++ 找到最后一个一样?提前找到范围的操作数量不会改变,但是如果您想迭代它们并动态测试 的结尾,则会有提前开销。

显然,如果 C 是一个与可能的最低 C 相比的“hacky”值,这将是方便但不是必需的,因为这会使向后搜索更快,但我们已经减轻了您的脆弱风险?

另一种选择是制作您自己的地图容器。外部映射将由 索引,内部映射由 索引。

然后您可以添加自己的方法来使用 实现整个地图的索引和迭代;并使用现有的 方法在索引时返回内部映射,或者将迭代器作为 lower_bound 和 upper_bound 的结果返回,然后可以将其用作所有 的范围。

【讨论】:

    【解决方案3】:

    如果您的订单在f(xa, xb)x {xa, xb, xc} 始终为&lt; 而不是y {ya, yb, yc}(反之为&gt;),则变得容易。将其视为“先按 A 和 B 排序”。

    那么你只需要知道你的最大和最小C 值。

    lower_bound(a, b, MIN_C)upper_bound(a, b, MAX_C) 之间进行搜索。或者,如 cmets 中所建议的那样

    元组的映射到C到MyData的映射? ——弗拉德·范斯坦

    会起作用的。

    【讨论】:

    • 遗憾的是,C 没有提供一个简单的最小值,我确信它会永远有效。如果确实如此,如问题中所述,这会更容易。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-04
    • 1970-01-01
    • 2012-03-10
    • 1970-01-01
    • 1970-01-01
    • 2013-10-07
    相关资源
    最近更新 更多