18位身份证编码校验——我国第二代居民身份证

75 篇文章 0 订阅

一道校验居民身份证编码校验码的小题,触发了我对我国第二代身份证整串编码的探究。


(笔记模板由python脚本于2023年12月14日 19:49:46创建,本篇笔记适合掌握Python字符串str基本数据类型,可以熟练应用于代码编写中的coder翻阅)


【学习的细节是欢悦的历程】


  自学并不是什么神秘的东西,一个人一辈子自学的时间总是比在学校学习的时间长,没有老师的时候总是比有老师的时候多。
            —— 华罗庚


等风来,不如追风去……


一道校验身份证编码校验码的小题
身份证18位编码校验
(触发了我对我国第二代身份证整串编码的探究)


本文质量分:

97

本文地址: https://blog.csdn.net/m0_57158496/article/details/135003059

CSDN质量分查询入口:http://www.csdn.net/qc


目 录

  • ◆ 我国第二代身份证18位编码校验
    • 1、题目描述
    • 2、算法解析
      • 2.1 校验校验码
      • 2.2 校验省份代码
      • 2.3 出生日期校验
      • 2.4 身份证编码信息解析
    • 3、完整源码(Python)



◆ 我国第二代身份证18位编码校验


1、题目描述


1、将身份证号码前17位数分别乘以不同的系数(第1~17位的系数分别为: 7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 2);

2、将这17位数字和系数相乘的结果相加;

3、用加出来和除以11,看余数是多少?

4、余数只可能有0 1 2 3 4 5 6 7 8 9 10这11个数字,其分别对应的最后一位身份证的号码为1 0 X 9 8 7 6 5 4 3 2;

5、通过上面得知如果余数是2,就会在身份证的第18位数字上出现罗马数字的X。如果余数是10,身份证的最后一位号码就是2。

  根据上述知识编制一个函数,用于判断给定18位数字字符串(唯一可以在最后位上带大写字母X),校验是否正确。



回页目录


2、算法解析


  要校验身份证编码最后一位,就得根据题目描述中的规则,用前17位数字计算出校验码,与输入比对。

要点:

1、输入都是str字符串类型得转整型int才可以进行数值计算;

2、将计算得出的余数与校验码字典对应,取出校验码。

如:
在这里插入图片描述

  对校验校验码的编程实现,触发了我对居民身份证编码的浓厚兴趣,搜索百科仔细学习后,相对整串18位编码来一次校验。这对于我仅仅会python基础,还是一个极具挑战和小活儿哩。😋😋


18位第二代居民身份证编码组成

1~6位:行政区划编码依次每两个数字代表省、市、县级行政区划;
7~14位:出生日期。年4位,月日各两位;
15~17位:同地区同一天出生的人的序号(奇数分配给男性,偶数分配给女性)
18位:校验码。


  可以点击“我国居民身份证”夸克百科词条,查阅关于“我国第二代居民身份证”的更多知识。


  接下来,就让我们正式开启“编排”身份证编码的快乐之旅——

在这里插入图片描述


2.1 校验校验码


  计算校验码的方案,前面已经阐述过了,这里就聊聊数据处理。拆分前17位数字并转换成整型列表,将17位系数也转成成整型列表,方便两两相乘。用zip函数列表解析出前17位数字编码和系数的积,sum函数求17个积总和,用其模11得其余数,在校验码字典中查出前17个数字编码对应的校验码,与输入比对校验。
  规则详见题目描述,点击蓝色文字跳转查阅。

  • 代码运行效果截屏图片
    在这里插入图片描述

Python代码


def isIdCheckcode(myId):
    ''' 校验居民身份证校验码 '''
    id17 = map(int, list(myId[:-1])) # 拆分**ID前17位并转整型。
    ratio = map(int, '7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 2'.split()) # 17位系数拆分并转整型。
    tailDict = dict(zip('0 1 2 3 4 5 6 7 8 9 10'.split(), '1 0 X 9 8 7 6 5 4 3 2'.split())) # ID末位总积和模11余数和实际字符对应字典。
    checkCode = tailDict.get(str(sum([i[0]*i[1] for i in zip(id17, ratio)]) % 11)) # 按ID编码规则计算模11余数。
    print(f"\n按编码规则计算出的ID校验码:{checkCode}")

    return checkCode == myId[-1]



回页目录



2.2 校验省份代码


  我只从夸克百科词条查出我国省级行政区划代码,只可以校验前两位省代码。如果加入市、县身份证编码数据,第3~6位也是可以校验的。😋

  如果输入前两个数字不在我国省行政区划身份证编码字典中,输入就不是合法第二代居民身份证编码。


