【问题标题】:how to select a subset from a std::vector or list?如何从 std::vector 或列表中选择一个子集?
【发布时间】:2013-11-23 22:07:03
【问题描述】:

c++ 大师:

有很多有用的 c++ stl 算法,例如 find 或 search。但是,它们似乎只返回一个交互器。

如果我想为 STL 容器执行 SQL 样式的“选择”怎么办?比如说,一个向量(可能扩展到列表或地图)。像

std::pair<vector::iterator, vector::iterator> select(std::vector::iterator begin, std::vector::iterator end, Comparor equal_to)

输出应该是一个范围,类似于 std::pair,类似于 boost::multi-index 中方法的返回值

在stl中有这样的东西吗?或任何类似的实体库?

【问题讨论】:

  • 如果子集不是连续的子范围怎么办?
  • 标准库中没有这样的东西。一种选择是 boost::Range,特别是 boost::range::filtered
  • @KerrekSB 输出元素通常应该是不连续的
  • @ccfenix:如果输出元素不连续,那么您到底如何期望一对vector::iterator 对象告诉您它们不在整个向量中?实现这一点的唯一方法是将相等的元素复制或移动到向量的连续部分中,您在其他地方说过这太昂贵了。
  • @Steve 我应该更具体地解释我的问题:我希望输出是迭代器的容器,而不是深度复制对象的容器(这将是巨大的)

标签: c++ boost stl


【解决方案1】:

你基本上有两种方法:

1)您在上面的评论中所说的,将结果写入(指向的迭代器)到迭代器容器中。看起来像这样:

template <typename ForwardIterator, typename OutputIterator, typename UnaryPredicate>
void select_iterators(ForwardIterator first, ForwardIterator last, 
                      OutputIterator out, UnaryPredicate pred) {
    while (first != last) {
        if pred(*first) *out++ = first;
        ++first;
    }
}

那你这样称呼它:

vector<Foo> myfoos;
vector<vector<Foo>::iterator> results;
select_iterators(myfoos.begin(), myfoos.end(), std::back_inserter(results), some_comparator);

您实际上可以根据其他算法定义select_iterators,使用copy_ifboost::counting_iterator,但是当直接实现如此简单时,我认为不值得。它看起来像:

template <typename ForwardIterator, typename OutputIterator, typename UnaryPredicate>
void select_iterators(ForwardIterator first, ForwardIterator last, 
                      OutputIterator out, UnaryPredicate pred) {
    std::copy_if(
        boost::make_counting_iterator(first), 
        boost::make_counting_iterator(last),
        out,
        [&](ForwardIterator it) { return pred(*it); }
    );
}

2) 与其预先测试所有值并将结果写入某处,不如定义一个迭代器,该迭代器每次递增时都会在原始范围上前进,直到找到下一个匹配项。 Boost 提供了两种方法,boost::filter_iteratorboost::adaptors::filter。所以你可以写:

auto results = boost::adaptors::filter(myfoos, some_comparator);

然后,无论您想对结果做什么,您都可以从 results.begin() 迭代到 results.end(),就好像它是一个容器一样。它不是一个容器,它不满足整个容器接口。它满足Boost定义的一个接口,命名为Range,相当于“可以迭代”。它实际上只是myfoos 的过滤视图,因此不会复制甚至移动任何Foo 对象。

【讨论】:

  • 这可能有点晚了,但为什么仅仅使用std::copy_if(src.begin(), src.end(), dst.begin(), pred) 还不够?
  • @ZivS:由于原始提问者的评论,“我希望输出是迭代器的容器,而不是深度复制对象的容器”
  • @ZivS:尽管我写了它,但确实需要我仔细阅读才能弄清楚 :-) 除了返回值之外,我第一次实现 select_iterators 之间的区别而copy_if 就是我的= first,而copy_if 将有= *first
【解决方案2】:

如果您可以修改您的矢量std::partition 将是您的选择。你怎么称呼它:

std::vector<int>::iterator p =
    std::partition(v.begin(), v.end(), you_predicate);

您的答案介于 v.begin()p 之间。

【讨论】:

  • 谢谢!这应该涉及很多复制或交换,对吗?恐怕对我来说太贵了
  • 复杂性在链接页面中进行了说明。如果你的复制算法代价高昂,kt 不是一个好的选择。
【解决方案3】:

您可能正在寻找boost::range

boost::range 实际上是一对定义容器元素范围的迭代器。该库包括各种算法,它们从一个范围返回一个范围(例如容器中等效值的范围,具有用户提供的等效函子)。

【讨论】:

    【解决方案4】:
    template<typename ForwardIterator, typename OutputIterator, typename UnaryPredicate>
        void find_elements(ForwardIterator first, ForwardIterator last, OutputIterator out, UnaryPredicate pred)
        {
            while(first != last)
            {
                if(pred(*first))
                    *out++ = first;
                ++first;
            }
        }
    

    注意事项:

    1.) 你说你希望你的容器是iterator 而不是const_iterator。类型将与您传递给函数的开始和结束范围相同。例如,对于const 容器,类型将为const_iterator,如果您使用vector::cbeginvector::cend,它也将为const_iterator,如果您使用不同的迭代器,例如vector::begin,它将无法编译和vector::cend

    2.) 向量经常失去其迭代器的有效性,因此使用这些迭代器时要小心。例如,如果您添加到向量中,则此函数返回的每个迭代器都可能无效。为防止这种情况,请使用不同的容器(例如列表)或使用vector::reserve

    3.) 前向迭代器必须支持 ++,并且在取消引用时与 InputIterator 具有相同的类型(例如 vector&lt;int&gt;::iterator)。它还必须在递增后仍然是一个有效的迭代器,否则该函数将毫无意义。输出迭代器必须去一个有足够空间来容纳所有找到满足pred 的迭代器的地方。如果事先不知道空间,可以使用&lt;iterator&gt; 中的std::back_inserter 和定义了container::push_back 的容器,它会根据需要增长。

    这是一个功能测试,让您了解它是如何工作的。

    int main()
    {
        vector<string> ss = {"hi", "yog", "engils", "pog"};
    
        // Define a predicate
        auto isSizeThree = [](string const &s)
        {
            return s.size() == 3;
        };
    
        // example one: Somehow I know how many satisfy the predicate. I just don't know where they are.
        vector<vector<string>::iterator> answer(2);
        find_elements(begin(ss), end(ss), begin(answer), isSizeThree);
    
        // Check answer
        cout << "Test 1" << endl;
        for(auto entry : answer)
            cout << *entry << endl;
    
        // Example two: I don't know how many there will be (more typical). If I use a continer with stuff in it - it will stack right on the back of it!
        find_elements(begin(ss), end(ss), back_inserter(answer), isSizeThree);
    
        // Check answer (has the answer from test 1 still in it)
        cout << "Test 2" << endl;
        for(auto entry : answer)
            cout << *entry << endl;
    
        // Example Three: Same as test 2 but clear the ansewr vector first.
        answer.clear();
        find_elements(begin(ss), end(ss), back_inserter(answer), isSizeThree);
    
        // Check answer
        cout << "Test 3" << endl;
        for(auto entry : answer)
            cout << *entry << endl;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-05-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多