Luhn算法检验和验证

Luhn检验和验证

Luhn公式是一种广泛使用的系统,用于对标识号进行验证。它根据原始标识号,把每隔一个数字的值扩大一倍。然后把各个单独数字的值加在一起(如果扩大一倍后的值为2个数字,就把这两个数字分别相加)。如果相加之后可以被10整除,那么这个标识号就是合法的。

编写一个程序,接受一个任意长度的标识号,并根据Luhn公式确定这个标识号是否合法。这个程序在读取下一个字符之前必须处理之前所读取的那个字符。

过程有些复杂,在此上传一张图片以供各位理解:

记住:最终标识号的检验和应该能够被10整除,或者说应该以0结尾。

接下来我们一起分解问题

  1. 知道哪些数字需要扩大一倍。
  2. 对扩大一倍后大于等于10的数字,根据他们的单独数字进行处理。
  3. 知道已经到达了标识号的尾部。
  4. 分别读取每个数字。

(注:我们不需要按照特定的顺序处理这些问题。)

首先,我们处理扩大一倍后大于或等于10的数:

如果我们从单独的数字0~9开始并把它们扩大一倍,最大值将是18。因此,一共只有两种可能性:如果扩大一倍后的值为单个数字,就不需要再做处理;如果扩大一倍后的值大于或等于10,它的范围肯定在10~18之间,因此第一个数字总是为1.我们通过一个代码来验证一下:

 1     int digit;
 2     printf("Enter a single digit number,0-9:");
 3     scanf("%d",&digit);
 4     int doubledDigit = digit * 2;  //程序读取数字,并把它的值扩大一倍
 5     int sum;
 6     if(doubledDigit >= 10)
 7         sum = 1 + doubledDigit % 10;  //求和计算
 8     else
 9         sum = doubledDigit;
10     printf("Sum of digits in doubled number:%d\n",sum);  //输出求和结果 

验证结果如下:

我们可以把这段代码转化为一个短小的函数,这样就可以简化未来的代码了。(是不是很有远见呢?)

 1 int doubleDigitValue(int digit)
 2 {
 3     int doubledDigit = digit * 2;  //程序读取数字,并把它的值扩大一倍
 4     int sum;
 5     if(doubledDigit >= 10)
 6         sum = 1 + doubledDigit % 10;  //求和计算
 7     else
 8         sum = doubledDigit;
 9     return sum;
10 }

现在,我们读取标识号的单独数字:

如果我们以数值类型(例如int)的形式读取标识号,将会读取一个长长的数,需要处理很多事情。另外,可以读取的最大整数也是有限制的。但在该问题中,标识号可以是任意长度的。因此,我们必须逐字符读取。这意味着我们要知道怎样读取一个表示数字的字符并把它转换为整数类型,以便对它进行数学运算。来看以下代码:

1     char digit;
2     printf("Enter a one-digit number:");
3     scanf("%c",&digit);
4     int sum = digit;
5     printf("Is the sum of digits:%d?\n",sum); 

运行结果为:

字符7是以字符码值55存储的,因此当我们把这个字符作为整数时,得到的结果就是55.

因此,我们需要一种机制把字符7转换为整数7。

我们可以创建一张表,其中包含原值和目标值,还有两值之间的误差。

字符码-目标整数值
字符 字符码 目标整数值
0 48 0 48
1 49 1 48
2 50 2 48
3 51 3 48
4 52 4 48
5 53 5 48
6 54 6 48
7 55 7 48
8 56 8 48
9 57 9 48

字符码和目标整数值之差始终是48,因此我们需要做的就是使字符码减去这个值。而48正好是0的字符码,所以我们可以采用一种更通用、更容易理解的解决方案:就是减去字符0的字符码而不是减去像48这样预先确定的值:

1     char digit;
2     printf("Enter a one-digit number:");
3     scanf("%c",&digit);
4     int sum = digit - ‘0‘;
5     printf("Is the sum of digits:%d?\n",sum); 

运行结果为:

