判断无序映射的无序映射是否包含键的最简方法

37
我正在使用一个无序映射的嵌套,以便我可以使用“多键”语法引用元素:my_map[k1][k2]。是否有一种便捷的方法来在尝试访问它之前检查元素是否存在呢?如果没有,最简单的方法是什么?

3
C++20终于有了这个https://en.cppreference.com/w/cpp/container/unordered_map/contains。为什么要花这么长时间才能引入一个明显应该拥有的功能呢? - jw_
7个回答

49
如果您的目的是测试键是否存在,我不建议使用。
my_map[k1][k2]

因为operator[]会为不存在的键默认构造一个新值。

相反,我更喜欢使用std::unordered_map::find。所以如果您确定第一个键存在,但第二个键不存在,您可以这样做

if (my_map[k1].find(k2) != my_map[k1].end())
{
    // k2 exists in unordered_map for key k1
}

如果您想编写一个检查双方键是否存在的函数,那么您可以编写类似以下内容的代码。
//------------------------------------------------------------------------------
/// \brief Determines a nested map contains two keys (the outer containing the inner)
/// \param[in] data Outer-most map
/// \param[in] a    Key used to find the inner map
/// \param[in] b    Key used to find the value within the inner map
/// \return True if both keys exist, false otherwise
//------------------------------------------------------------------------------
template <class key_t, class value_t>
bool nested_key_exists(std::unordered_map<key_t, std::unordered_map<key_t, value_t>> const& data, key_t const a, key_t const b)
{
    auto itInner = data.find(a);
    if (itInner != data.end())
    {
        return itInner->second.find(b) != itInner->second.end();
    }
    return false;
}

1
如果您不确定 k1 是否存在,您需要使用 find 两次 - 一次检查 k1,另一次检查 k2 - zmb
1
еңЁиҝҷз§Қжғ…еҶөдёӢпјҢжӮЁеҸҜд»ҘдҪҝз”Ёзӯ”жЎҲеә•йғЁзҡ„nested_key_existsеҮҪж•°гҖӮеҰӮжһңдёӨдёӘй”®йғҪеӯҳеңЁпјҢеҲҷе®ғе°Ҷиҝ”еӣһtrueпјҢеҗҰеҲҷиҝ”еӣһfalseгҖӮ - Cory Kramer
@CoryKramer 谢谢,这是一个非常好的、易于理解的答案。我会测试它,然后接受。 - user997112
1
不清楚 my_map[k1] 的值是否会被缓存,因此两次查找可能会很昂贵。 - Slava
1
@Slava 不错,如果这是一个问题,从 my_map[k1] 检索到的内部映射可以存储在临时变量中,以避免两次查找。 - Cory Kramer
显示剩余3条评论

11
在C++20中,您可以使用contains方法(如果我没记错的话,已添加到所有关联容器中):
if (my_map.contains(k1) && my_map[k1].contains(k2))
{
    // do something with my_map[k1][k2]
}

2
这种方法的缺点是k1搜索被执行了两次。更好的解决方案是,在单个k1搜索的结果上执行k2搜索。 - Remy Lebeau
1
好的观点,但他们要求的是最简单的方法,而不是最快的。因此,我不确定这是否是这个问题的更好解决方案。 - pooya13

7

5
template<class M>
bool contains(M const&){return true;}
template<class M, class K, class...Ks>
bool contains(M const&m, K const&k, Ks const&...ks){
  auto it=m.find(k);
  if (it==m.end()) return false;
  return contains(it->second, ks...);
}

这将适用于每个单值关联容器。

contains(my_map, k1, k2) 如果存在一个元素k1包含k2,则返回 true。


1
类似这样的吗?(对于可变情况)
using inner_map = std::map<key_type, value_type>;
using outer_map = std::map<key_type, inner_map>

boost::optional<value_type&> 
element_for_keys(outer_map& map, const key_type& k1, const key_type& k2)
{
  auto it_outer = map.find(k1);
  if (it_outer = map.end())
    return {};
  auto &map2 = it_outer->second;
  auto it_inner = map2.find(k2);
  if (it_inner == map2.end())
    return {};

  return { it_inner->second };
}

这样调用:

auto op_value = element_for_keys(my_map, kv1, kv2);
if (op_value) {
  // use op_value.value()
}
else {
  // handle case where it does not exist
}

...或者有更像Python的方式...

try {
  auto& v = my_map.at(k1).at(k2);
  // use v
}
catch(const std::out_of_range & e) {
  // didn't find it
}

0

我不相信有一个多键语法可以检查,但最简单的方法是使用find方法。您可以编写一个简单的函数将其应用于unordered_mapunordered_maps。

reference


0

另一种方法是使用std::pair作为键,将两级哈希表转换为一级哈希表,其好处:

  • 更简单的代码和结构
  • 可能比两级哈希表更快(我们调用更少的哈希函数,获得更紧凑的内存布局,更加缓存友好)

缺点:我们有一些键冗余,因此对于具有许多重复项的大键来说,这将是一个糟糕的选择,但这种情况不会太常见,因此这里的策略仍然很有用。

std::unordered_map<std::pair<int, int>, int> map;

然后检查是否存在:

使用find并与end迭代器进行比较

map.find(std::make_pair(k0, k1)) != map.end()

使用count函数(注意不要与unordered_multimap一起使用)

map.count(std::make_pair(k0, k1)) != 0

或C++20包含:

map.contains(std::make_pair(k0, k1))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接