C++20范围的切片视图

9
Python的`itertools`有一个名为`islice(seq, start, stop, step)`的过程,它接收一个序列并返回一个迭代器,该迭代器包含在`start`和`stop`之间每个第`step`个值的序列值。
C++20的Ranges库提供类似的功能吗?例如,是否存在像`slice`这样的函数,它接收一个随机访问迭代器`start`,一个结束标记`stop`和一个步长值`step`,并返回一个随机访问迭代器,该迭代器在`start`和`stop`之间迭代每个第`step`个值?
如果没有,是否可以使用Ranges库提供的原语来实现这样的迭代器适配器呢?
(我知道如何手动实现这样的适配器,所以这不是问题。)

range-v3有stride_viewspan,可以结合使用以达到相同的效果。我不知道C++20是否具备ranges-v3的所有功能。 - eerorika
2个回答

9

并非完全如此。

C++20将拥有view::iota,它可以提供从起始值到停止值的序列。然而,它没有步幅功能,只能通过++进行递增。

但是,您可以与range-v3的view::stride结合使用以添加步长。如下:

auto evens = view::iota(0, 100) | view::stride(2); // [0, 2, 4, 6, ... ]

对于现有的范围,有view::slice,它也不需要取步长。但这些是正交的并且层次结构清晰:
auto even_teens  = view::iota(0, 100)
                 | view::slice(10, 20)
                 | view::stride(2); // [10, 12, 14, 16, 18]

确实,view::sliceview::stride一起可以做到我一直在寻找的功能。(不幸的是,如果它们没有包含在C++ 20中,那么对我的原始问题的答案将是否定的。)range-v3的view::sliceview::stride有文档吗? - Marc
@Marc 我不这么认为。我认为最好的方法是查看测试和声明。 - Barry

7

很遗憾,Range-v3中的slicestride(如Barry'sanswer所示)目前还不在C++20Ranges library中提供。 但是,您可以通过组合std::views::drop_whilestd::views::take_while来替换slice。要替换stride,您可以使用范围适配器std::views::filter并向其传递特定的lambda表达式。要像Barry的示例一样过滤每隔一个元素,我将使用带有init捕获的有状态lambda表达式。您可以将所有内容放在一起,以以下方式表示范围[10, 12, 14, 16, 18]

auto even_teens = std::views::iota(0, 100)
                | std::views::drop_while([](int i) { return i < 10; })
                | std::views::take_while([](int i) { return i < 20; })
                | std::views::filter([s = false](auto const&) mutable { return s = !s; });

为了得到一个更加通用的步幅解决方案,您可以在lambda表达式中使用计数器和取模运算符。为了能够以可读的方式指定步幅大小n,我会使用以下lambda表达式,它提供了另一个lambda表达式来跟踪步幅操作:
auto stride = [](int n) {
    return [s = -1, n](auto const&) mutable { s = (s + 1) % n; return !s; };
};

总的来说,最终解决方案如下:
auto even_teens = std::views::iota(0, 100)
                | std::views::drop_while([](int i) { return i < 10; })
                | std::views::take_while([](int i) { return i < 20; })
                | std::views::filter(stride(2));

Wandbox上的代码


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