【问题标题】:std::vector removing elements which fulfill some conditionsstd::vector 删除满足某些条件的元素
【发布时间】:2021-10-17 00:39:43
【问题描述】:

正如标题所说,我想删除/合并满足特定条件的向量中的对象。我的意思是我知道如何从向量中删除整数,例如值 99。

Scott Meyers 的删除成语:

vector<int> v;
v.erase(remove(v.begin(), v.end(), 99), v.end());

但是假设如果有一个包含延迟成员变量的对象向量。现在我想消除所有延迟差异仅小于特定阈值的对象,并希望将它们组合/合并到一个对象。

该过程的结果应该是一个对象向量,其中所有延迟的差应至少为指定的阈值。

【问题讨论】:

  • “我想消除所有延迟差异仅小于特定阈值的对象” -- 你能详细说明一下吗?有什么不同?容器中的其他元素?附近的元素?一些具体的价值?
  • 假设向量包含一些对象,对象o1和对象o2,它们的延迟分别为o1.delay=10o2.delay=50,因此它们的相对延迟为40。这个延迟差为太小而无法考虑,因此我想将o1o2 视为一个对象而不是两个单独的对象。
  • @Christoph 如果您想考虑多个值,remove_if 可能很难使用

标签: c++ vector stl


【解决方案1】:

std::remove_if 来救援!

99 将被 UnaryPredicate 替换,这将过滤您的延迟,我将使用 lambda 函数。

下面是例子:

v.erase(std::remove_if(
    v.begin(), v.end(),
    [](const int& x) { 
        return x > 10; // put your condition here
    }), v.end());

【讨论】:

  • 条件不能是可变的。例如,将 max 而不是 10。
  • @user1436187 呃,为什么不呢?
  • 如果你能解释一下我们如何做这样的事情会很棒:v.erase(std::remove_if( v.begin(), v.end(), [](const int&amp; x, int max) { return x &gt; max; // put your condition here }), v.end());
  • @user1436187 它必须来自外部范围。在v.erase() 调用之前定义并传递给[] 内的lambda。
【解决方案2】:

C++20 引入 std::erase_if 正是为了达到这个问题的目的。

它简化了std::vectorerase-remove idiom,并为其他标准容器实现,包括std::string

鉴于当前接受的答案中的示例:

v.erase(std::remove_if(
    v.begin(), v.end(),
    [](const int& x) { 
        return x > 10; // put your condition here
    }), v.end());

您现在可以使相同的逻辑更具可读性:

std::erase_if( v, [](const int& x){return x > 10;} );
//   container ^  ^ predicate

【讨论】:

  • 值得一提的是,标准库还为basic_stringmap 等引入了此函数的适当版本。这应该是今天的默认选择。
【解决方案3】:

一个老问题,但很受欢迎,所以我要为此添加另一个选项。

remove_if 函数保留序列的顺序。这可能非常重要。但是,如果您的程序不关心顺序,这也可能完全是浪费时间。

为了保持顺序,remove_if 需要将元素向下移动以填充被移除的元素。

让我介绍partition。它不是移动元素来填充间隙,而是将元素从末端移动到间隙中。这确实保留顺序,但它可以快得多,尤其是在大型数组中。

这是一个示例程序:

#include <algorithm>
#include <chrono>
#include <iostream>
#include <iterator>
#include <memory>
#include <string>
#include <vector>

using namespace std;

struct Event {
  chrono::nanoseconds delay;
  string name;

  friend ostream &operator<<(ostream &os, const Event &e) {
    return os << "{ \"delay\": " << e.delay.count() << ", \"name\": \""
              << e.name << "\" }";
  }
};

template <typename T>
ostream &operator<<(ostream &os, const vector<T> &container) {
  bool comma = false;
  os << "[ ";
  for (const auto &x : container) {
    if (comma)
      os << ", ";
    os << x;
    comma = true;
  }
  os << " ]";
  return os;
}

int main() {
  vector<Event> iv = {
      {0ms, "e1"},  {10ms, "e2"}, {11ms, "e3"}, {0ms, "e4"},
      {12ms, "e5"}, {8ms, "e6"},  {13ms, "e7"},
  };

  iv.erase(partition(begin(iv), end(iv),
                     [](const auto &x) { return x.delay > 0ns; }),
           end(iv));
  cout << iv << '\n';
  return 0;
}

我在 Linux 上使用 GCC 编译它,如下所示:
g++ -Wall -W -pedantic -g -O3 -std=c++17 partition-test.cpp -o partition-test

然后运行它:
./partition-test [ { "delay": 13000000, "name": "e7" }, { "delay": 10000000, "name": "e2" }, { "delay": 11000000, "name": "e3" }, { "delay": 8000000, "name": "e6" }, { "delay": 12000000, "name": "e5" } ]

我还要介绍一个有趣的命令行工具,名为jq aka JSON Query:

./partition-test | jq 
[
  {
    "delay": 13000000,
    "name": "e7"
  },
  {
    "delay": 10000000,
    "name": "e2"
  },
  {
    "delay": 11000000,
    "name": "e3"
  },
  {
    "delay": 8000000,
    "name": "e6"
  },
  {
    "delay": 12000000,
    "name": "e5"
  }
]

这也是一个非常棒的 JSON 格式化程序。使其易于阅读。

现在您可以看到“e7”和“e6”以延迟 == 0 填充已擦除的 Event 对象。并且数组不再按顺序排列。

【讨论】:

    【解决方案4】:

    使用谓词函数(C++11中的惯用方式):

    v.erase(remove_if(
                v.begin(), v.end(), bind(greater<int>(), _1, 99)),
            v.end());
    

    【讨论】:

    • funky bind,仅此一项就 +1。
    • 就我个人而言,我发现 lambdas 使 bind 大多已过时。它们更普遍有用,即使在像 bind 这样可以工作的简单情况下,lambda 也更容易理解和记住语法。
    • @BenjaminLindley 同意。另一方面,如果您没有 C++11,boost::bind 仍然有用。
    猜你喜欢
    • 1970-01-01
    • 2020-11-12
    • 2017-01-03
    • 1970-01-01
    • 2018-01-09
    • 2021-12-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多