【问题标题】:How do I merge two maps in STL and apply a function for conflicts?如何在 STL 中合并两个映射并应用函数来解决冲突?
【发布时间】:2019-04-11 20:28:08
【问题描述】:

我已经阅读了Merge two STL 地图问题,虽然它很接近,但我一直在寻找类似here 描述的功能。

简而言之,我想将两个 std::map 实例 (具有相同的键和值类型) 合并为一个,但需要注意的是,如果对象两个地图中都存在。

是否有现有的boostrange-v3std 函数可以做到这一点?如果没有,实现它的最佳方法是什么?

示例代码:

double mergePredicate(double lhs, double rhs)
{
    return lhs + rhs;
}

int main()
{
    std::map<int, double> mapA = { {0, 1.0}, {1, 2.0} };
    std::map<int, double> mapB = { {1, 1.5}, {2, 2.5} };

    // Merge maps in some way...
    merge(mapA, mapB, mergePredicate);

    // result: mapA == { {0, 1.0}, {1, 3.5}, {2, 2.5} }
    for (const auto& p : mapA) {
        std::cout << p.first << " " << p.second << std::endl;
    }
}

【问题讨论】:

    标签: c++ merge stdmap


    【解决方案1】:

    对于这种特殊情况,因为operator[]会在不存在的情况下创建一个键,所以您可以使用简单的循环将两个值相加:

    for (const auto& pair : mapB) {
        mapA[pair.first] += pair.second;
    }
    

    当你想使用一个函数时,可以在不存在键的情况下使用默认初始化的值:

    for (const auto& pair : mapB) {
        mapA[pair.first] = mergePredicate(mapA[pair.first], pair.second);
    }
    

    【讨论】:

    • 注意复杂度是O(N log N)
    • 是否有充分的理由使用 auto&amp;&amp; 而不是 std::fwd() 的内容?我不习惯像这样看到一个没有另一个。
    【解决方案2】:

    我不知道任何现有的函数可以做到这一点,但你可以利用std::mapmerge函数(live example):

    template<typename K, typename V, typename F>
    void mergeWithConflicts(std::map<K, V>& base, std::map<K, V> toMerge, F combine) {
        base.merge(toMerge);
    
        // All that's left in toMerge is conflicting keys
        for (const auto& [k, v] : toMerge) { 
            base[k] = combine(base[k], toMerge[k]);
        }
    }
    

    作为奖励,merge 的实现与您可以手动执行的操作相比相当有效,除非您使用extract 之类的方法重新实现它。它不是复制或移动元素,而是调整内部指针以将节点从一个映射移动到另一个映射。但是,这意味着它会修改另一个地图。如建议的那样,该参数是按值获取的,因此如果不再需要另一个地图,则可以将其移入,否则可以复制。

    【讨论】:

    • 注意复杂度是O(N log N)
    • @Jarod42,公平点。这取决于冲突与地图大小的预期。我相信您的线性方法也可以在重新排列节点时具有相同的好处,尽管我想说这与此版本之间的权衡在于代码复杂性。
    • @Jarod42 在您的复杂性中不应该有 2 个变量代表 2 个不同地图的长度,即 O( M ... N )?你是说merge()O(N log N) 还是全部?
    • @nonsensickle: std::map::mergeN*log(size()+N)) (虽然我会想象线性......)。然后冲突解决不是线性完成的(关于“新”base.size()),而是在O(N3 log (size() + N3))N3:冲突的大小)中完成。所以确实更准确地说,我们应该有 3 个参数 base.size()toMerge.size()conflicts.size(),但我原来的注释没有那么精确:-)。
    【解决方案3】:

    我不知道任何现有的功能,但您可以从类似于 std::merge 实现的东西中推出自己的功能以具有线性复杂性:

    template<class Map, class Merger>
    void merge(Map& dest, const Map& source, Merger merger)
    {
        auto it1 = dest.begin();
        auto it2 = source.begin();
        auto&& comp = dest.value_comp();
    
        for (; it1 != dest.end() && it2 != source.end(); ) {
            if (comp(*it1, *it2)) {
                ++it1;
            } else if (comp(*it2, *it1)) {
                dest.insert(it1, *it2); // with hint to have correct complexity
                ++it2;
            } else { // equivalent
                it1->second = merger(it1->second, it2->second);
                ++it1;
                ++it2;
            }
        }
        dest.insert(it2, source.end());
    }
    

    Demo

    【讨论】:

    • 这也是我想出的,也是一种常见的方法(参见intersection of sorted arrays)。但我觉得应该有一个库功能已经给了我这个......
    • 这太棒了!不过,我会将*it1*it2 传递给merger,以防万一密钥对合并有用。 (不过,我知道 OP 目前不需要此功能。)
    • 顺便说一句,dest.value_comp()source.value_comp() 有一个(可能非常罕见)可能有状态并且不相等。不幸的是,没有通用的方法来测试它。
    猜你喜欢
    • 2011-04-08
    • 2012-03-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-29
    • 2015-10-15
    • 2020-12-17
    • 1970-01-01
    • 2018-04-19
    相关资源
    最近更新 更多