【问题标题】:How to filter items from a std::map? [duplicate]如何从 std::map 中过滤项目? [复制]
【发布时间】:2010-09-15 21:10:39
【问题描述】:

我大致有以下代码。这可以做得更好或更有效吗?也许使用std::remove_if?您可以在遍历地图时从地图中删除项目吗?我们可以避免使用临时地图吗?

typedef std::map<Action, What> Actions;
static Actions _actions;

bool expired(const Actions::value_type &action)
{
  return <something>;
}

void bar(const Actions::value_type &action)
{
  // do some stuff
}

void foo()
{
  // loop the actions finding expired items
  Actions actions;
  BOOST_FOREACH(Actions::value_type &action, _actions)
  {
    if (expired(action))
      bar(action);
    else
      actions[action.first]=action.second;
    }
  }
  actions.swap(_actions);
}

【问题讨论】:

    标签: c++ boost stl


    【解决方案1】:

    Mark Ransom 算法的一种变体,但不需要临时算法。

    for(Actions::iterator it = _actions.begin();it != _actions.end();)
    {
        if (expired(*it))
        {
            bar(*it);
            _actions.erase(it++);  // Note the post increment here.
                                   // This increments 'it' and returns a copy of
                                   // the original 'it' to be used by erase()
        }
        else
        {
            ++it;  // Use Pre-Increment here as it is more effecient
                   // Because no copy of it is required.
        }
    }
    

    【讨论】:

    • 做得很好。太糟糕了,我花了 2 1/2 年才看到这种改进。
    • @Mark Ransom:没关系。我们仍然可以称它为Mark Ransom technique :-)
    • 感谢@Mark Ransom 和@Martin。该代码中有这么多信息。我一直想知道为什么 Stroustrup 更喜欢 ++i。
    【解决方案2】:

    您可以使用erase(),但我不知道BOOST_FOREACH 将如何处理无效的迭代器。 documentation for map::erase 声明只有被擦除的迭代器会失效,其他的应该没问题。以下是我将如何重组内部循环:

    Actions::iterator it = _actions.begin();
    while (it != _actions.end())
    {
      if (expired(*it))
      {
        bar(*it);
        Actions::iterator toerase = it;
        ++it;
        _actions.erase(toerase);
      }
      else
        ++it;
    }
    

    【讨论】:

    • 请不要使用这个(过时的)答案中提供的解决方案。它的行为取决于容器。由于 C++11 有一个更好的解决方案:erase 返回一个新的迭代器到被擦除元素之后的元素。 for(auto it = container.begin(); it != container.end(); ) if (to_delete(it)) it = container.erase(it); else ++it;
    【解决方案3】:

    似乎没有人知道的是,当在任何容器上使用时,erase 会返回一个新的、保证有效的迭代器。

    Actions::iterator it = _actions.begin();
    while (it != _actions.end())
    {
      if (expired(*it))
      {
        bar(*it);
        it = _actions::erase(it);
      }
      else
        ++it;
    }
    

    我相信,在这种情况下存储 actions.end() 可能不是一个好计划,因为无法保证迭代器的稳定性。

    【讨论】:

    • 根据我在回复中链接的文档,擦除返回 void,并且您的代码示例将无法编译。
    • 我认为这是VC++中的一个扩展
    • 这不适用于任何容器,仅适用于序列模型的容器。对于作为关联容器模型的容器,erase 的返回类型为 void。
    • 看起来 map::erase() 也必须返回迭代器:open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2728.html#130
    • 随着 C++11 的出现,这个答案现在是正确的。 +1
    【解决方案4】:

    如果想法是删除过期项目,为什么不使用map::erase?这样,您只需删除不再需要的元素,而无需使用您想要保留的所有元素重建整个副本。

    这样做的方法是保存指向要擦除的元素的迭代器,然后在迭代结束后将它们全部擦除。

    或者,您可以保存访问过的元素,移动到下一个元素,然后删除临时元素。但是,在您的情况下,循环边界会变得混乱,因此您必须自己微调迭代。

    根据 expired() 的实现方式,可能还有其他更好的方法。例如,如果您正在跟踪时间戳作为地图的键(如 expired() 所暗示的那样?),您可以对当前时间戳执行 upper_bound,并且范围 [ begin(), upper_bound() ) 中的所有元素都需要被处理和擦除。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-02-15
      • 2021-12-22
      • 1970-01-01
      • 2020-03-30
      • 2014-06-01
      • 1970-01-01
      • 1970-01-01
      • 2012-06-10
      相关资源
      最近更新 更多