现在,我们转到问题的下一部分,判断哪些数字需要扩大一倍:

我们可以先试着把长度限制为6,则我们只需要读取6个数字,对它们进行求和,然后判断它们的和是否被10所整除,代码如下:

 1     char digit;
 2     int checksum = 0;
 3     printf("Enter a six-digit number:");
 4     for(int position = 1;position <= 6;position++){
 5         scanf("%c",&digit);
 6         checksum += digit - ‘0‘;
 7     }
 8     printf("Checksum is:%d\n",checksum);
 9     if(checksum%10 == 0)
10         printf("Valid:Checknum is divisible by 10\n");
11     else
12         printf("Invalid:Checknum is not divisible by 10\n");

运行结果为:

  

现在,我们需要为实际的Luhn检验公式增加逻辑,把从左边开始位置为奇数的数字扩大一倍。我们可以使用求摸操作符(%)确定奇数和偶数的位置,因为偶数的定义是它能够被2所整除。因此如果表达式位置%2的结果是1,这个位置就是奇数,应该把它扩大一倍。顺便插一句,在扩大一倍后,如果结果大于或等于10,还需要对这个结果的各个数字进行求和。代码如下(只需把for循环那改一下):

1     for(int position = 1;position <= 6;position++){
2         scanf("%c",&digit);
3         if(position%2 == 0) checksum += digit - ‘0‘;
4         else checksum += doubleDigitValue(digit - ‘0‘);
5     }

运行结果为:

到目前为止,我们在这个问题上已经取得很大的进展,但还需要完成一些步骤才能为任意长度的标识号编写代码。为了最终解决这个问题,我们需要采用分治法。

先考虑怎样处理长度为任意偶数的标识号。

我们所面临的第一个问题是怎样确定已经到达了标识号的末尾。如果用户输入了一个多位的标识号又按下了Enter键表示结束,并且我们是逐个字符读取输入的,那么在最后一个数字之后所读取的字符是什么呢?我们不妨用代码来试验一下:

1     printf("Enter a number:");
2     char digit;
3     while(1){
4         scanf("%c",&digit);
5         printf("%d\n",int(digit));
6     }

运行结果为:

  

输入1234,结果是49 50 51 52 10(结果基于ASCII码)。从运行结果中可以看出,10就是我们所寻找的结果,所以我们可以在前面的代码中用一个while循环代替for循环:

 1     //处理任意偶数长度的标识号
 2     char digit;
 3     int checksum = 0;
 4     int position = 1;
 5     printf("Enter a number with an even number of digits:");
 6     scanf("%c",&digit);  //读取第一个值
 7     while(digit != 10){  //用来检查字符码的值是否为行末符
 8         if(position%2 == 0)  //偶数位判断
 9          checksum += digit - ‘0‘;
10         else checksum += 2 * (digit - ‘0‘);
11         scanf("%c",&digit);  //读取每个后续的值
12         position++;
13     }
14     printf("Checksum is:%d\n",checksum);
15     if(checksum%10 == 0)
16         printf("Valid:Checksum is divisible by 10\n");
17     else
18         printf("Invalid:Checksum is not divisible by 10\n");

运行结果为:

 

现在已经解决了“怎样确定已经到达了标识号的末尾”的问题。

要穷尽每种可能性,标识号的长度必须是奇数或者偶数。如果我们预先知道长度,就可以知道应该把奇数位的数字或者偶数位的数字扩大一倍。但是,在读取完这个标识号之前,我们并不知道这个信息。在思考这个问题前,我们先来类比另外一个问题:

编写一个程序,从用户那里读取10个整数。在输入了所有的整数之后,要求显示这些数中正数或负数的数量。

编写思路:需要一个对正数进行计数的变量,并用另一个变量对负数进行计数。当用户在程序的最后指定了具体的请求时,只需显示适当的变量作为响应即可。代码如下:

 1     int number;
 2     int positiveCount = 0;
 3     int negativeCount = 0;
 4     for(int i = 1;i <= 10;i++){
 5         scanf("%d",&number);
 6         if(number > 0) positiveCount++;  //计数正值
 7         if(number < 0) negativeCount++;  //计数负值
 8     }
 9     char response;  //选择回答
