【问题标题】:Nested unordered_map / hash_map update in C++C++ 中的嵌套 unordered_map / hash_map 更新
【发布时间】:2017-03-19 08:11:55
【问题描述】:

我是 C++ 新手,对 unordered_map(或 hash_map)有以下问题:

#include <unordered_map>
#include <iostream>
using namespace std;

int main()
{
    unordered_map<int,int> h1;
    int temp1=0;
    h1.insert(pair<int,int>(0,temp1));
    unordered_map<int, unordered_map<int,int>> h2;
    h2.insert(pair<int, unordered_map<int,int>>(1,h1));
    unordered_map<int, unordered_map<int,int>>::iterator h2_itor=h2.find(1);
    h2_itor->second.find(0)->second++;

    unordered_map<int, unordered_map<int,int>> h3;
    for(int i=0;i<100;i++)
    {
        int first=rand()%10;
        int second=rand()%10;

        unordered_map<int, unordered_map<int,int>>::iterator h3_itor=h3.find(first);
        if(h3_itor!=h3.end())
        {
            unordered_map<int,int> submap=h3_itor->second;
            unordered_map<int,int>::iterator submap_itor=submap.find(second);
            if(submap_itor!=submap.end())
                submap_itor->second++;
            else
                submap.insert(pair<int,int>(second, 1));
        }
        else
        {
            unordered_map<int,int> submap;
            submap.insert(pair<int,int>(second,1));
            h3.insert(pair<int, unordered_map<int,int>>(first,submap));
        }
    }

    return 0;
}

输出很奇怪。对于 h1 和 h2 似乎有效,这意味着 h1 中键为 0 的值已更新(递增 1)。虽然这看起来微不足道,但对于 h3,我随机插入一些“对”(第一,第二)并使用哈希图进行计数,但计数似乎无法更新。比如可能是这样的:

insert 1 -> 7 -> 1 
 ... 
now update 1 -> 7 -> 1 to 1 -> 7 -> 2 using my code
fetch: h3.find(1)->second.find(7)->second : it's still 1 but not 2!

表示更新值不成功。我知道在 Java 中这永远不会发生。那么这个问题出在哪里呢?

【问题讨论】:

  • 我发现很难遵循测试程序的逻辑。你能把问题减少到 5 或 6 行代码吗?
  • @Richard Hodges 我已经改进了代码以使其更短。 h1 和 h2 的逻辑是将 h1 作为带有某个(随机)键的值插入到 h2 中,然后尝试更新 h1 中某个项目的值(将 h1 本身作为 h2 的一个项目)。基本思想是看是否可以更新哈希映射的子映射。 (虽然听起来微不足道。)h3 的逻辑也是一个“嵌套”的哈希映射。我在其中随机插入一些子图,以及一些随机项到这些子图中,看看值是否可以更新。
  • 修改后的程序不会尝试产生任何输出,因此预期输出(空)与实际输出(也是空)相同。这在我的书中按预期工作。请阅读minimal reproducible example

标签: c++ hashmap unordered-map


【解决方案1】:

问题出在这里:

unordered_map<int,int> submap = h3_itor->second;

这会导致整个子图被复制到新的本地submap 对象中。当它在离开作用域时被销毁时,你对它所做的所有修改都会丢失。

相反,您可以使用对要修改的实际 hashmap 元素的引用:

unordered_map<int,int> &submap = h3_itor->second;

这个&amp; 应该可以解决所有问题。

【讨论】:

  • 谢谢你:-)。有用。我不熟悉C++ O.O的语法
  • 好吧,如果你来自 Java,那么一开始它可能看起来很奇怪,但我觉得这个特殊的东西(赋值总是意味着一个副本)在 c++ 中更直接,你会习惯的它相当快。还有其他更不自然的事情要遇到:)
【解决方案2】:

这是代码第二部分的重构版本(我认为)。我还生成了一个一致的测试数据集,这样我们就可以在每次运行时重现行为(随机性是测试的禁忌)。

这是代码。有什么问题?

#include <unordered_map>
#include <iostream>

using i2i = std::unordered_map<int, int>;
using map_i_to_i2i = std::unordered_map<int, i2i>;

void test_insert(map_i_to_i2i& outer, int v1, int v2)
{
    auto iouter = outer.find(v1);
    if (iouter == outer.end()) {
        std::cout << "case c) [" << v1 << "] = [" << v2 << "] = 1\n";
        outer[v1][v2] = 1;
    }
    else {
        auto& inner = iouter->second;
        auto iinner = inner.find(v2);
        if (iinner == inner.end())
        {
            std::cout << "case b) [" << v1 << "][" << v2 << "] = 1\n";
            inner.emplace_hint(iinner, v2, 1);
        }
        else {
            std::cout << "case c) [" << v1 << "][" << v2 << "] += 1\n";
            iinner->second += 1;
        }
    }
}

int main()
{
    map_i_to_i2i h3;
    for (int passes = 0 ; passes < 3 ; ++passes)
    {
        for (int x = 0 ; x < 2 ; ++x) {
            for (int y = 0 ; y < 2 ; ++y) {
                test_insert(h3, x, y);
            }
        }
    }
    return 0;
}

【讨论】:

    【解决方案3】:

    不是一个真正的答案,只是一个 FYI:如果这不仅仅是一个使用迭代器的练习,还有一个更简单的方法来做到这一点:

    unordered_map<int, unordered_map<int,int>> h3
    h3[0][0] = 1;
    
    for (int i=0; i<100; i++ ) {
        int first=rand()%10;
        int second=rand()%10;
        h3[first][second]++;
    }
    

    这是可行的,因为如果缺少值,unordered_map::operator[] 将默认构造并插入它。对于 map,这是一个空 map,对于 int,这是零。

    如果你想要另一个默认值,你可以使用unordered_map::emplace,例如:

    unordered_map<int, unordered_map<int,int>> h3
    h3[0][0] = 2;
    
    for (int i=0; i<100; i++ ) {
        int x=rand()%10;
        int y=rand()%10;
        int& val = h3[x].emplace(y, 1).first->second;
        val *= 2;
    }
    

    是的,这有点令人困惑:emplace 如果缺少键,则插入指定的值(如果已存在则不覆盖),并返回 std::pair&lt;iterator, bool&gt;

    这里,bool 告诉你你的值是否被插入,迭代器本身是std::pair&lt;key,val&gt;* 的包装器,因此.first-&gt;second 用于获取值。

    除了更短/更易读之外,这两种方法也更有效。在您的代码中,如果该值不存在,您会进行两次查找,但以上两者都只进行一次查找。

    【讨论】:

      猜你喜欢
      • 2010-12-11
      • 1970-01-01
      • 1970-01-01
      • 2017-12-02
      • 1970-01-01
      • 2019-11-03
      • 2023-04-03
      • 1970-01-01
      • 2014-05-17
      相关资源
      最近更新 更多