第1章:数据清洗工具 · 实战案例13

📌 实战案例13:数据清洗工具

数据清洗,说白了就是给脏数据“搓澡”。
我做了这么多年C语言开发,接过不少数据处理的活儿。最头疼的不是算法,而是那些格式乱七八糟的文本。
空白字符、日期格式、电话号码、重复记录…… 你想想看,如果手工清理,几千行数据能让人崩溃。
正则表达式就是干这个的。今天咱们就手写一个轻量级的数据清洗工具,用C语言 + POSIX正则搞定四个核心任务。

数据清洗工具 去除空白字符 统一日期格式 标准化电话号码 合并重复数据 🔧 POSIX 正则引擎

1. 去除空白字符 —— 别让空格捣乱

我记得刚入行时,接过一个CSV文件,里面全是乱七八糟的空格、制表符、甚至换行符混在字段里。
isspace()一个个判断?太慢了。正则一行搞定。

核心思路: 匹配所有空白字符 [[:space:]]+,替换为一个空格或直接删除。
#include <regex.h>
// 去除首尾及多余空白,保留单词间一个空格
void clean_whitespace(char *str) {
    regex_t regex;
    regcomp(®ex, "[[:space:]]+", REG_EXTENDED);
    // 先替换所有连续空白为一个空格
    // 再用 regsub 或手工处理…… 这里展示逻辑
    regfree(®ex);
}

实际项目中我更喜欢用regexec配合偏移量循环替换。嗯,这里要注意:
如果直接替换为空,会把单词连在一起。所以一般先压缩成一个空格,再trim首尾。

💡 我的习惯: 写一个 trim_and_collapse() 函数,内部调用两次正则:
第一次 ^[[:space:]]+|[[:space:]]+$ 去掉首尾;第二次 [[:space:]]+ 压缩中间。

2. 统一日期格式 —— 把“2024/3/1”变成“2024-03-01”

日期格式混乱是家常便饭。有人写 2024/3/1,有人写 01-03-2024,还有人写 2024年3月1日
我接过一个日志系统,日期字段有8种格式…… 当时差点想跑路。

用正则分组捕获,再重组输出。看代码:

// 匹配 "YYYY/M/D" 或 "YYYY-MM-DD" 等,统一为 YYYY-MM-DD
// 正则: ^([0-9]{4})[-/]([0-9]{1,2})[-/]([0-9]{1,2})$
// 替换为: \1-\2-\3   (但月份和日期需要补零)
// 补零操作可以用条件判断,或者二次正则
⚠️ 我曾经踩过的坑: 直接用 regsub 替换,但月份和日期没有前导零。
后来我改用 regexec 提取子串,然后用 sprintf 格式化 %02d。稳得很。

说白了,日期清洗分两步:匹配 + 标准化。匹配用正则,标准化用C的字符串处理。

3. 标准化电话号码 —— 扔掉分隔符,统一格式

电话号码的写法五花八门:138-1234-5678(010) 8888-6666+86 13812345678……
我的目标是:只保留数字,然后根据长度决定是否加国际前缀。

原始格式清洗后(国内)清洗后(国际)
138-1234-567813812345678+8613812345678
(010) 8888-666601088886666+861088886666
+86 138 1234 567813812345678+8613812345678

正则表达式:[^0-9+] 匹配所有非数字和非加号字符,直接删除。然后再根据业务规则补全。

避坑指南: 有些号码带分机号 x123,别一刀切删掉。我一般先提取主号码,再单独处理分机。

4. 合并重复数据 —— 基于关键字段去重

重复数据通常指“姓名+电话”相同,或者“邮箱”相同。用C语言做去重,我习惯用哈希表或者有序数组。
但这里我们聚焦正则:先用正则提取关键字段,然后比较。

// 假设每行格式: "姓名,电话,邮箱"
// 用正则提取电话和邮箱作为key
regex_t key_regex;
regcomp(&key_regex, "^([^,]+),([^,]+),([^,]+)$", REG_EXTENDED);
// 然后对每一行执行regexec,捕获name, phone, email
// 用哈希表记录phone+email组合,遇到重复则跳过

你想想看,如果数据量不大,直接用双重循环也能凑合。但上万条记录时,O(n²)就太慢了。
我个人的做法:用POSIX正则提取字段后,构建一个简单的哈希表(链表法),速度杠杠的。

🧠 小技巧: 去重之前先做空白和日期清洗,否则“张三 ”和“张三”会被当成两条数据。

整合:数据清洗管线

上面四个步骤可以串成一条管线。我通常按这个顺序:

  1. 去除空白 —— 先清理多余空格和不可见字符
  2. 标准化电话号码 —— 只保留数字和加号
  3. 统一日期格式 —— 所有日期变成 YYYY-MM-DD
  4. 合并重复数据 —— 基于清洗后的字段去重

顺序很重要。我曾经先合并再去空白,结果重复数据没识别出来,因为一个带空格一个不带。后来就学乖了。

核心代码框架(伪代码 + 正则):

// 伪代码展示清洗管线
void data_clean(FILE *input, FILE *output) {
    char line[1024];
    while (fgets(line, sizeof(line), input)) {
        clean_whitespace(line);      // 步骤1
        normalize_phone(line);       // 步骤2
        normalize_date(line);        // 步骤3
        if (!is_duplicate(line)) {   // 步骤4
            fputs(line, output);
            mark_as_seen(line);
        }
    }
}

说实话,C语言的正则库(<regex.h>)不如Perl或Python方便,但胜在轻量、无依赖。
我一般在嵌入式或者性能敏感的场景下用这套方案。你如果只是写一次性脚本,用Python更省事。但想深入底层,C的正则绝对值得掌握。

⚠️ 注意: POSIX正则不支持 \d\s 这类简写,必须用 [[:digit:]][[:space:]]
我第一次从Python转C时,在这里卡了半小时。切记!

好了,这一章我们走马观花看了四个清洗场景。每个场景都能单独写成一个模块。
下一章我们会深入每个函数的实现细节,包括内存管理和边界情况。不过那是后话了。

(本章为第1章 · 实战案例13 数据清洗工具)

公众号:蓝海资料掘金营,微信deep3321