10     printf("Do you want the (p)ositive or (n)egative count?");
11     getchar(); //吞掉回车
12     scanf("%c",&response);
13     if(response == ‘p‘)
14      printf("Positive Count is %d\n",positiveCount);
15     if(response == ‘n‘)
16      printf("Negative Count is %d\n",negativeCount);

运行结果为:

  

这个类比的问题显示了我们在解决Luhn检验和问题时所需要用到的方法:同时以两种方式追踪当前的检验和,分别是在标识符为奇数长度和偶数长度的情况下。当我们读取完这个编号并确定了它的真正长度时,再选择表示正确的检验和的变量。

现在,我们可以把所有的代码都集中在一起,来解决这个问题了。

开始AC!!!

 1     char digit;
 2     int oddLengthChecksum = 0;
 3     int evenLengthChecksum = 0;
 4     int position = 1;
 5     printf("Enter a number:");
 6     scanf("%c",&digit);
 7     while(digit != 10){
 8         if(position%2 == 0){
 9             oddLengthChecksum += doubleDigitValue(digit - ‘0‘);
10             evenLengthChecksum += digit - ‘0‘;
11         }
12         else{
13             oddLengthChecksum += digit - ‘0‘;
14             evenLengthChecksum += doubleDigitValue(digit - ‘0‘);
15         }
16         scanf("%c",&digit);
17         position++;
18     }
19     int checksum;
20     if((position - 1)%2 == 0) checksum = evenLengthChecksum;
21     else checksum = oddLengthChecksum;
22     printf("Checksum is:%d\n",checksum);
23     if(checksum%10 == 0)
24         printf("Valid:Checknum is divisible by 10\n");
25     else
26         printf("Invalid:Checknum is not divisible by 10\n");

运行结果为:

   

尾声:这篇博文写了一晚上,视力开始模糊了,而且还有一些头痛的症状,可能是昨天下午出去玩吹凉风了。不过今天还是很开心的,看着一个完整的算法被我们切成一小块一小块的细致分析和代码检验,沉浸于其中,一点点的接近真相,我感到兴奋和快乐!刚开始我还对函数调用和程序中的回车问题有所疑惑,不过在一位朋友的指点下我还是顺利通过了。最重要的是,我对这个算法也有了更深一步的了解与认识。

不得不承认,我开始喜欢上写作了,那么问题来了,写作也会如期而至地喜欢上我吗?

时间: 2024-12-14 18:47:21

Luhn算法检验和验证的相关文章

Luhn算法?验证银行卡是否有效

