Skip to content

M28664: 验证身份证号

dict, http://cs101.openjudge.cn/pctbook/M28664/

中华人民共和国公民的身份证号码由 18 位数字或 X 组成,其中最后一位可能是 X。 身份证号码的前6位表示行政区划代码,第7位到第 14 位表示出生日期,第 15 位到第 17 位表示顺序码,第 18 位表示校验码。 现给定若干个身份证号,请检验身份证号是否合法。如果合法,输出 YES,否则输出 NO。 保证前 17 位数字合法,因此你只需要检验第 18 位校验码是否合法即可。

校验码的计算方法如下: - 将前面的身份证号码 17 位数分别乘以不同的系数。从第1位到第 17 位的系数分别为7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2。 - 将这 17位数字和系数相乘的结果相加。 - 用加出来的和除以 11,看余数是多少。 - 余数只可能有0,1,2,3,4,5,6,7,8,9,10这11个数字。其分别对应的最后一位身份证的号码为1,0,X,9,8,7,6,5,4,3,2。(即余数0对应1,余数1对应0,余数2对应X...)

输入

共几+1行。 第一行一个正整数n,保证1≤n≤50. 接下来几行,每一行为一个身份证号。(若最后一位为 X,则为大写字母 x)

输出

输出n行。 每行表示身份证号码是否合法。如果合法,输出 YES,否则输出 NO。

样例输入

2
371311200312247819
130631197601191234

样例输出

YES
NO

提示

tags: implementation

来源

2024fall-cs101: Algo DS (476)

python
n=int(input())  
orth=[7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2]  
prod=[1,0,"X",9,8,7,6,5,4,3,2]  
for k in range(n):  
    iden=input()  
    res=0  
    for k in range(0,17):  
        res=(res+int(iden[k])*orth[k])%11  
    if iden[17]=="X":  
        if res==2:  
            print("YES")  
        else:  
            print("NO")  
    else:  
        if prod[res]==int(iden[17]):  
            print("YES")  
        else:  
            print("NO")
python
# 定义权重数组
l = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]

# 读取测试用例数量
n = int(input())

for _ in range(n):
    s = input()
    
    # 检查输入字符串的长度
    if len(s) != 18:
        print('NO')
        continue
    
    # 计算校验码
    x = sum(int(s[i]) * l[i] for i in range(17)) % 11
    x = (12 - x) % 11
    
    # 处理特殊情况
    if x == 10:
        x = 'X'
    
    # 比较校验码
    if s[17] == str(x):
        print('YES')
    else:
        print('NO')

思路:

本题涉及多个一一对应关系,使用 zip()、列表推导式和 dict(zip()) 能够简洁地建立映射关系。

身份证号码的最后一位是校验码,需要单独提取并与计算结果比对。使用 list.pop() 适合。

代码

python
n = int(input())
ratio = [7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2]
ends = [0,1,2,3,4,5,6,7,8,9,10]
pairs = [1,0,'X',9,8,7,6,5,4,3,2]
dictionary = dict(zip(ends,pairs))
for _ in range (n):
    line = list(input())
    last = line.pop()
    line = [int(i) for i in line]
    end = sum(a*b for a,b in zip(ratio,line))%11
    real_last = str((dictionary[end]))
    if last == real_last:
        print('YES')
    else:
        print('NO')

这段代码是一个用于验证中国居民身份证号码(18位)校验位的程序。

python
# 定义加权因子字符串(通过字母编码,避免显式列表)
# 对应的权重为:[7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2, 1]
# 原理:ord('h') - ord('a') = 104 - 97 = 7,以此类推
WEIGHT_CODE = 'hjkfiecbgdhjkfiecb'

# 模11后对应的校验码余数表(用于验证总和模11是否应为1)
# 这是一个数学性质:合法身份证的加权总和 % 11 == 1
# 详细推导见下方说明

def verify_id_card(id_number):
    """
    验证一个18位中国居民身份证号码是否合法(仅校验位验证)
    
    参数:
        id_number (str): 18位身份证号码,最后一位可以是'X'或'x'
    
    返回:
        bool: 合法返回 True,否则返回 False
    """
    # 步骤1:检查长度是否为18位
    if len(id_number) != 18:
        return False

    # 步骤2:将输入中的 'X' 或 'x' 替换为 ':',以便 ord(':') - 48 = 10
    # 这是一个巧妙的ASCII技巧,避免额外判断
    cleaned_id = id_number.replace('X', ':').replace('x', ':')

    # 步骤3:检查前17位是否全为数字,第18位是否为数字或':'
    if not cleaned_id[:17].isdigit() or not (cleaned_id[17].isdigit() or cleaned_id[17] == ':'):
        return False

    # 步骤4:计算加权和
    total_weighted_sum = 0
    for i in range(18):
        # 获取第i位的权重(通过字符编码转换)
        weight = ord(WEIGHT_CODE[i]) - ord('a')
        # 获取第i位的数值(字符转数字,':' 表示10)
        digit_value = ord(cleaned_id[i]) - 48  # ord('0') = 48
        total_weighted_sum += weight * digit_value
        # 每步取模防止整数溢出(可选,但安全)
        total_weighted_sum %= 11

    # 步骤5:根据数学性质,合法身份证的加权和模11必须等于1
    return total_weighted_sum == 1


