【C++23】(四)Ranges 增强与 views::chunk_by:数据处理新利器
数据处理时,你还在用循环 + 计数器分组吗?C++23 的
views::chunk_by让「按条件对连续元素分组」变得优雅——一行代码搞定以前要 20 行的逻辑。
前言
C++20 引入了 Ranges 库,改变了我们写算法的方式:惰性求值、管道语法、组合性。
C++23 在此基础上大幅增强,带来了一批实用的新 views:
chunk_by:按条件分组连续元素enumerate:带索引遍历zip_transform:多序列并行变换concat:拼接视图ranges::to:视图转容器
一、views::chunk_by:连续元素分组
1.1 痛点回顾
假设有这样一个需求:把数组 {1, 1, 2, 2, 2, 3, 1, 1} 按「相邻相同元素」分组。
以前要这么写:
1 | std::vector<int> nums = {1, 1, 2, 2, 2, 3, 1, 1}; |
代码冗长,逻辑分散。
1.2 chunk_by 的优雅解法
1 |
|
输出:
1 | value=1 count=2 |
1.3 chunk_by 的语义
chunk_by 的关键点:
- 比较相邻元素:用传入的谓词(如
std::equal_to{})判断相邻元素是否该分到同一组 - 只分组连续相同:非连续的相同值会分成不同组
- 返回 views:不复制数据,惰性求值
1 | // chunk_by 的分组逻辑图示 |
1.4 自定义谓词
可以用任意二元谓词,不只是相等比较:
1 | // 按 "差值小于 3" 分组 |
二、views::enumerate:带索引遍历
2.1 痛点
以前想知道元素的下标,往往要额外维护一个计数器:
1 | std::vector<std::string> words = {"hello", "world"}; |
2.2 enumerate 的优雅解法
1 |
|
输出:
1 | 0: hello |
2.3 组合使用
enumerate 可以和其他 view 组合:
1 | std::vector<int> nums = {3, 6, 9, 12, 15}; |
三、views::zip_transform:多序列并行变换
3.1 痛点
想要把两个向量对应元素相加?以前要:
1 | std::vector<int> a = {1, 2, 3}; |
3.2 zip_transform 的优雅解法
1 |
|
3.3 组合自定义变换
1 | // 两字符串对应字符拼接 |
四、views::concat:拼接视图
4.1 拼接多个容器
1 |
|
注意:concat 返回的是 view,不复制数据,惰性求值。
五、std::ranges::to:视图转容器
5.1 痛点
Ranges 的 view 是惰性的,但最终往往需要转成具体容器:
1 | // C++20 繁琐写法 |
5.2 C++23 的 to
1 | // C++23:一行搞定 |
5.3 to 的工厂函数
1 | // 更简洁的写法 |
六、完整示例
1 |
|
七、C++23 Ranges 新特性一览
| 特性 | 作用 | 典型场景 |
|---|---|---|
views::chunk_by | 按谓词分组相邻元素 | 数据分块、合并连续区间 |
views::enumerate | 带索引遍历 | 需要下标的循环 |
views::zip_transform | 多序列并行变换 | 向量运算、交叉合并 |
views::concat | 拼接多个视图 | 多个容器串接 |
ranges::to<Container> | 视图转容器 | 最终物化结果 |
行动建议:从今天起,遇到「分组」「索引」「两两运算」的场景,试试这些 C++23 Ranges 工具——你会发现数据处理代码可以如此简洁优雅。
📚 C++23 新特性 系列导航
本文是《C++23 新特性》系列第 4/4 篇。
| 方向 | 章节 |
|---|---|
| ◀ 上一篇 | (三)std::print / to_underlying |
📖 全部 4 篇目录(点击展开)
语法/控制流可视化
下面是一张马卡龙色 Mermaid 图,帮你从图形角度把握本章涉及的语法与控制流。
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#FFE5EC', 'primaryTextColor': '#5D5D5D', 'primaryBorderColor': '#FFB3C6', 'lineColor': '#B5EAD7', 'secondaryColor': '#C7CEEA', 'tertiaryColor': '#FFDAC1'}}}%%
flowchart LR
SRC["📊 源数据<br/>vector / range"] --> V1["views::chunk_by<br/>相邻同值分组"]
V1 --> V2["views::stride n<br/>等距采样"]
V2 --> V3["views::enumerate<br/>附加下标"]
V3 --> V4["views::zip<br/>多范围配对"]
V4 --> COL["🪣 views::collect<br/>物化结果"]
COL --> OUT["✅ 输出"]
SRC -. "惰性求值" .- V1
V1 -. "惰性" .- V2
V2 -. "惰性" .- V3
V3 -. "惰性" .- V4
style SRC fill:#FFE5EC,stroke:#FFB3C6,color:#5D5D5D
style V1 fill:#C7CEEA,stroke:#A8DADC,color:#5D5D5D
style V2 fill:#B5EAD7,stroke:#A8DADC,color:#5D5D5D
style V3 fill:#FFDAC1,stroke:#FFB3C6,color:#5D5D5D
style V4 fill:#C7CEEA,stroke:#A8DADC,color:#5D5D5D
style COL fill:#FFE5EC,stroke:#FFB3C6,color:#5D5D5D
style OUT fill:#B5EAD7,stroke:#A8DADC,color:#5D5D5D本章讲 C++23 Ranges 增强(含 views::chunk_by、views::stride 等),对比维度:本特性 vs C++20 ranges 旧写法 vs 其他语言数据切分。
对比分析
一、本特性 vs 旧写法
| 维度 | C++23 ranges 写法 | C++20 ranges 写法 | 旧手写循环 |
|---|---|---|---|
| 按相邻同值分组 | v | chunk_by(eq) | 自己写组合 view | 双指针循环 |
| 等距采样 | v | stride(n) | 嵌套 filter / indices | 取模 |
| 与 zip 组合 | zip(v1, v2) | chunk_by(...) | 已有 zip | 嵌套迭代器 |
二、对比其他语言
| 语言 | 数据切分 / 分组 | 风格 |
|---|---|---|
| C++23 | chunk_by / stride / zip | 惰性、view 组合 |
| Python | itertools.groupby / more-itertools | 解释、惰性 |
| Rust | chunks / group_by(slice / itertools) | 零开销 |
| Java | Stream Collectors.groupingBy | 急切 |
| Haskell | Data.List.group / groupBy | 类型化惰性 |
三、优缺点
优点:
- 把常见的”分组 / 采样”标准化为 view
- 与现有 ranges 风格一致,学习成本低
缺点:
- 部分 view 在不同编译器下性能差异较大
- 命名约定仍在收敛
四、何时选
- 任何”数据切片 + 分组”流水线:先看 C++23 views
- 性能敏感:benchmark 后再决定是否回到手写循环