我国身份证编码省级行政区划代码

北京市 110000
天津市 120000
河北省 130000
山西省 140000
内蒙古自治区 150000
辽宁省 210000
吉林省 220000
黑龙江省 230000
上海市 310000
江苏省 320000
浙江省 330000
安徽省 340000
福建省 350000
江西省 360000
山东省 370000
河南省 410000
湖北省 420000
湖南省 430000
广东省 440000
广西壮族自治区 450000
海南省 460000
重庆市 500000
四川省 510000
贵州省 520000
云南省 530000
西藏自治区 540000
陕西省 610000
甘肃省 620000
青海省 630000
宁夏回族自治区 640000
新疆维吾尔自治区 650000
台湾省(886) 710000
香港特别行政区(852) 810000
澳门特别行政区(853) 820000

由以上文本用字典解析式生成省级行政区划代码字典的python代码


    provinceDict = {i.split()[1][:2]: i.split()[0] for i in provinceText.split('\n')} # 从文本生成编码和省中文名称字符对应字典。


  • 代码运行效果截屏图片
    在这里插入图片描述

Python代码


def getProvince(myId):
    ''' 读取居民身份证行政省区划信息 '''
    provinceText = '''北京市 110000
天津市 120000
河北省 130000
山西省 140000
内蒙古自治区 150000
辽宁省 210000
吉林省 220000
黑龙江省 230000
上海市 310000
江苏省 320000
浙江省 330000
安徽省 340000
福建省 350000
江西省 360000
山东省 370000
河南省 410000
湖北省 420000
湖南省 430000
广东省 440000
广西壮族自治区 450000
海南省 460000
重庆市 500000
四川省 510000
贵州省 520000
云南省 530000
西藏自治区 540000
陕西省 610000
甘肃省 620000
青海省 630000
宁夏回族自治区 640000
新疆维吾尔自治区 650000
台湾省 710000
香港特别行政区 810000
澳门特别行政区 820000'''
    provinceDict = {i.split()[1][:2]: i.split()[0] for i in provinceText.split('\n')} # 从文本生成编码和省中文名称字符对应字典。
    #print(provinceDict) 调试用语句。
    return provinceDict.get(myId[:2])



回页目录


2.3 出生日期校验


  身份证编码中的出生日期是8位:年4位,月日各两位。首先校验年月日的合法性,即月份数字只可以是1~12,天数只可以是1~月底(28、29、30、31);再就是合理性校验,比如输入未来的日期(年份不可以大于当前年,同年月份不可以大于当前月,同年同月,天数不可以大于当天),否则就是不合理的。


  出生日期校验,难点在于确定生日当月天数,即身份证编码的第13、14位数字,不同月份最大数值不一样,分别是28、29、30和31。一般大小月份相对简单,把除2月外的月份分大小月份两组,可以用成员关系符“in”判定是30还是31。2月比较特殊,涉及闰月(即公历闰年)判定来确定是28还是29。如果统一写一个函数判定12个月天数,2月天数则需年月两个数据才可以,其它月份天数是每年固定不变的只要有月份数据就好。

  据此,我分别写了公历闰年判定函数leapyear(year),在该函数的助力下完成了公历12个月每月天数的确定函数编写。为方便,我用lambda单行匿名函数关键字把这两个函数“浓缩”成了一行,如同一个变量。

  • 计算2020\~2024年每个月的天数

    调用每月天数计算函数isdays代码
    在这里插入图片描述

    代码运行效果截屏图片
    在这里插入图片描述

两个“变量”似的单行匿名函数leapyear(year)和isdays(year, month)



leapyear = lambda year: (not year%4 and year%100) or not year%400 # 闰年判定单行匿名函数。


isdays = lambda x: 31 if x[1] in (1, 3, 5, 7, 8, 10, 12) else 30 if x[1] in (4, 6, 9, 11) else 29 if leapyear(x[0]) else 28 # 根据年月计算公历某年某月有几天的单行匿名函。



  • 出生日期异常提示效果截屏图片
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
      以上列举了拦截的部分不“合法”和不合理的出生日期输入。