def main():
    """主函数:读取多个身份证号码并验证"""
    try:
        n = int(input().strip())  # 输入测试用例数量
        for _ in range(n):
            identity = input().strip()  # 读取身份证号码
            if verify_id_card(identity):
                print('YES')
            else:
                print('NO')
    except Exception as e:
        # 防止输入异常导致程序崩溃
        print("NO")


# 运行程序
if __name__ == '__main__':
    main()

数学原理 :如果整个18位都合法,则总加权和模11恒等于 1

python
(Σ_{i=0}^{17} weight[i] * digit[i]) % 11 == 1

1. code = 'hjkfiecbgdhjkfiecb'

  • 这是一个长度为18的字符串,但它实际上代表的是身份证校验算法中的 加权因子(Weight Factors)

  • 在身份证校验中,每一位有一个固定的加权因子,从左到右依次是:

    [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2, 1]
  • 但是这里用字母表示,是因为:
    ord('h') - ord('a') = 107 - 97 = 10?不对!

    实际上,我们来验证一下:

    • 'h' -> ord('h')=104 -> 104 - 97 = 7
    • 'j' -> 106 - 97 = 9
    • 'k' -> 107 - 97 = 10
    • 'f' -> 102 - 97 = 5
    • 'i' -> 105 - 97 = 8
    • 'e' -> 101 - 97 = 4
    • 'c' -> 99 - 97 = 2
    • 'b' -> 98 - 97 = 1
    • 'g' -> 103 - 97 = 6
    • 'd' -> 100 - 97 = 3

所以 code = 'hjkfiecbgdhjkfiecb' 实际上对应的是加权因子数组:

[7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2,1]

即每一位的权重。


2. identity = input().replace('X', ':')

  • 读入一个身份证号码字符串。

  • 将其中的 'X' 替换为 ':'。这是关键一步,为什么?

    原因:

    • 身份证最后一位可能是 'X'(代表数字10),但在计算时需要当作 10 处理。
    • ord('X') = 88,而 ord('0') = 48,所以 ord('X') - 48 = 40,这不对。
    • ord(':') = 58,而 58 - 48 = 10,正好!

    所以这句的作用是:

    把身份证最后一位的 'X' 变成一个 ASCII 码为 58 的字符(即 ':'),这样在后续计算 ord(ch) - 48 时就能得到 10

    ✅ 这是一个巧妙的技巧,避免了单独判断 'X' 的情况。

3.print('YES' if summ == 1 else 'NO')

这是因为:当整个18位都是合法时,总加权和模11的结果恒为1

我们来验证一下:

假设前17位加权和为 S,第18位为 a,则总加权和为 S + 1*a(最后一位权重是1)。

S % 11 = r,则正确校验位应为 check[r],其中 check = [1,0,'X',9,8,7,6,5,4,3,2]

那么总加权和为:S + check[r],模11后应为:

(S + check[r]) % 11 = (r + check[r]) % 11

我们看是否恒为1:

rcheck[r](r + check[r]) % 11
01(0+1)%11 = 1 ✅
10(1+0)%11 = 1 ✅
2'X'=10(2+10)%11 = 12%11=1 ✅
3912%11=1 ✅
4812%11=1 ✅
5712%11=1 ✅
6612%11=1 ✅
7512%11=1 ✅
8412%11=1 ✅
9312%11=1 ✅
10212%11=1 ✅

惊人发现:无论 r 是多少,(r + check[r]) % 11 = 1

所以,只要身份证是合法的,总加权和模11一定等于1


验证一个18位身份证号码是否合法。利用了一个数学性质:

合法身份证号码的每一位数字乘以其对应的加权因子(7,9,10,...,1),然后总和模11的结果恒为1


补充说明

  • 加权因子:[7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2,1]
  • 模11后余数对应的校验码:[1,0,X,9,8,7,6,5,4,3,2]
  • 本代码通过 replace('X', ':') 巧妙处理 'X'(因为 ord(':')-48=10
  • 最终判断 (总加权和 % 11) == 1 来决定是否合法。