C++ 中有 Python range() 的等价物吗?

Is there any equivalent of Python range() in C++?

我想使用std::for_each并行迭代[a, b)范围内的向量索引,计算Weierstrass function的值并将其写入std::vector

std::vector<std::array<float, 2>> values(1000);
auto range = /** equivalent of Pyhthon range(0, values.size()) **/;

std::for_each(std::execution::par, range.begin(), range.end(), [&](auto &&i) {
    values[i][0] = static_cast<float>(i) / resolution;
    values[i][1] = weierstrass(a, b, static_cast<float>(i) / resolution);
});

// a, b, and resolution are some constants defined before
// weierstrass() is the Weierstrass function

我在网上找到了一些解决方案,但都需要包含一些第三方库或创建我自己的范围class。有没有标准的解决方案?

如果问题在于创建类似于 python 的 range() 的范围,您可以查看 https://en.cppreference.com/w/cpp/iterator/iterator 并使用它的示例:

#include <iostream>
#include <algorithm>
 
template<long FROM, long TO>
class Range {
public:
    // member typedefs provided through inheriting from std::iterator
    class iterator: public std::iterator<
                        std::input_iterator_tag,   // iterator_category
                        long,                      // value_type
                        long,                      // difference_type
                        const long*,               // pointer
                        long                       // reference
                                      >{
        long num = FROM;
    public:
        explicit iterator(long _num = 0) : num(_num) {}
        iterator& operator++() {num = TO >= FROM ? num + 1: num - 1; return *this;}
        iterator operator++(int) {iterator retval = *this; ++(*this); return retval;}
        bool operator==(iterator other) const {return num == other.num;}
        bool operator!=(iterator other) const {return !(*this == other);}
        reference operator*() const {return num;}
    };
    iterator begin() {return iterator(FROM);}
    iterator end() {return iterator(TO >= FROM? TO+1 : TO-1);}
};
 
int main() {
    // std::find requires an input iterator
    auto range = Range<15, 25>();
    auto itr = std::find(range.begin(), range.end(), 18);
    std::cout << *itr << '\n'; // 18
 
    // Range::iterator also satisfies range-based for requirements
    for(long l : Range<3, 5>()) {
        std::cout << l << ' '; // 3 4 5
    }
    std::cout << '\n';
}

您可以使用 std::views::iota(), its use is similar (but a bit different) to Python's range(). With help of std::ranges::for_each()。两者都在 C++20 中可用。

Try it online!

#include <algorithm>
#include <ranges>
#include <iostream>

int main() {
    std::ranges::for_each(std::views::iota(1, 10), [](int i) {
        std::cout << i << ' ';
    });
}

输出:

1 2 3 4 5 6 7 8 9 

作为@Afshin 的,在上面提到的代码中std::ranges::for_each() 不支持std::execution::par 执行multi-threaded。

要克服这个问题,您可以使用 iota 和常规 std::for_each(),如下所示:

Try it online!

#include <algorithm>
#include <ranges>
#include <iostream>
#include <execution>

int main() {
    auto range = std::views::iota(1, 10);
    std::for_each(std::execution::par, range.begin(), range.end(),
        [](int i) {
            std::cout << i << ' ';
        });
}

输出:

1 2 3 4 5 6 7 8 9 

我决定根据 Python 的 range().

中的工作原理,从头开始实现 Range class plus 迭代器

类似于Python,您可以通过三种方式使用它:Range(stop)Range(start, stop)Range(start, stop, step)。这三个都支持任何负值。

为了测试实现的正确性,我填充了两个无序集合,一个包含所有生成的值,另一个包含所有使用的线程 ID(以表明它实际使用 multi-core CPU 执行)。

虽然我将我的迭代器标记为随机访问类型,但它仍然缺少一些方法,如 -=-- 运算符,这些额外的方法有待进一步改进。但是对于 std::for_each() 的用法,它有足够的方法。

如果我在实施中犯了一些错误,请在我的回答中添加评论并进行解释。

Try it online!

#include <limits>
#include <execution>
#include <algorithm>
#include <iostream>
#include <iterator>
#include <thread>
#include <unordered_set>
#include <string>
#include <sstream>
#include <mutex>

class Range {
public:
    Range(ptrdiff_t start_stop, ptrdiff_t stop =
            std::numeric_limits<ptrdiff_t>::max(), ptrdiff_t step = 1)
        : step_(step) {
        if (stop == std::numeric_limits<ptrdiff_t>::max()) {
            start_ = 0;
            stop_ = start_stop;
        } else {
            start_ = start_stop;
            stop_ = stop;
        }
        if (step_ >= 0)
            stop_ = std::max(start_, stop_);
        else
            stop_ = std::min(start_, stop_);
        if (step_ >= 0)
            stop_ = start_ + (stop_ - start_ + step_ - 1) / step_ * step_;
        else
            stop_ = start_ - (start_ - stop_ + step_ - 1) / (-step_) * (-step_);
    }
    class RangeIter {
    public:
        using iterator_category = std::random_access_iterator_tag;
        using value_type = ptrdiff_t;
        using difference_type = ptrdiff_t;
        using pointer = ptrdiff_t const *;
        using reference = ptrdiff_t const &;