Python代码


    # 8位生日字符串校验 #
    if tyear < year:
        print(f"\n\n{' 出生年份错误!请查证。':~^29}\n\n{'(未出生的人怎么可以编码呢?)':^27}")
        return # 打印错误提示后,返回None。
    elif year == tyear and month > tmonth:
        print(f"\n\n{' 出生月份错误!请查证。':~^29}\n\n{'(未出生的人怎么可以编码呢?)':^27}")
        return # 打印错误提示后,返回None。
    elif year == tyear and month == tmonth and day > tday:
        print(f"\n\n{' 出生日错误!请查证。':~^30}\n\n{'(未出生的人怎么可以编码呢?)':^27}")
        return # 打印错误提示后,返回None。
    elif not 0 < month <= 12:
        print(f"\n\n{' 出生月份错误!请查证。':~^29}\n")
        return # 打印错误提示后,返回None。
    elif not 0 < day <= isdays([year, month]):
        print(f"\n\n{' 出生日错误!请查证。':~^30}\n")
        return # 打印错误提示后,返回None。



回页目录


2.4 身份证编码信息解析


  如果输入身份证编码字符串省份代码、出生日期字符串、校验码均校验成功,则解析所属省份、出生日期、所属县当日出生排序、性别等信息,并计算当前年龄打印输出。


  • 代码运行效果截屏图片

    我的ID试码
    在这里插入图片描述

    百科词条示例身份证编码试码
    在这里插入图片描述

Python代码


    # 号码无误,读取信息返回 #
    sex = '男' if int(myId[-2])%2 else '女' # 三元操作语句替换中文性别。
    return f"\n“{myId}”隶属于“{province}”。\n号码所有者是一名{sex}性,\n出生于{year}{month}{day}日,今年{tyear-year}岁,\n在当地同一天出生的人中排第{int(myId[14:17])}位。"



回页目录


3、完整源码(Python)

(源码较长,点此跳过源码)

#!/sur/bin/nve python
# coding: utf-8
from time import localtime


'''

1、将身份证号码前17位数分别乘以不同的系数(第1~17位的系数分别为: 7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 2);
2、将这17位数字和系数相乘的结果相加;
3、用加出来和除以11,看余数是多少?
4、余数只可能有0 1 2 3 4 5 6 7 8 9 10这11个数字,其分别对应的最后一位身份证的号码为1 0 X 9 8 7 6 5 4 3 2;
5、通过上面得知如果余数是2,就会在身份证的第18位数字上出现罗马数字的X。如果余数是10,身份证的最后一位号码就是2。

根据上述知识编制一个函数,用于判断给定18位数字字符串(唯一可以在最后位上带大写字母X),校验是否正确。

'''


def isIdCheckcode(myId):
    ''' 校验居民身份证校验码 '''
    id17 = map(int, list(myId[:-1])) # 拆分**ID前17位并转整型。
    ratio = map(int, '7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 2'.split()) # 17位系数拆分并转整型。
    tailDict = dict(zip('0 1 2 3 4 5 6 7 8 9 10'.split(), '1 0 X 9 8 7 6 5 4 3 2'.split())) # ID末位总积和模11余数和实际字符对应字典。
    checkCode = tailDict.get(str(sum([i[0]*i[1] for i in zip(id17, ratio)]) % 11)) # 按ID编码规则计算模11余数。
    return checkCode == myId[-1], checkCode


leapyear = lambda year: (not year%4 and year%100) or not year%400 # 闰年判定单行匿名函数。


isdays = lambda x: 31 if x[1] in (1, 3, 5, 7, 8, 10, 12) else 30 if x[1] in (4, 6, 9, 11) else 29 if leapyear(x[0]) else 28 # 根据年月计算公历某年某月有几天的单行匿名函。

# 计算2020~2024每个月的天数 #
for i in range(2020, 2025):
    print()
    for j in range(1, 13):
        print(f"{j:>2} of {i}: 有{isdays((i, j))}天")


