【问题标题】:c++ how to shrink std::vector to a subselection efficientlyc ++如何有效地将std :: vector缩小为子选择
【发布时间】:2021-08-13 15:52:26
【问题描述】:

给定两个向量

std::vector<SomeStruct> items;  //1'000'000 items
std::vector<int> selection;  //900'000 unique indices in ascending order

如果selection 包含items 的有效索引,我如何有效地缩小items 以仅包含最初由selection 索引的元素?

【问题讨论】:

  • selection 中的元素是否已排序?你指的高效是什么意思?您想最大限度地减少内存、复杂性还是最大限度地提高可读性?
  • 你尝试了吗?我会创建一个新向量,也许使用std::transformstd::back_inserter 来填充它,但本质上它只是一个简单的循环。我不会从items 中删除元素,因为我预计这会非常昂贵
  • 向量的大小是多少? selection 中是否还有更多元素需要删除?
  • 我也会考虑根本不修改items,而是使用自定义迭代器,允许访问items 中存在于selection 中的那些元素。这实际上取决于边界条件以及“高效”的含义
  • 由于索引向量已排序,只需对其进行迭代。在数据向量的开头设置一个迭代器(“其他迭代器”)。对于索引向量中的每个元素,将数据向量中该位置的元素与另一个迭代器引用的元素一起移动,并递增另一个迭代器。然后通过擦除另一个迭代器的最终值处和之后的每个元素来缩小向量。如果索引是有序的,则此方法有效,您无需在当前检查的索引之前保留任何元素。

标签: c++ vector move


【解决方案1】:

我将把这个答案反过来写。多多包涵,希望你能理解。

让我们首先编写一个包装器,让我们只迭代选定的项目:

#include <iostream>
#include <vector>

struct SomeStruct {};

struct selected_item {
    std::vector<SomeStruct>& items;
    std::vector<size_t>& selection;
    struct iterator {
        std::vector<SomeStruct>& items;
        std::vector<size_t>::iterator selection_iterator;
        SomeStruct& operator *(){
            return items[*selection_iterator];
        }
        iterator& operator++(){
            ++selection_iterator;
            return *this;
        }
        bool operator!=(const iterator& other){
            return selection_iterator != other.selection_iterator;
        }
    };
    iterator begin() { return {items,selection.begin()}; }
    iterator end() { return {items,selection.end()};}
};

int main() {
    std::vector<SomeStruct> items{{},{},{},{}};
    std::vector<size_t> selection{1,3};
    for (auto& i : selected_item{items,selection}){
        std::cout << "item selected\n";
    }
}

使用它,您现在可以编写一个循环,将所选项目从 items 移动到新向量中,然后将该新向量移动到项目中:

int main() {
    std::vector<SomeStruct> items{{},{},{},{}};
    std::vector<size_t> selection{1,3};
    std::vector<SomeStruct> temp_items;
    temp_items.reserve(selection.size());
    for (auto& i : selected_item{items,selection}){
       temp_items.emplace_back(std::move(i));
    }
    items = std::move(temp_items);
}

假设SomeStruct可以移动,这不会复制任何SomeStruct。然而,搬家也不是免费的。根据您实际要从items 中删除元素的原因(为什么不首先填充所选项目的向量,而不是填充索引向量?)您还可以考虑完全跳过移动并仅使用上述内容包装器对所选项目执行任何您想做的事情。由于选择了 90% 的项目,可能是内存的节省和更有效的元素访问(由于较小的向量)并没有超过移动,所以您不妨直接这样做:

int main() {
    std::vector<SomeStruct> items{{},{},{},{}};
    std::vector<size_t> selection{1,3};
    for (auto& i : selected_item{items,selection}){
       do_something_with_selected_item(i);
    }
    
}

另一种选择是实际从items 中删除元素。我没有考虑它,因为我预计它会相当昂贵。我可能错了。与往常一样,要知道您需要衡量什么更有效。

PS: 包装器是用 gcc 测试的。我发现编写自定义迭代器有点烦人,不确定它是否需要 operator== 或后增量。我只实现了让 gcc 满意的必要条件。

【讨论】:

    猜你喜欢
    • 2010-09-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多