【问题标题】:Remove elements from range and copy removed elements to new range从范围中删除元素并将删除的元素复制到新范围
【发布时间】:2014-12-05 16:36:13
【问题描述】:

我需要一种方法来从范围中删除所有满足特定条件的元素(在这种特殊情况下为 std::vector)并将这些删除的元素复制到新范围(例如带有输出参数的 std::remove_if。)此操作后输入范围的顺序和输出范围的顺序均不相关。

一种天真的方法是使用std::partition 查找所有“邪恶”元素,然后复制它们并最后删除它们,但这会毫无必要地触及所有“邪恶”元素两次。

另外,我可以自己编写所需的remove_if 变体,但为什么要重新发明轮子(另外我不知道我是否可以匹配高质量库实现的效率)。

所以问题是:

这样的功能是否已经存在? 允许使用 Boost,但首选标准 C++(该项目尚不依赖于 boost)。

如果没有,是否有比纯手工制作的 remove_if 变体更快的智能算法?

【问题讨论】:

  • @IdeaHat 我的“邪恶”元素不必相邻。
  • @0x499602D2 也许我读错了参考资料,但这不只是复制所有“好”元素(我不需要复制顺便说一句)而不修改输入范围吗?
  • 它说它复制了不满足某个谓词的元素,但实际上并没有从原始容器中删除元素。由坏。 :)
  • @0x499602D2 remove_copyremove_copy_if 似乎名字很糟糕。没有remove-ing 正在进行!真的很像copy_if_not...

标签: c++


【解决方案1】:

不,它没有。有些函数可以做一个(删除与谓词匹配的元素)或另一个(复制与谓词匹配的元素),但不能两者兼而有之。但是我们很容易分两步编写自己的代码:

template <typename InputIter, typename OutputIter, typename UnaryPredicate>
InputIter remove_and_copy(InputIter first, InputIter last,
                     OutputIter d_first, UnaryPredicate pred)
{
    std::copy_if(first, last, d_first, pred);
    return std::remove_if(first, last, pred);
}

用法如下:

std::vector<int> v = {1, 2, 3, 4, 5, 6, 7};
std::vector<int> u;

v.erase(
    remove_and_copy(v.begin(), v.end(), std::back_inserter(u),
                    [](int i) { return i%2 == 0; }),
    v.end()
);

// now v is {1, 3, 5, 7} and u is {2, 4, 6}

如果你只需要vector,你可以写得有点不同,但仍然只有两行:

template <typename T, typename UnaryPredicate>
void remove_and_copy(std::vector<T>& from, std::vector<T>& to, UnaryPredicate pred)
{
    std::copy_if(from.begin(), from.end(), std::back_inserter(to), pred);
    from.erase(std::remove_if(from.begin(), from.end(), pred), from.end());
}

或者编写自己的循环:

template <typename T, typename UnaryPredicate>
void remove_and_copy(std::vector<T>& from, std::vector<T>& to, UnaryPredicate pred)
{
    for (auto it = from.begin(); it != from.end(); ) {
        if (pred(*it)) {
            to.push_back(*it);
            it = from.erase(it);
        }
        else {
            ++it;
        }
    }
}

两者的用法:

remove_and_copy(v, u, [](int i) { return i%2 == 0; });

【讨论】:

  • 好吧,它对整个范围进行了两次迭代,遗憾的是我看不出这比我提到的 std::partition 方法更可取。
  • @BaummitAugen 取决于相对费用,但这会更快,假设写入比读取慢。分区方法最多需要((last-first)/2 swaps)*(3 writes/swap)+evil_count 写入。 Barry 将需要2*evil_count 写入(永远不需要保留写入的目的地,因此您不需要交换)。这也将是稳定的,而分区则不会。然而,测量将是证明这一论点的唯一真正方法。
  • “测量一切”,一如既往。 :) 是的,无论如何我都需要这样做,我只是希望有人已经为我这样做了。与num_evil 相比,范围相当长,但交换当然也不是免费的。我会看到...
  • @BaummitAugen 如果使用算法比stable_partition 慢,我会感到惊讶...但您始终可以编写自己的循环。更新了我的答案。
  • @Barry 是的,我会在这个周末的某个时间测量它。顺便说一句,我不需要稳定的。
【解决方案2】:

在使用迭代器时删除的问题是您无法访问实际的容器,因此您实际上无法摆脱元素。相反,例如,std::remove() 所做的是将目标范围移动到范围的末尾,容器稍后将使用它来实际删除元素。

相反,您可以让您的函数将流作为参数,以便在找到目标值后调用其删除方法:

#include <algorithm>
#include <iterator>
#include <string>
#include <iostream>

template <typename Container, typename OutputIt, typename UnaryPredicate>
auto remove_and_copy_if(Container& c, OutputIt d_first, UnaryPredicate pred)
    -> decltype(c.begin())
{
    auto it = std::begin(c);
    for (; it != std::end(c);  )
    {
        while (it != std::end(c) && pred(*it))
        {
            d_first++ = *it;
            it = c.erase(it);
        }

        if (it != std::end(c)) ++it;
    }
    return it;
}

template <typename Container, typename OutputIt, typename T>
auto remove_and_copy(Container& c, OutputIt d_first, T const& value)
    -> decltype(c.begin())
{
    return remove_and_copy_if(c, d_first, 
    [&] (T const& t) { return t == value; });
}

int main()
{
    std::string str = "Text with some   spaces ";
    std::string output;
    std::cout << "Before: " << str << '\n';

    remove_and_copy(str, std::back_inserter(output), ' ');

    std::cout << "After: " << str << '\n';
    std::cout << "Characters removed: " << output << '\n';
}

Demo

【讨论】:

  • 你的循环是错误的。您正在跳过c 中的第一个元素,然后如果最后一个元素满足pred,您将走到最后。
  • @Barry 谢谢。固定。
  • 第一部分,是的,不是第二部分。
  • 我不明白第二部分是如何实现的。
  • 如果it 是最后一个元素,并且满足pred,那么您将检查pred(*(end(c))
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-10-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-28
  • 2015-09-16
相关资源
最近更新 更多