def isId(myId):
    ''' 校验居民身份证 '''

    '''
    18位身份证标准在国家质量技术监督局于1999年7月1日实施的GB11643-1999《公民身份号码》中做了明确的规定。
    因而,本程序仅验证我国第二代的18位居民身份证编码字符串。
    '''

    province = getProvince(myId)

    # 18位编码长度和省行政区划代码校验 #
    if len(myId) != 18:
        print(f"\n\n{' 第二代身份证编码位数不符!请查证。':~^23}\n")
        return # 打印错误提示后,返回None。
    elif not province:
        print(f"\n\n{' 省行政区划代码有误!请查证。':~^26}\n")
        return # 打印错误提示后,返回None。

    tyear, tmonth, tday = localtime()[:3] # 当前日期年月日。
    year = int(myId[6:10])
    month = int(myId[10:12])
    day = int(myId[12:14])
    
    # 8位生日字符串校验 #
    if tyear < year:
        print(f"\n\n{' 出生年份错误!请查证。':~^29}\n\n{'(未出生的人怎么可以编码呢?)':^27}")
        return # 打印错误提示后,返回None。
    elif year == tyear and month > tmonth:
        print(f"\n\n{' 出生月份错误!请查证。':~^29}\n\n{'(未出生的人怎么可以编码呢?)':^27}")
        return # 打印错误提示后,返回None。
    elif year == tyear and month == tmonth and day > tday:
        print(f"\n\n{' 出生日错误!请查证。':~^30}\n\n{'(未出生的人怎么可以编码呢?)':^27}")
        return # 打印错误提示后,返回None。
    elif not 0 < month <= 12:
        print(f"\n\n{' 出生月份错误!请查证。':~^29}\n")
        return # 打印错误提示后,返回None。
    elif not 0 < day <= isdays([year, month]):
        print(f"\n\n{' 出生日错误!请查证。':~^30}\n")
        return # 打印错误提示后,返回None。

    # 末位检验校验码 #
    checkcode = isIdCheckcode(myId)
    if not checkcode[0]:
        print(f"\n{f'程序计算出的校验码:{checkcode[1]}':30}\n\n{' 校验码错误!请查证。':~^30}\n")
        return # 打印错误提示后,返回None。

    # 号码无误,读取信息返回 #
    sex = '男' if int(myId[-2])%2 else '女' # 三元操作语句替换中文性别。
    return f"\n“{myId}”隶属于“{province}”。\n号码所有者是一名{sex}性,\n出生于{year}{month}{day}日,今年{tyear-year}岁,\n在当地同一天出生的人中排第{int(myId[14:17])}位。"


def getProvince(myId):
    ''' 读取居民身份证行政省区划信息 '''
    provinceText = '''北京市 110000
天津市 120000
河北省 130000
山西省 140000
内蒙古自治区 150000
辽宁省 210000
吉林省 220000
黑龙江省 230000
上海市 310000
江苏省 320000
浙江省 330000
安徽省 340000
福建省 350000
江西省 360000
山东省 370000
河南省 410000
湖北省 420000
湖南省 430000
广东省 440000
广西壮族自治区 450000
海南省 460000
重庆市 500000
四川省 510000
贵州省 520000
云南省 530000
西藏自治区 540000
陕西省 610000
甘肃省 620000
青海省 630000
宁夏回族自治区 640000
新疆维吾尔自治区 650000
台湾省 710000
香港特别行政区 810000
澳门特别行政区 820000'''
    provinceDict = {i.split()[1][:2]: i.split()[0] for i in provinceText.split('\n')} # 从文本生成编码和省中文名称字符对应字典。
    #print(provinceDict) 调试用语句。
    return provinceDict.get(myId[:2]) # 省份代码正确返回省份名称,错误返回空None。


if __name__ == '__main__':
    myId = input(f"\n\n输入:").strip()
    
    with open('/sdcard/Documents/csdn/IDs.txt', 'a') as f:
        f.write(f"\n{myId}")

    myId = '53010219200508011X' if not myId else myId # 如果没有输入,启用默认真实号码样例(来源:夸克百科词条内列举的ID号码)。

    if myId == '53010219200508011X':
        print(f"\n默认样例:“53010219200508011X”\n{f' (样例来源:夸克百科词条) ':~^29}")

    #checkcode = isIdCheckcode(myId)
    #right = '正确' if checkcode[0] else '错误'
    #print(f"\n\n输出:\n程序计算出的校验码是:{checkcode[1]}\nThe checkcode of ID“{myId}” is {checkcode[0]}.\n(身份证编码“{myId}”的校验码“{myId[-1]}”是{right}的。)")
    
    isid = isId(myId) # 调用ID校验函数校验ID编码。
    right = '正确' if isid else '错误'
    idInfo = f"\n\n号码信息:{isid}" if isid else ''
    print(f"\n\n输出:\nThe ID“{myId}” is {'True' if isid else 'Flase'}\n(身份证编码“{myId}”是{right}的。){idInfo}\n")



回页首


上一篇:  随机拆分红包金额(随机拆分给定金额为给定个数红包,像微信、QQ、支付宝随机红包那种,要求红包总金额绝对与给定金额相等)
下一篇: 



我的HOT博:

  本次共计收集289篇博文笔记信息,总阅读量44.72w。数据采集于2023年12月11日 23:07:13,用时5分11.8秒。阅读量不小于4.0k的有17篇。

    评论 2
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包

    打赏作者

    梦幻精灵_cq

    你的鼓励将是我创作的最大动力

    ¥1 ¥2 ¥4 ¥6 ¥10 ¥20
    扫码支付:¥1
    获取中
    扫码支付

    您的余额不足,请更换扫码支付或充值

    打赏作者

    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值