C++:try_emplace与emplace

map.try_emplace>(nums.at(i), {1,i,i})

可优化为:

map.try_emplace(nums.at(i), 1, i, i)

是我低估了C++模板自动推导的能力。。然后又学到了新的知识233


昨天刷LeetCode每日一题[1],发现官方的题解有这样一段:

unordered_map> mp;
int n = nums.size();
        for (int i = 0; i < n; i++) {
            if (mp.count(nums[i])) {
                mp[nums[i]][0]++;
                mp[nums[i]][2] = i;
            } else {
                mp[nums[i]] = {1, i, i};
            }
        }

当时我就在想,这个for循环里面的if-else实现也太丑陋了,更可况还存在mp[nums[i]]={1, i, i}这种不规范的操作。(这段代码当将operator[]替换为.at()时是编译不过的,提示std::out_of_range,因为为.at()调用时会进行边界检查)

丑,那自然就像让它变好看。我最先开始想到的就是容器修改器函数:std::unordered_map::emplace():

mp.emplace>(i, {1, i, i});

这样可以避免越界行为,但乍一看好像依旧要进行类似mp.count(nums[i])的判定,而我认为这是丑陋的。

那该怎么办呢?于是我想到了std::unordered_map的另一个修改器函数:try_emplace(),它可以在容器中已存在等价于本次所要插入的key时,不做任何事

这看起来是我所需要的,但我还缺少一个核心:我该如何检测try_emplace()的执行结果?这让我一度苦恼,直到我想到返回值的存在。

经查阅语法文档[2],我发现try_emplace()返回由指向被插入元素,或若不发生插入则为既存元素的迭代器,和指代插入是否发生的bool(若发生插入则为true,否则为false)构成,简而言之,返回值类型即为std::pair

这也就意味着我可以通过 .second 获取到try_emplace()的执行结果,一切问题迎刃而解!

std::unordered_map> map;
        int s = nums.size();
        for(int i = 0; i < s; ++i)
        {
            if(!map.try_emplace>(nums.at(i), {1,i,i}).second)
            {
                ++std::get<0>(map.at(nums.at(i)));
                std::get<2>(map.at(nums.at(i))) = i;
            }                
        }

这里我用一个std::tuple类型的value代替了题解中的std::vector,因为本题只需要存储三个int类型的变量。

我将原本丑陋且不规范的代码通过一句:

if(!map.try_emplace>(nums.at(i), {1,i,i}).second)

完成了优雅、现代的重构,这令我格外舒爽。但更重要的是,这让我有了深刻的反思:过去我总是想当然的认为这些不通过返回值获取所需数据的函数,它们的返回值并不重要或可能都为void,但现在看起来并不是这样,这些函数的返回值总能在一些情况下被用到,而库的设计者早已预见了这些。


最后,有趣的是,我还发现函数emplace()也具有着相同的返回值,且当容器已含有等价Key时,同样也执行无效。也就是说我可以不使用try_emplace(),而使用更为普遍的emplace()。而事实上,当我这样尝试时,发现时空消耗巨额增加:

使用try_emplace()时

使用emplace()时

这是灾难性的,文档中对此解释道:

不同于insert或emplace,若不发生插入,则这些函数不从右值参数移动,这令操纵 value 为仅移动类型的 map ,如 std::unordered_map>更为容易。另外, try_emplace分离地处理关键和到 mapped_type的参数,不同于要求参数构造 value_type(即一个 std::pair)的emplace。
—— cppreference.com

简而言之,当执行try_emplace(Key, Val),且容器中已经含有等价的Key时,函数并不进行参数构造,而如果是执行emplace(Key, Val),那么无论何种情况总是会调用std::pair的构造函数,这导致程序产生了大量的垃圾数据,并浪费了很多时间。


一道简单难度的题,竟让我有了如此多的收获,这是我意想不到的。在感叹学无止境的同时,亦无数次感慨C++的博大精深。

当真是学海无涯。

参考

  1. ^679.数组的度 力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
  2. ^cppreference.com  std::unordered_map::try_emplace - cppreference.com

你可能感兴趣的:(c++,算法)