C++unique_copy复制去重序列

2026-04-11 14:10:32 1189阅读 0评论

unique_copy:不是“去重”,是“压缩相邻重复项”的精密拷贝工具

刚学 C++ 标准库时,很多人看到 unique_copy 的名字,下意识以为它能像 Python 的 set() 一样,把一串乱序数字全扫一遍,留下唯一值。结果一试,{3,1,4,1,5,9,2,6,5} 经它处理后还是 {3,1,4,1,5,9,2,6,5}——啥也没变。不是函数坏了,是你对它的理解,从一开始就偏了方向。

unique_copy 不是万能去重器,它是针对已排序或已分组序列的“相邻重复压缩”搬运工。它的核心动作只有一个:跳过连续重复的元素,只保留每组重复中的第一个,再把它们拷贝到目标容器。它不改变原序列,也不排序,更不查重全局——它只认“紧挨着”的重复。

这就像整理书架:你不会把散落在各层的《C++ Primer》全收拢成一本;但如果你已经把所有同系列书按顺序排好,《Effective C++》《More Effective C++》《Effective Modern C++》,这时 unique_copy 就能帮你剔除其中不小心放了两本《Effective C++》且恰好挨着的情况——前提是,那两本真得肩并着肩。

所以,用好它的第一步,不是写代码,而是预判数据形态
如果你手头是一段未排序的 vector<int> v = {2, 1, 3, 1, 2};,别急着套 unique_copy。先问自己:你真正要的是什么?

  • 要保留原始顺序、只删掉后面重复出现的数?→ 那得手写循环 + unordered_set 记录见过的值;
  • 要最终结果升序无重复?→ 先 sortunique_copy
  • 要按出现频次分组,每组内部连续?→ 那得先 sort,再 unique_copy 才有意义。

关键点来了:unique_copy 要求输入迭代器范围必须满足“等价元素连续出现”这一隐含前提。
标准里写的是 “removes all but the first element from every consecutive group of equal elements”,注意这个 consecutive(连续的)。它不比较 v[0]v[3],只看 v[i]v[i+1] 是否相等。所以,{1,2,2,3,3,3,1} 经它处理后是 {1,2,3,1}——末尾那个孤立的 1 不会被前面的 1 消掉,因为它不“连续”。

实际编码时,最容易踩的坑是忘了接 unique 的返回值做截断。等等,unique_copy 不需要?对,它不需要。这是它和 unique 的本质区别:

  • unique 直接在原容器上操作,返回新逻辑终点的迭代器,你得手动 erase 后半段;
  • unique_copy 安静地把压缩后的序列写进另一块内存,返回目标容器中最后一个被写入位置的后续迭代器——这才是你该关注的“新长度”。

来看一个真实场景:日志分析中提取连续状态变更点。
假设某设备上报的时间戳与状态序列如下(已按时间排序):

vector<pair<time_t, string>> log = {
    {100, "IDLE"}, {105, "RUNNING"}, {110, "RUNNING"}, 
    {115, "ERROR"}, {120, "ERROR"}, {125, "ERROR"}, {130, "IDLE"}
};

你想提取“状态真正切换的时刻”,即每次状态变化的第一个记录。这时,unique_copy 正好派上用场——我们按状态字段分组,而非整个 pair

vector<pair<time_t, string>> changes;
changes.reserve(log.size()); // 预留空间,避免反复 realloc

auto last = unique_copy(log.begin(), log.end(), back_inserter(changes),
    [](const auto& a, const auto& b) { return a.second == b.second; });
changes.erase(last, changes.end()); // 注意:unique_copy 不自动截断目标容器!

这里传入了自定义谓词,告诉它:“只要 second(状态)相同,就算连续重复”。于是 {105,"RUNNING"}{110,"RUNNING"} 被视为一组,只留前者;而 {125,"ERROR"}{130,"IDLE"} 状态不同,必然保留。最终 changes 里存的就是所有状态切换锚点。

有人会问:为什么不用 std::unique 直接在原 log 上操作?因为日志原始序列不能动——你可能还要按时间做其他分析。unique_copy 的不可变特性,让它天然适合这类“衍生视图”构建。

再补一个细节:目标容器必须有足够空间,或使用能自动增长的插入器(如 back_inserterfront_inserter)。若目标是 raw array,务必确保长度 ≥ 输入长度,否则越界是分分钟的事。

最后说句实在话:unique_copy 不是高频函数,但它一旦用对场景,就显得特别干净利落。它不抢 sort 的活,也不替 set 去重,它只在“数据已具局部秩序”的缝隙里,精准完成一次轻量拷贝压缩。理解它的边界,比记住它的语法更重要。

下次看到重复数据,先别急着祭出万能锤子。停下来想想:这些重复,是散落各处,还是抱团扎堆?答案决定了——你是该排序,还是该写哈希,还是,就用 unique_copy 安静地抄一遍。

文章版权声明:除非注明,否则均为Dark零点博客原创文章,转载或复制请以超链接形式并注明出处。

发表评论

快捷回复: 表情:
验证码
评论列表 (暂无评论,1189人围观)

还没有评论,来说两句吧...

目录[+]