是否有一种安全(定义的行为)方式使用 STL 来减少基于其索引有效过滤向量的样板文件?

问题描述 投票:0回答:3

我经常发现自己想根据它的index而不是它的值来过滤一个向量。

auto some_values = std::vector{1, 0, 4, 6, 2};

// Somewhere I figure out which items to remove.
// This may be from user interaction or something
// nothing to do with the original values:
const auto removing = std::vector<bool>{0, 0, 1, 0, 1};

所以,我很想像这样使用

erase_if

std::erase_if(some_values, [it = removing.begin()](auto) mutable {return *it++;});

它似乎适用于 gcc 和 clang。但是,std::erase cppref 页面 上似乎没有任何关于谓词调用顺序的内容,所以我认为这是未定义的行为?

std::remove_if
同样的问题。请注意,压缩范围不适用于大多数压缩选项,因为通常结果范围无法调整基础数据的大小。

使用 for 循环并创建数组的副本并不是太多的样板,但我目前正在将过滤器应用于一些低级代码,在这些代码中我不能只复制所有数据。一些数据将是巨大的,需要在这些过滤操作期间做出响应。

最坏的情况我可以添加这样的功能来解决问题:

template <class T, auto N, class Pred>
size_t RemoveIfIndex(std::span<T, N> span, Pred&& pred)
{
  size_t write = 0; // Also behaves as a counter
  
  // Initialise to the first deleted index:
  for (; (write < span.size()) && !pred(write); ++write);
  
  // Shuffle the elements:
  for (size_t read = write + 1; read < span.size(); ++read)
  {
    if (!pred(read))
    {
      span[write] = span[read];
      ++write;
    }
  }
  return write;
}

template <class T, class Alloc, class Pred>
size_t EraseIfIndex(std::vector<T, Alloc>& vec, Pred&& pred)
{
  const size_t new_size = RemoveIfIndex(std::span{vec.begin(), vec.end()}, pred);
  vec.resize(new_size);
  return new_size;
}

但是当我需要添加类似类别的函数(需要索引信息)时,我对向我们的低级库添加更多代码犹豫不决,而且我怀疑我对范围/适配器/过滤器有一些了解'我缺少这会使这些功能变得不必要。

c++ stl c++20 stl-algorithm erase-remove-idiom
3个回答
1
投票

它可能符合也可能不符合您对“STL”的看法,但

std::valarray
更普遍地支持像这样的掩码阵列,因此您可以这样做:

#include <valarray>
#include <iostream>

int main() {
    std::valarray<int> values { 1, 2, 3, 4, 5 };
    std::valarray<bool> mask  { 0, 1, 1, 0, 1 };

    // zero all the values where there's a 1 in the mask array
    values[mask] = 0;

    // sum where there were zeros in the mask array:
    std::cout << values.sum(); // result: 5 (1+4)
}

这也适用于数组的内容。例如,您可以指定一个条件(其中 valarray 的名称充当一种占位符):

#include <valarray>
#include <iostream>

int main() {
    std::valarray<int> values { 1, 2, 3, 4, 5 };

    values[values > 3] = 0;
    // values = { 1, 2, 3, 0, 0 };
    std::cout << values.sum(); // 6 (1+2+3)
}

所以通常不像你说的那样removing元素,但是根据情况,你可能会得到类似的效果,基本上忽略你不需要/不想要的元素。

免责声明:主要是将此发布为很少有人知道的有趣内容。老实说,很多人都质疑它到底有多有用——

valarray
可以做一些很酷的事情,但它也有一些相当奇怪的局限性。它很少被使用是有充分理由的。


0
投票

你是对的,你的

erase_if
电话不能保证有效。

根据您需要过滤元素的用途,不删除它们可能是可行的,而是构建一个表以在原始向量中查找它们。沿着线的东西......

#include <vector>
#include <iostream>

template <typename T>
struct filtered_vector {
    std::vector<T>& data;
    std::vector<size_t> indices;
    size_t s;
    filtered_vector(std::vector<T>& data,const std::vector<bool>& removing) : data(data) {
        size_t count = 0;
        for (const auto& r : removing) {
            if (!r) indices.push_back(count);
            ++count;
        }
    }
    T& operator[](size_t index) {
        return data[indices[index]];
    }
    size_t size() { return indices.size();}
};

int main() {
    auto some_values = std::vector{1, 0, 4, 6, 2};
    const auto removing = std::vector<bool>{0, 0, 1, 0, 1};
    filtered_vector<int> f{some_values,removing};
    for (size_t i=0;i<f.size();++i) std::cout << f[i] << " ";
}

作为旁注,如果您确实经常擦除和插入元素但同时需要对其他元素的稳定引用,则可能值得考虑

std::list
使用稳定的迭代器。


-1
投票

==警告不正确的示例,留作教育目的== (remove_if 在内存中四处移动元素,因此索引在迭代期间不会排列)

为什么是最坏的情况?拥有工作职能是一种很好的工作方式。它并不总是关于最少的代码行数。 无论如何我都会创建一个函数来检查边界条件(例如向量大小应该匹配)

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

void remove_by_index(std::vector<int>& values, const std::vector<bool>& filter)
{
    if (values.size() != filter.size()) throw std::invalid_argument("");
    std::size_t index{ 0 };
    auto it = std::remove_if(values.begin(), values.end(), [&](const auto) { return filter[index++]; });
    values.erase(it, values.end());
}

int main()
{
    auto some_values = std::vector{ 1, 0, 4, 6, 2 };
    const auto remove = std::vector<bool>{ 0, 0, 1, 0, 1 };

    remove_by_index(some_values, remove);

    for (const auto value : some_values)
    {
        std::cout << value << " ";
    }

    return 0;
}
© www.soinside.com 2019 - 2024. All rights reserved.