📌 实战案例13:数据清洗工具
数据清洗,说白了就是给脏数据“搓澡”。
我做了这么多年C语言开发,接过不少数据处理的活儿。最头疼的不是算法,而是那些格式乱七八糟的文本。
空白字符、日期格式、电话号码、重复记录…… 你想想看,如果手工清理,几千行数据能让人崩溃。
正则表达式就是干这个的。今天咱们就手写一个轻量级的数据清洗工具,用C语言 + 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-5678 | 13812345678 | +8613812345678 |
| (010) 8888-6666 | 01088886666 | +861088886666 |
| +86 138 1234 5678 | 13812345678 | +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正则提取字段后,构建一个简单的哈希表(链表法),速度杠杠的。
整合:数据清洗管线
上面四个步骤可以串成一条管线。我通常按这个顺序:
- 去除空白 —— 先清理多余空格和不可见字符
- 标准化电话号码 —— 只保留数字和加号
- 统一日期格式 —— 所有日期变成 YYYY-MM-DD
- 合并重复数据 —— 基于清洗后的字段去重
顺序很重要。我曾经先合并再去空白,结果重复数据没识别出来,因为一个带空格一个不带。后来就学乖了。
核心代码框架(伪代码 + 正则):
// 伪代码展示清洗管线
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的正则绝对值得掌握。
\d、\s 这类简写,必须用 [[:digit:]]、[[:space:]]。我第一次从Python转C时,在这里卡了半小时。切记!
好了,这一章我们走马观花看了四个清洗场景。每个场景都能单独写成一个模块。
下一章我们会深入每个函数的实现细节,包括内存管理和边界情况。不过那是后话了。
(本章为第1章 · 实战案例13 数据清洗工具)