【问题标题】:Efficient way to filter out elements from std::vector从 std::vector 中过滤掉元素的有效方法
【发布时间】:2018-02-24 11:04:48
【问题描述】:

为了从std::vector 中过滤掉一些不良元素,我最终完成了以下代码:

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

typedef struct mystruct {
    int id;
    std::string name;
};

int main()
{        
    std::vector<mystruct> all_items = {{151, "test1"}, {154, "test4"}, {152, "test2"}, {151, "test1"}, {151, "test1"}, {153, "test3"}};
    std::vector<int> bad_ids = {151, 152};
    std::vector<mystruct> filter_items;

    for (const auto& item : all_items) {
        if ( std::find(bad_ids.begin(), bad_ids.end(), item.id) != bad_ids.end() ) {
            std::cout << "id: " << item.id << " is bad" << std::endl;
        } else {
            std::cout << "id: " << item.id << " is good item" << std::endl;
            filter_items.emplace_back(item);
        }
    }

    for (auto f : filter_items) {
        std::cout << "Good item: " << f.id << std::endl;
    }
}

有没有更有效的方法? std::remove_copy_if 或 Boost 可以在这里使用吗?如何使用?

【问题讨论】:

  • 如果您有工作代码可以改进,请联系SE Code Review
  • 谢谢!我可以轻松地将其转移到 SE Code Review 还是应该复制/粘贴它?
  • 是的,只需复制整个问号即可。把它作为问题放在那里,然后删除这个。
  • bad_ids 中有多少元素在您的应用程序中实际存在?如果超过几个,那么您可能会从使用集合中受益。
  • @RustyX:我希望bad_ids 中的一些元素是非唯一的。

标签: c++ algorithm c++11 boost stl


【解决方案1】:

是的,您可以使用std::remove_copy_if,例如

std::remove_copy_if(
  all_items.begin(), 
  all_items.end(), 
  std::back_inserter(filter_items),
  [&bad_ids](const mystruct& item) { return std::find(bad_ids.begin(), bad_ids.end(), item.id) != bad_ids.end(); });

LIVE

或者你可以直接在vector上使用std::remove_iferasebad元素,例如

all_items.erase(
  std::remove_if(
    all_items.begin(), 
    all_items.end(), 
    [&bad_ids](const mystruct& item) { return std::find(bad_ids.begin(), bad_ids.end(), item.id) != bad_ids.end(); }), 
  all_items.end());

LIVE

【讨论】:

    【解决方案2】:

    扩展@songyuanyao 的正确答案,保留一个容器助手的小库以使代码更具表现力。

    #include <iostream>
    #include <vector>
    #include <algorithm>
    
    struct mystruct {
        int id;
        std::string name;
    };
    
    template<class T, class A, class Pred>
    std::vector<T, A> copy_unless(std::vector<T, A> container, Pred&& pred)
    {
        container.erase(std::remove_if(container.begin(), container.end(), 
                                       std::forward<Pred>(pred)), 
                        container.end());
        return container;
    }
    
    template<class Container, class Pred>
    bool any_match(Container&& container, Pred&& pred)
    {
        return std::find_if(container.begin(), container.end(), pred) != container.end();
    }
    
    int main()
    {        
        std::vector<mystruct> all_items = {{151, "test1"}, {154, "test4"}, {152, "test2"}, {151, "test1"}, {151, "test1"}, {153, "test3"}};
        std::vector<int> bad_ids = {151, 152};
    
        auto is_bad = [&bad_ids](mystruct const& item)
        {
            auto match_id = [&item](int id){ return item.id == id; };
            return any_match(bad_ids, match_id);
        };
    
        auto filter_items = copy_unless(all_items, is_bad);
    
        for (auto&& f : filter_items) {
            std::cout << "Good item: " << f.id << std::endl;
        }
    }
    

    我确定我记得 boost 中有一个这样的库,但我这辈子都不记得它是哪一个了。

    【讨论】:

    • 你想到了 Boost Range 吗? Perhaps
    • @sehe 我认为就是这样。有趣的是我从未使用过它。我对算法接口不满意——将返回类型指定为模板参数的想法似乎有点过时。我相信我们作为一个社区可以做得更好。
    • copy_range 是一个奇怪的结果。顺便说一句,Boost 我们作为一个社区。贡献:)
    • @sehe 目前正在研究解决方案。我确信我们可以制作出富有表现力、高效且 100% 安全的算法。会及时通知您。
    【解决方案3】:

    我建议提升范围:

    Live On Coliru

    int main() {
        myvec all_items = { { 151, "test1" }, { 154, "test4" }, { 152, "test2" },
                            { 151, "test1" }, { 151, "test1" }, { 153, "test3" } };
    
        auto is_good = [bad_ids = std::set<int> { 151, 152 }](mystruct v) {
            return bad_ids.end() == bad_ids.find(v.id); 
        };
    
        // just filter on the fly:
        for (auto& f : all_items | filtered(is_good)) {
            std::cout << "Good item: " << f.id << std::endl;
        }
    
        // actually copy:
        auto filter_items = boost::copy_range<myvec>(all_items | filtered(is_good));
    }
    

    打印

    Good item: 154
    Good item: 153
    

    改进...

    你可以通过一些因素来改善风格:

    假设你有一个像 contains 这样的实用程序:

    template <typename... Arg, typename V> bool contains(std::set<Arg...> const &set, V const &v) {
        return set.end() != set.find(v);
    }
    
    template <typename... Arg, typename V> bool contains(std::vector<Arg...> const &vec, V const &v) {
        return vec.end() != std::find(vec.begin(), vec.end(), v);
    }
    

    然后它变得更具可读性:

    Live On Coliru

    auto is_good = [&bad_ids](auto& v) { return !contains(bad_ids, v.id); };
    
    for (auto& f : all_items | filtered(is_good)) {
        std::cout << "Good item: " << f.id << std::endl;
    }
    

    现在,我觉得整个bad_ids 列表也可能是动态的。但如果不是,您可以使用 Phoenix 更“就地”:

    巅峰潮人:

    Live On Coliru

    for (auto& f : all_items | filtered(!contains_(std::set<int> { 151, 152 }, arg1->*&mystruct::id))) {
        std::cout << "Good item: " << f.id << std::endl;
    }
    

    我知道。这是无缘无故地推动它,但是,嘿。只是显示:)

    【讨论】:

      【解决方案4】:

      为什么不使用partition 算法?它将重新排列all_items,以便先放置好物品,然后放置坏物品。

      http://en.cppreference.com/w/cpp/algorithm/partition

      【讨论】:

      • std::remove_if 也有效地做同样的事情 删除是通过移动(通过移动分配)范围内的元素以这样的方式完成的删除出现在范围的开头
      猜你喜欢
      • 2018-10-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-06-05
      • 2023-02-18
      • 2022-11-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多