【问题标题】:remove_if comparing two elements in the same vectorremove_if 比较同一向量中的两个元素
【发布时间】:2023-03-09 18:40:01
【问题描述】:

我有一个对象向量。让我们称它们为“myObj”。我需要遍历 myObj 的向量并确定当前对象是否与该向量中的另一个 myObj 交互,如果其中一个对象与另一个对象交互,则删除这两个对象。

for (std::vector<myObj*>::iterator it = objects.begin(); it < objects.end(); ++it)
{
    myObj*& r1 = (*it);
    myObj*& r2 = (*(it + 1));
    if ( r1->interactsWith(r2) )
    {
        objectInteracted(r1);
        objectInteracted(r2);
        delete r1;
        delete r2;
        r1 = NULL;
        r2 = NULL;
        ++it;
    }
}

objects.erase(std::remove(objects.begin(), objects.end(), NULL), objects.end()); //Causing "no conversion from const int to myObj"

如您所见,我正在尝试使用 remove-erase 习惯用法将这些对象从矢量中清除。但是,在尝试编译时,出现错误:“C2446 '==': no conversion from 'const int' to 'myObj *'

我更喜欢使用 remove_if 函数,但由于我的谓词需要接受并比较同一向量中的两个元素,所以我不知道如何正确构造谓词。有人可以告诉我为什么会出现编译错误,或者告诉我如何为上述内容构建正确的谓词吗?我已经在 Google 上搜索了好几个小时,但没有发现任何示例表明谓词具有更复杂的行为,例如,从列表中删除奇数,除了当前正在测试的元素之外,自然不需要任何输入。

【问题讨论】:

  • 使用调试器并在导致错误的行中修改适当的注释。这样帮助你会容易得多。
  • 是否可以选择使用 C++11?
  • 抱歉,现在添加评论。导致编译错误的行是 objects.erase(...) 行
  • 他实际上并没有从循环内的向量中删除元素。
  • Nemanja Boric,如果 C++11 具有适当的功能,它确实是一种选择。

标签: c++ vector std


【解决方案1】:

问题是您的NULL 被作为整数而不是指针传递给std::remove。如果可以选择使用 C++11,请使用 nullptr 而不是 NULL(在 for 循环和 std::remove 内部)。如果不是,请将NULL 转换为适当的类型:

objects.erase(std::remove(objects.begin(), objects.end(), static_cast<myObj*>(NULL)), 
              objects.end()); //Causing "no conversion from const int to myObj"

【讨论】:

  • 这是修复现有代码的最简单方法。谢谢!
【解决方案2】:

根据您的问题描述,听起来您需要一个谓词来测试给定对象是否与向量中的任何其他对象交互。

换句话说,听起来您需要一个std::remove_ifstd::find_if 作为其谓词的一部分。

以下方法使用 lambda 函数,因此需要 C++11。您可以通过 InteractionChecker::operator() 中的简单 for 循环来避免这种情况。

typedef std::vector<myObj*> ObjectVector;
struct InteractionChecker
{
  InteractionChecker(const ObjectVector& objects) : m_objects(objects) {}

  // Return 'true' iff checked_obj interacts with any object in m_objects.
  bool operator()(const myObj* checked_obj)
  {
    // Need to think about whether checked_obj interacts with itself
    return m_objects.end() != std::find_if(m_objects.begin(), m_objects.end(),
                                           [checked_obj](myObj* other)
                                           { 
                                               return checked_obj->interactsWith(other); 
                                           });
  } 

private:
  const ObjectVector& m_objects;
};

ObjectVector objects;
// Populate objects vector.

InteractionChecker checker(objects);
std::remove_if(objects.begin(), objects.end(), checker);

【讨论】:

  • 这很好,但仔细想想,上面的答案很好地满足了我的需求。还是投了赞成票。
【解决方案3】:

所以,我不认为您当前的代码适用于所有情况。因此,我认为它需要更大的修复。

例如,假设您的向量中有 3 个对象,并且所有 3 个对象都交互。您的代码将抓取前两个,然后将其删除。当我们到达第三个时,它不会有任何东西可以与之交互,所以它会留下来。 但这不是正确的行为

我写了一些代码如下:

  • 我们采用向量,并保持startend 的位置。这些迭代器表示要处理的有效剩余区域。

  • 我们向量中的三个区域:

    • [vec.begin(), start) 之间的元素是我们知道我们将保留的对象,并且已经将它们与向量中的所有内容进行了比较。所以我们不需要再碰它们了。
    • [start, end) 之间的元素是我们仍然需要比较事物以查看它们是否有任何交互的元素。
    • [end, vec.end()) 之间的元素是我们知道正在与其他事物交互的元素。但是,可能仍有更多元素与之交互,我们尚未找到。
  • 我们的结束条件是start == end。之所以可行,是因为此时没有更多元素可用于交互比较。

代码如下:

#include <algorithm>
#include <iostream>
#include <vector>

struct myObj {
    myObj(int val): val(val) {}

    bool interactsWith(struct myObj *other) {
        return this->val == other->val;
    }

    const int val;
};

int main() {
    std::vector<myObj *> vec;
    vec.push_back(new myObj(3));
    vec.push_back(new myObj(2));
    vec.push_back(new myObj(1));
    vec.push_back(new myObj(2));

    // Print the vector
    for (auto x : vec)
        std::cout << x->val << " ";
    std::cout << "\n";

    auto start = vec.begin();
    auto end = vec.end();

    while (start != end) {
        // Test if start interacts with anything in the vector
        std::vector<myObj *>::iterator match;
        for (match = start + 1; match != vec.end(); ++match)
            if ((*start)->interactsWith(*match))
                break;

        // We did not find a match
        if (match == vec.end()) {
            start++;
            continue;
        }

        // If the match isn't already in the removal area, move it there.
        if (match < end)
            std::iter_swap(match, --end);

        // The start is always before the removal area, so move it there.
        std::iter_swap(start, --end);
    }

    // Print the vector
    for (auto x : vec)
        std::cout << x->val << " ";
    std::cout << "\n";

    // Delete the memory backing these elements
    // We should probably just be using std::vector<std::unique_ptr>>...
    std::for_each(end, vec.end(), [](myObj * & obj) {
        delete obj;
        obj = NULL;
        });

    // Remove the elements from the vector
    vec.erase(end, vec.end());

    // Print the vector
    for (auto x : vec)
        std::cout << x->val << " ";
    std::cout << "\n";
}

这将输出:

[4:04pm][wlynch@apple /tmp] ./foo
3 2 1 2   // The original vector
3 1 2 2   // The elements that intersect have been moved to the back of the vector
3 1       // We've erased those elements from the vector.

还有一个说明:

这最终与NicholasM's answer 非常相似,但有一些他不存在的优化。

  • 一旦我们发现一个元素不与任何东西相交,我们就可以忽略它。
  • 当我们找到相交匹配时,我们可以将这两个元素移到列表的末尾。

这两种优化都是允许的,因为intersectsWith() 是关联的。

【讨论】:

  • 您说的完全正确,我忽略了我的代码可能无法正常运行的事实。感谢您指出:)。
猜你喜欢
  • 1970-01-01
  • 2018-04-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-05-05
  • 1970-01-01
  • 2020-07-03
相关资源
最近更新 更多