在基于范围的循环中删除映射元素

3
我想根据某些条件从地图中删除若干元素:
#include <unordered_map>
#include <ranges>
#include <iostream>

int main() {

    std::unordered_map<int, int> numbers = {{1,2}, {2,1}, {3,2}, {4,5}};

    auto even = [](auto entry){return entry.second %2 == 0;};
    for(auto& [key, val] : numbers | std::views::filter(even)) {
        numbers.erase(val);
    }

    for(auto& [key, val] : numbers) {
        std::cout << key << " " << val << "\n";
    }
}

但是似乎我正在使范围循环需要的迭代器无效:

4 5
3 2
1 2

我知道可以使用迭代器来明确地完成这个操作,但是否有一种漂亮而简洁的基于范围的方法来根据过滤器删除元素呢?


这不是重复的吗? - Peter Mortensen
1个回答

5

我建议您使用 std::erase_if(),如下所示:

std::erase_if(numbers, [](auto entry) {return entry.second % 2 == 0; });

如果您需要一个范围解决方案,并且不需要原地更改,您可以像下面这样做(此代码仅在C++23中编译):
numbers = numbers | std::views::filter(even) | std::ranges::to<decltype(numbers)>();

我不确定,但是根据文档,下面的代码可能比上面普通范围代码具有更好的性能:

auto temp_numbers = numbers | std::views::filter(even) | std::ranges::to<decltype(numbers)>();
numbers.swap(temp_numbers);

正如您在std::unordered_mapoperator=中所看到的,其移动赋值运算符的复杂度是线性的,但其移动构造函数的复杂度是常数的,而其swap()方法的复杂度也是常数的,因此它似乎具有更好的性能。但是,我没有任何基准来证明这一点。


numbers=std::move(...); 可能比 swap 更好。 - Red.Wave
@Red.Wave 我很惊讶,文档中说移动构造函数具有线性复杂度,而不是常数,所以我必须使用swap,你有什么想法,为什么? - sorosh_sabz
@sorosh_sabz,不是的。线性时间复杂度属于使用分配器的重载。移动构造函数在常数时间内运行。 - Red.Wave
@Red.Wave 我不是在谈论移动构造函数,我是在谈论移动赋值运算符,numbers=std::move(...); 调用的是移动赋值运算符,而不是移动构造函数。正如你可以在 https://en.cppreference.com/w/cpp/container/unordered_map/operator%3D 中看到的那样,移动赋值运算符具有线性复杂度,与分配器类型无关。 - sorosh_sabz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接