从范围中删除元素并将删除的元素复制到新范围

时间:2014-12-05 16:36:13

标签: c++

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

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

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

所以问题是:

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

如果没有,是否有一种智能算法比一个天真的手工remove_if变体更快?

2 个答案:

答案 0 :(得分:2)

不,它没有。有一个函数可以执行一个(删除与谓词匹配的元素)或另一个函数(复制匹配谓词的元素),但不能同时执行两个。但是,通过两个步骤编写我们自己的内容非常容易:

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; });

答案 1 :(得分: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