本文发表于入职啦(公众号: ruzhila) 大家可以访问入职啦学习更多的编程实战。
问题描述
身份证号码的验证是一个常见的需求,身份证的号码由17位数字和1位校验码组成,身份证的格式由:
- 6位地址码
- 8位出生日期码 格式为YYYYMMDD
- 3位顺序码组成
- 最后一位是校验码
校验码的计算方法如下:
- 将前17位数字的权值分别乘以系数相加
- 将相加的结果除以11,得到余数
- 通过余数查找校验码的值
- 如果校验码的值等于身份证号码的最后一位,则校验通过
- 如果校验码的值等于10,则校验码为X
- 如果校验码的值不等于身份证号码的最后一位,则校验不通过
- 日期的校验,年份的范围是1900-2099年
- 日期的校验,月份的范围是1-12月
- 日期的校验,日期的范围是1-31日
- 日期的校验,闰年的2月份是29天
- 日期的校验,平年的2月份是28天
- 日期的校验,4、6、9、11月份是30天
- 日期的校验,1、3、5、7、8、10、12月份是31天
- 日期的校验,2月份是28天
- 日期的校验,闰年的2月份是29天
- 日期的校验,闰年的判断方法是年份能被4整除,但是不能被100整除,或者能被400整除
测试代码
assertEqual(id_verify("11010519491231002X"), 'X', "1");
assertEqual(id_verify("110105194912310021"), '\0', "2");
assertEqual(id_verify("350703198001001237"), '7', "2");
解决思路
根据 GB11643-1999中有关公民身份号码的规定,公民身份号码是特征组合码,由十七位数字本体码和一位数字校验码组成。 校验码的计算方法如下:
- 将前17位数字的权值分别乘以系数相加: {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2}
- 将相加的结果除以11,得到余数
- 通过余数查找校验码的值: {'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'}
- 对日期进行校验,年份的范围是1900-2099年,月份的范围是1-12月,日期的范围是1-31日,闰年的2月份是29天,平年的2月份是28天,4、6、9、11月份是30天,1、3、5、7、8、10、12月份是31天
代码实现
身份证号码的校验
如果身份证号码的校验通过,则返回校验码,否则返回'\0',表示校验不通过
char calc_checksum(const char *card) {
// 加权因子
int weight[] = {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2};
// 校验码对应值
char checkCodes[] = {'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'};
int sum = 0;
if (card == NULL) {
return '\0';
}
for (int i = 0; i < 17; i++) {
int c = card[i];
if (c < '0' || c > '9') {
return '\0';
}
sum += (c - '0') * weight[i];
}
return checkCodes[sum % 11];
}
日期的校验
int isvalid_date(const char *card) {
const char *date = card + 6;
int year = 0;
for (int i = 0; i < 4; i++) {
year = year * 10 + (date[i] - '0');
}
if (year < 1900 || year > 2099) {
return 0;
}
int month = 0;
for (int i = 4; i < 6; i++) {
month = month * 10 + (date[i] - '0');
}
if (month < 1 || month > 12) {
return 0;
}
int day = 0;
for (int i = 6; i < 8; i++) {
day = day * 10 + (date[i] - '0');
}
if (day < 1 || day > 31) {
return 0;
}
if (month == 2) {
if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {
if (day > 29) {
return 0;
}
} else {
if (day > 28) {
return 0;
}
}
} else if (month == 4 || month == 6 || month == 9 || month == 11) {
if (day > 30) {
return 0;
}
}
return 1;
}
完整代码请参考:
总结
日期和校验码分别实现了校验的逻辑,这样当验证码正确的情况下我们再做日期的校验,可以减少不必要的计算。 整个代码没有使用任何的库,只是使用了C语言的基本语法,通过这个练习,我们可以学习如何使用C语言实现一个简单的校验算法。 日期的解析是比较简单的,主要是对年份、月份和日期的校验,通过这个练习,我们可以学习如何解析日期。
所有的后端面试常见的问题,我们每天都会在我们的编程群里面讨论和Code review, 欢迎大家加入我们的编程群,一起学习和进步。
欢迎大家关注 入职啦 (公众号: ruzhila) ,获取更多有趣的编程挑战题和技术干货!