Luhn算法 1.从卡号最后一位数字开始,逆向将奇数位(1.3.5等等)相加. 2.从卡号最后一位数字开始,逆向将偶数位数字,先乘以2(如果乘积为两位数,则将其减去9),再求和. 3.将奇数位总和加上偶数位总和,结果应该可以被10整除. function checkCard($card) { $num = 0; $card = str_split(trim($card)); krsort($card); $i = 1; foreach($card as $val){ if ($i % 2) {/

Luhn 算法-- 信用卡号码的校验

信用卡号码的校验用的是Luhn算法: 旧IBM的工程师Hans Peter Luhn在1954年发明的. 当时被申请为专利,现在已经公开,进入公共知识领域,成为国际标准组织的一项标准: ISO/EC 7812-1. 从卡号最后一位数字开始,逆向将奇数位数字相加求和 从卡号最后一位数字开始,逆向将偶数位数字,先乘以2,如果乘积为两位数,则减去9 (或两位数字求和),再求和 将奇数位总和加上偶数位总和,结果可以被10整除. 1 int luhnCheck(vector<int> &digi

luhn算法

在codewars刷js题时,碰到一个luhn算法题,很简单,但如何写的优雅和简洁却很考验功力.先介绍下luhn算法吧: LUHN算法,主要用来计算信用卡等证件号码的合法性. 1.从卡号最后一位数字开始,偶数位乘以2,如果乘以2的结果是两位数,将两个位上数字相加保存. 2.把所有数字相加,得到总和. 3.如果信用卡号码是合法的,总和可以被10整除. 我的代码如下: function validate(n){ var arr = parseToArray(n); arr = walkToRepla

判断用户输入的银行卡号是否正确--基于Luhn算法的格式校验

开发中,有时候,为了打造更好的用户体验,同时减轻服务器端的压力,需要对于一些如,手机号码,银行卡号,身份证号码进行格式校验 下面是判断银行卡号输入是否正确的代码(基于Luhn算法的格式校验): iOS代码: /** *  银行卡格式校验 * *  @param cardNo 银行卡号 * *  @return */ + (BOOL) checkCardNo:(NSString*) cardNo{ int oddsum = 0;     //奇数求和 int evensum = 0;    //偶

银行卡号码的校验规则(Luhn算法/模10算法)

银行卡校验 可以用于前端需要用户输入银行卡时做初步校验 银行卡号码的校验采用Luhn算法,校验过程大致如下: 从右到左给卡号字符串编号,最右边第一位是1,最右边第二位是2,最右边第三位是3-. 从右向左遍历,对每一位字符t执行第三个步骤,并将每一位的计算结果相加得到一个数s. 对每一位的计算规则:如果这一位是奇数位,则返回t本身,如果是偶数位,则先将t乘以2得到一个数n,如果n是一位数(小于10),直接返回n,否则将n的个位数和十位数相加返回. 如果s能够整除10,则此号码有效,否则号码无效.

[技术栈]C#利用Luhn算法(模10算法)对IMEI校验

1.Luhn算法(模10算法) 通过查看ISO/IEC 7812-1:2017文件可以看到对于luhn算法的解释,如下图: 算法主要分为三步: 第一步:从右边第一位(最低位)开始隔位乘2: 第二步:把第一步所得的每一个数字加入到原来的数中,比如9*2=18,为1+8: 第三步:用以0结尾且大于第二步所获得的数的和的最小整数减去第二步所获得的合即可以获得校验位,如70-67=3,3即为校验位,如果第二步所有数字的和以0结尾,比如30.40.50等,那么校验为0: 2.IMEI校验 IMEI码由GS

『cs231n』作业1问题1选讲_通过代码理解K近邻算法&amp;交叉验证选择超参数参数

通过K近邻算法探究numpy向量运算提速 茴香豆的"茴"字有... ... 使用三种计算图片距离的方式实现K近邻算法: 1.最为基础的双循环 2.利用numpy的broadca机制实现单循环 3.利用broadcast和矩阵的数学性质实现无循环 图片被拉伸为一维数组 X_train:(train_num, 一维数组) X:(test_num, 一维数组) 方法验证 import numpy as np a = np.array([[1,1,1],[2,2,2],[3,3,3]]) b

《BI那点儿事》数据挖掘各类算法——准确性验证

准确性验证示例1:——基于三国志11数据库 数据准备: 挖掘模型:依次为:Naive Bayes 算法.聚类分析算法.决策树算法.神经网络算法.逻辑回归算法.关联算法提升图: 依次排名为: 1. 神经网络算法(92.69% 0.99)2. 逻辑回归算法(92.39% 0.99)3. 决策树算法(91.19% 0.98)4. 关联算法(90.60% 0.98)5. 聚类分析算法(89.25% 0.96)6. Naive Bayes 算法(87.61 0.96) Naive Bayes算法——分类矩

【算法】验证哥德巴赫猜想

问题来源 Timus Online Judge 网站上有这么一道题目:1356. Something Easier.这道题目的输入是一组  2 到 109 之间整数,对于每个输入的整数,要求用最少个数的素数的和来表示.这道题目的时间限制是 1 秒. 问题解答 我们知道著名的哥德巴赫猜想是: 任何一个充分大的偶数都可以表示为两个素数之和 于是我们有以下的 C 语言程序(1356.c): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22