【问题标题】:std::map insert or std::map find?std::map 插入或 std::map 查找?
【发布时间】:2010-09-10 22:56:03
【问题描述】:

假设您要在其中保留现有条目的地图。 20% 的时间,您插入的条目是新数据。使用返回的迭代器执行 std::map::find 然后 std::map::insert 是否有优势?或者尝试插入然后根据迭代器是否指示记录已插入或未插入是否更快?

【问题讨论】:

  • 我已得到纠正,并打算使用 std::map::lower_bound 而不是 std::map::find。

标签: c++ optimization stl stdmap


【解决方案1】:

答案是你都不做。相反,你想做一些 Effective STL 的第 24 条 Scott Meyers 建议的事情:

typedef map<int, int> MapType;    // Your map type may vary, just change the typedef

MapType mymap;
// Add elements to map here
int k = 4;   // assume we're searching for keys equal to 4
int v = 0;   // assume we want the value 0 associated with the key of 4

MapType::iterator lb = mymap.lower_bound(k);

if(lb != mymap.end() && !(mymap.key_comp()(k, lb->first)))
{
    // key already exists
    // update lb->second if you care to
}
else
{
    // the key does not exist in the map
    // add it to the map
    mymap.insert(lb, MapType::value_type(k, v));    // Use lb as a hint to insert,
                                                    // so it can avoid another lookup
}

【讨论】:

  • 这确实是 find 的工作原理,诀窍在于它结合了 find 和 insert 所需的搜索。当然,使用 insert 然后查看第二个返回值也是如此。
  • 两个问题:1) 使用 lower_bound 与使用 find 查找地图有何不同? 2)对于'map',当'lb!= mymap.end()'时&&的右手操作不是总是正确的吗?
  • @Richard: find() 返回 end() 如果键不存在,lower_bound 返回项目应该在的位置(这反过来可以用作插入提示)。 @puetzek:“只是插入”不会覆盖现有键的引用值吗?不确定 OP 是否希望这样做。
  • 有人知道 unordered_map 是否有类似的东西吗?
  • @peterchen map::insert 不会覆盖现有值(如果存在),请参阅cplusplus.com/reference/map/map/insert
【解决方案2】:

这个问题的答案还取决于创建您在地图中存储的值类型的成本:

typedef std::map <int, int> MapOfInts;
typedef std::pair <MapOfInts::iterator, bool> IResult;

void foo (MapOfInts & m, int k, int v) {
  IResult ir = m.insert (std::make_pair (k, v));
  if (ir.second) {
    // insertion took place (ie. new entry)
  }
  else if ( replaceEntry ( ir.first->first ) ) {
    ir.first->second = v;
  }
}

对于诸如 int 之类的值类型,上面的方法比 find 后跟 insert 更有效(在没有编译器优化的情况下)。如上所述,这是因为在地图中的搜索只发生了一次。

但是,插入调用要求您已经构造了新的“值”:

class LargeDataType { /* ... */ };
typedef std::map <int, LargeDataType> MapOfLargeDataType;
typedef std::pair <MapOfLargeDataType::iterator, bool> IResult;

void foo (MapOfLargeDataType & m, int k) {

  // This call is more expensive than a find through the map:
  LargeDataType const & v = VeryExpensiveCall ( /* ... */ );

  IResult ir = m.insert (std::make_pair (k, v));
  if (ir.second) {
    // insertion took place (ie. new entry)
  }
  else if ( replaceEntry ( ir.first->first ) ) {
    ir.first->second = v;
  }
}

为了调用“插入”,我们支付了构建值类型的昂贵调用 - 根据您在问题中所说的,您 20% 的时间不会使用这个新值。在上述情况下,如果更改映射值类型不是一个选项,那么首先执行“查找”以检查我们是否需要构造元素会更有效。

或者,可以更改映射的值类型,以使用您最喜欢的智能指针类型存储数据句柄。对 insert 的调用使用空指针(构造起来非常便宜),并且仅在必要时才构造新的数据类型。

【讨论】:

    【解决方案3】:

    两者在速度上几乎没有任何差异,find 会返回一个迭代器,insert 也会这样做,并且无论如何都会搜索映射以确定条目是否已经存在。

    所以.. 这取决于个人喜好。我总是尝试插入,然后在必要时更新,但有些人不喜欢处理返回的对。

    【讨论】:

      【解决方案4】:

      我认为,如果您先查找然后插入,则额外费用将是您找不到密钥并在之后执行插入。这有点像按字母顺序浏览书籍但没有找到书籍,然后再次浏览书籍以查看将其插入的位置。它归结为您将如何处理密钥以及它们是否不断变化。现在有一些灵活性,如果你没有找到它,你可以记录,异常,做任何你想做的事情......

      【讨论】:

      • 我们可以用map::lower_bound代替map::find,但是比较繁琐。
      【解决方案5】:

      如果您关心效率,您可能需要查看hash_map<>

      通常 map 被实现为二叉树。根据您的需要,hash_map 可能更有效。

      【讨论】:

      • 会喜欢的。但是 C++ 标准库中没有 hash_map,而且 PHB 不允许在此之外的代码。
      • std::tr1::unordered_map 是建议添加到下一个标准的哈希映射,并且应该在大多数当前的 STL 实现中可用。
      【解决方案6】:

      我似乎没有足够的分数来发表评论,但勾选的答案对我来说似乎是冗长的 - 当您认为 insert 无论如何都会返回迭代器时,为什么要搜索 lower_bound,当您可以使用迭代器返回。奇怪。

      【讨论】:

      • 因为(当然是 C++11 之前的)使用插入意味着您仍然必须创建一个 std::map::value_type 对象,所以接受的答案甚至可以避免。
      【解决方案7】:

      任何有关效率的答案都取决于您的 STL 的确切实施。唯一确定的方法是对它进行双向基准测试。我猜差异不大,所以根据你喜欢的风格来决定。

      【讨论】:

      • 这并不完全正确。 STL 与大多数其他库的不同之处在于,它为大多数操作提供了明确的 big-O 要求。无论函数使用什么实现来实现 O(log n) 行为,2 * O(log n) 和 1 * O(log n) 之间都存在一定的差异。这种差异在您的平台上是否显着是另一个问题。但差异永远存在。
      • @srm 定义 big-O 需求仍然没有告诉您操作将花费多长时间的绝对值。您所说的保证差异不存在。
      【解决方案8】:

      map[ key ] - 让 stl 整理出来。这样可以最有效地传达您的意图。

      是的,很公平。

      如果您先查找然后再插入,则当您错过时,您将执行 2 x O(log N),因为查找只会让您知道是否需要插入而不是插入应该去的位置(lower_bound 可能会有所帮助你在那里)。直接插入然后检查结果就是我要走的路。

      【讨论】:

      • 不,如果条目存在,则返回对现有条目的引用。
      • -1 对于这个答案。正如 Kris K 所说,使用 map[key]=value 将覆盖现有条目,而不是按照问题的要求“保留”它。您无法使用 map[key] 测试是否存在,因为如果 key 不存在,它将返回一个默认构造的对象,并将其创建为 key 的条目
      • 重点是测试地图是否已经填充,如果不存在则仅添加/覆盖。使用 map[key] 假定该值始终存在。
      猜你喜欢
      • 1970-01-01
      • 2019-08-19
      • 1970-01-01
      • 1970-01-01
      • 2022-10-22
      • 2019-05-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多