        RangeIter() {}
        RangeIter(ptrdiff_t start, ptrdiff_t stop, ptrdiff_t step)
            : cur_(start), stop_(stop), step_(step) {}
        RangeIter & operator += (ptrdiff_t steps) {
            cur_ += step_ * steps;
            if (step_ >= 0)
                cur_ = std::min(cur_, stop_);
            else
                cur_ = std::max(cur_, stop_);
            return *this;
        }
        RangeIter operator + (ptrdiff_t steps) const {
            auto it = *this;
            it += steps;
            return it;
        }
        ptrdiff_t operator [] (ptrdiff_t steps) const {
            auto it = *this;
            it += steps;
            return *it;
        }
        ptrdiff_t operator - (RangeIter const & other) const {
            return (cur_ - other.cur_) / step_;
        }
        RangeIter & operator ++ () {
            *this += 1;
            return *this;
        }
        ptrdiff_t const & operator * () const {
            return cur_;
        }
        bool operator == (RangeIter const & other) const {
            return cur_ == other.cur_;
        }
        bool operator != (RangeIter const & other) const {
            return !(*this == other);
        }
        ptrdiff_t cur_ = 0, stop_ = 0, step_ = 0;
    };
    auto begin() const { return RangeIter(start_, stop_, step_); }
    auto end() const { return RangeIter(stop_, stop_, step_); }
private:
    ptrdiff_t start_ = 0, stop_ = 0, step_ = 0;
};

int main() {
    ptrdiff_t start = 1, stop = 1000000, step = 2;
    std::mutex mutex;
    std::unordered_set<std::string> threads;
    std::unordered_set<ptrdiff_t> values;
    auto range = Range(start, stop, step);
    std::for_each(std::execution::par, range.begin(), range.end(),
        [&](int i) {
            std::unique_lock<std::mutex> lock(mutex);
            std::ostringstream ss;
            ss << std::this_thread::get_id();
            threads.insert(ss.str());
            values.insert(i);
        });
    std::cout << "Threads:" << std::endl;
    for (auto const & s: threads)
        std::cout << s << std::endl;
    {
        bool correct = true;
        size_t cnt = 0;
        for (ptrdiff_t i = start; i < stop; i += step) {
            ++cnt;
            if (!values.count(i)) {
                correct = false;
                std::cout << "No value: " << i << std::endl;
                break;
            }
        }
        if (values.size() != cnt)
            std::cout << "Expected amount of values: " << cnt
                << ", actual " << values.size() << std::endl;
        std::cout << "Correct values: " << std::boolalpha
            << (correct && (values.size() == cnt)) << std::endl;
    }
}

输出:

Threads:
1628
9628
5408
2136
2168
8636
2880
6492
1100
Correct values: true

作为替代方案,您可以通过添加所需的索引使每个工作包都携带必要的信息。

示例:

std::vector<std::pair<size_t, std::array<float, 2>>> values(1000);
for(size_t i = 0; i < values.size(); ++i) values[i].first = i;

std::for_each(std::execution::par, values.begin(), values.end(),
    [resolution](auto& p) {
        p.second[0] = static_cast<float>(p.first) / resolution;
        p.second[1] = weierstrass(a, b, static_cast<float>(p.first) / resolution);
    });

不在 values 上像上面那样在线程部分使用索引 可能 防止虚假共享并提高性能。您还可以使每个工作包对齐以防止错误共享,以查看这是否对性能有影响。

#include <new>

struct alignas(std::hardware_destructive_interference_size) workpackage {
    size_t index;
    std::array<float, 2> arr;
};

std::vector<workpackage> values(1000);
for(size_t i = 0; i < values.size(); ++i) values[i].index = i;

std::for_each(std::execution::par, values.begin(), values.end(),
    [resolution](auto& wp) {
        wp.arr[0] = static_cast<float>(wp.index) / resolution;
        wp.arr[1] = weierstrass(a, b, static_cast<float>(wp.index) / resolution);
    });

您可以用另一种方式编写代码,并完全放弃对范围的任何需求,如下所示:

std::vector<std::array<float, 2>> values(1000);

std::for_each(std::execution::par, values.begin(), values.end(), [&](std::array<float, 2>& val) {
    auto i = std::distance(&values[0], &val);
    val[0] = static_cast<float>(i) / resolution;
    val[1] = weierstrass(a, b, static_cast<float>(i) / resolution);
});

我应该说此代码有效 当且仅当您使用 std::for_each 时,因为它声明:

Unlike the rest of the parallel algorithms, std::for_each is not allowed to make copies of the elements in the sequence even if they are trivially copyable.