67
\$\begingroup\$

Challenge

Write the shortest program or function to calculate the Luhn Algorithm for verifying (credit card) numbers.

Luhn algorithm explained

From RosettaCode, this algorithm for the purposes of this challenge is specified as such, with the example input of 49927398716:

Reverse the digits, make an array:
 6, 1, 7, 8, 9, 3, 7, 2, 9, 9, 4
Double the numbers in odd indexes:
 6, 2, 7, 16, 9, 6, 7, 4, 9, 18, 4
Sum the digits in each number:
 6, 2, 7, 7, 9, 6, 7, 4, 9, 9, 4
Sum all of the numbers:
 6 +たす 2 +たす 7 +たす 7 +たす 9 +たす 6 +たす 7 +たす 4 +たす 9 +たす 9 +たす 4 = 70
If the sum modulo 10 is 0, then the number is valid:
 70 % 10 = 0 => valid

IO Rules

Input: A string or number (your choice), in your language's input/output format of choice

Output: A truthy or falsy value, respectively, indicating whether or not the input is valid according to the test above.

Notes / Tips

  • Try not to accidentally post your own credit card or account numbers, if you use them to test :)

  • If the input is invalid and impossible to process with the specified algorithm (i.e, too short to work with), you can do whatever you want, including blow up my computer.

  • However, the previous bullet does not mean that your language can do whatever it wants with Numbers that are too large for it to handle. If your language isn't capable of handling a test case, then consider taking a string as input.

Examples

The following examples were validated with this Python script; if you think one is wrong or have a question, just ping @cat.

49927398716 True
49927398717 False
1234567812345670 True 
1234567812345678 False
79927398710 False
79927398711 False
79927398712 False
79927398713 True
79927398714 False
79927398715 False
79927398716 False
79927398717 False
79927398718 False
79927398719 False
374652346956782346957823694857692364857368475368 True
374652346956782346957823694857692364857387456834 False
8 False **
0 True **

** according to the Python implementation, but you may do anything because these are too short to be eligible by a strict adherence to the specification.


If any of the above invalidates existing answers (though I believe that should not be possible), then those answers are stil valid. However, new answers, in order to be valid, should follow the specification above.

Leaderboard

var QUESTION_ID=22,OVERRIDE_USER=73772;function answersUrl(e){return"https://api.stackexchange.com/2.2/questions/"+QUESTION_ID+"/answers?page="+e+"&pagesize=100&order=desc&sort=creation&site=codegolf&filter="+ANSWER_FILTER}function commentUrl(e,s){return"https://api.stackexchange.com/2.2/answers/"+s.join(";")+"/comments?page="+e+"&pagesize=100&order=desc&sort=creation&site=codegolf&filter="+COMMENT_FILTER}function getAnswers(){jQuery.ajax({url:answersUrl(answer_page++),method:"get",dataType:"jsonp",crossDomain:!0,success:function(e){answers.push.apply(answers,e.items),answers_hash=[],answer_ids=[],e.items.forEach(function(e){e.comments=[];var s=+e.share_link.match(/\d+/);answer_ids.push(s),answers_hash[s]=e}),e.has_more||(more_answers=!1),comment_page=1,getComments()}})}function getComments(){jQuery.ajax({url:commentUrl(comment_page++,answer_ids),method:"get",dataType:"jsonp",crossDomain:!0,success:function(e){e.items.forEach(function(e){e.owner.user_id===OVERRIDE_USER&&answers_hash[e.post_id].comments.push(e)}),e.has_more?getComments():more_answers?getAnswers():process()}})}function getAuthorName(e){return e.owner.display_name}function process(){var e=[];answers.forEach(function(s){var r=s.body;s.comments.forEach(function(e){OVERRIDE_REG.test(e.body)&&(r="<h1>"+e.body.replace(OVERRIDE_REG,"")+"</h1>")});var a=r.match(SCORE_REG);a&&e.push({user:getAuthorName(s),size:+a[2],language:a[1],link:s.share_link})}),e.sort(function(e,s){var r=e.size,a=s.size;return r-a});var s={},r=1,a=null,n=1;e.forEach(function(e){e.size!=a&&(n=r),a=e.size,++r;var t=jQuery("#answer-template").html();t=t.replace("{{PLACE}}",n+".").replace("{{NAME}}",e.user).replace("{{LANGUAGE}}",e.language).replace("{{SIZE}}",e.size).replace("{{LINK}}",e.link),t=jQuery(t),jQuery("#answers").append(t);var o=e.language;/<a/.test(o)&&(o=jQuery(o).text()),s[o]=s[o]||{lang:e.language,user:e.user,size:e.size,link:e.link}});var t=[];for(var o in s)s.hasOwnProperty(o)&&t.push(s[o]);t.sort(function(e,s){return e.lang>s.lang?1:e.lang<s.lang?-1:0});for(var c=0;c<t.length;++c){var i=jQuery("#language-template").html(),o=t[c];i=i.replace("{{LANGUAGE}}",o.lang).replace("{{NAME}}",o.user).replace("{{SIZE}}",o.size).replace("{{LINK}}",o.link),i=jQuery(i),jQuery("#languages").append(i)}}var ANSWER_FILTER="!t)IWYnsLAZle2tQ3KqrVveCRJfxcRLe",COMMENT_FILTER="!)Q2B_A2kjfAiU78X(md6BoYk",answers=[],answers_hash,answer_ids,answer_page=1,more_answers=!0,comment_page;getAnswers();var SCORE_REG=/<h\d>\s*([^\n,]*[^\s,]),.*?(\d+)(?=[^\n\d<>]*(?:<(?:s>[^\n<>]*<\/s>|[^\n<>]+>)[^\n\d<>]*)*<\/h\d>)/,OVERRIDE_REG=/^Override\s*header:\s*/i;
body{text-align:left!important}#answer-list,#language-list{padding:10px;width:290px;float:left}table thead{font-weight:700}table td{padding:5px}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <link rel="stylesheet" type="text/css" href="//cdn.sstatic.net/codegolf/all.css?v=83c949450c8b"> <div id="answer-list"> <h2>Leaderboard</h2> <table class="answer-list"> <thead> <tr><td></td><td>Author</td><td>Language</td><td>Size</td></tr></thead> <tbody id="answers"> </tbody> </table> </div><div id="language-list"> <h2>Winners by Language</h2> <table class="language-list"> <thead> <tr><td>Language</td><td>User</td><td>Score</td></tr></thead> <tbody id="languages"> </tbody> </table> </div><table style="display: none"> <tbody id="answer-template"> <tr><td>{{PLACE}}</td><td>{{NAME}}</td><td>{{LANGUAGE}}</td><td>{{SIZE}}</td><td><a href="{{LINK}}">Link</a></td></tr></tbody> </table> <table style="display: none"> <tbody id="language-template"> <tr><td>{{LANGUAGE}}</td><td>{{NAME}}</td><td>{{SIZE}}</td><td><a href="{{LINK}}">Link</a></td></tr></tbody> </table>

Titus
14.9k1 gold badge25 silver badges41 bronze badges
asked Jan 27, 2011 at 21:43
\$\endgroup\$
0

69 Answers 69

1
2 3
24
\$\begingroup\$

Golfscript - 24 chars

-1%{2+0!:0)*109%+}*10%8=

Explanation:

  1. -1% reverses the string
  2. { begins a block (which we use as a loop). Each character in the strings is pushed as it's ascii value.
    1. 2+ adds 2. (the ascii value of a digit is 48+n, so we have 50+n now and the last digit is n)
    2. 0!:0 inverts the value of 0 and stores it (everything is a variable), so we have 1 on the first iteration, 0 on the second, etc.
    3. )* adds one to this value and multiplies it, so we multiply by 2, then 1, then 2, etc.
    4. 109% is remainder modulo 109. This affects only values 5-9 which have been doubled and reduces them to the correct value.
    5. + adds this value to the running sum
  3. }* ends the block and does a 'fold' operation. First, the first character is pushed (since we have reversed, this is the check digit). Then, alternate pushing and executing the block. Thus, we are using the first character's ascii value as the starting value for the running sum.
  4. 10% takes the remainder modulo 10.
  5. 8= will return 1 if the value is 8. We use this because we did not normalize the first pushed character (the check digit).

One might think that we could use 8- instead of 2+ to save a character by changing 109% to 89%, except then we would need to add a space so the - is subtraction (instead of -0).

answered Feb 2, 2011 at 16:56
\$\endgroup\$
12
\$\begingroup\$

Python, (削除) 73 (削除ここまで) 69 characters

def P(x):D=map(int,x);return sum(D+[d-d/5*9for d in D[-2::-2]])%10==0
answered Jan 29, 2011 at 8:53
\$\endgroup\$
2
  • 5
    \$\begingroup\$ You can save two more chars by not looping backwards : D[-2::-2] -> D[1::2] as the order of a sum isn't important :) \$\endgroup\$ Commented Feb 1, 2014 at 23:14
  • 3
    \$\begingroup\$ ==0 can be shortened to <1 \$\endgroup\$ Commented Mar 23, 2019 at 10:54
11
\$\begingroup\$

GolfScript, 44 chars

-1%{16%}%2/1,\+{(\.{0=2*.9>9*-+}{;}if+}*10%!

Selected commentary

Interestingly, the first two items below demonstrate three completely different uses of the % operator: array selection, map, and mod. Most GolfScript operators are "context-sensitive", giving them hugely divergent behaviours depending on what types the arguments are.

  1. -1% reverses the string. This is important as the digit pairs are counted from the right.
  2. {16%}% converts all the ASCII digits into numbers, by modding them with 16.
  3. 2/ splits the array into groups of 2.
  4. 1, is a cheap way to do [0].
  5. \+ effectively prepends the 0 to the digits array. It does this by swapping then concatenating.

The 0 is prepended in preparation for the fold that comes in next. Rather than taking an explicit initial value, GolfScript's fold uses the first item in the array as the initial value.

Now, let's look at the actual fold function. This function takes two arguments: the folded value, and the current item on the array (which in this case will be an array of 2 or (uncommonly) 1, because of the 2/ earlier). Let's assume the arguments are 1 [2 3].

  1. (\. splits out the leftmost array element, moves the remaining array to the front, then copies it. Stack now looks like: 1 2 [3] [3].
  2. The if checks if the array is empty (which is the case for the last group when dealing with an odd-sized account number). If so, then no special processing happens (just pop off the empty array).
  3. For an even group:
    1. 0= grabs the first (only, in this case) element of the array. 1 2 3
    2. 2* doubles the number. 1 2 6
    3. .9>9*- subtracts 9 from the number if it's greater than 9. Implemented as: copy the number, compare with 9, multiply the result (which is either 0 or 1) with 9, then subtract. 1 2 6
    4. + finally adds that to the first number. 1 8
  4. + (after the if) adds the result of the if to the original value, resulting in the new folded value.

After the folding completes, we simply mod with 10 (10%), and negate the result (!), so that we return 1 iff the sum is a multiple of 10.

answered Jan 29, 2011 at 4:30
\$\endgroup\$
6
  • \$\begingroup\$ This seems to return 0 for the example number on wikipedia (49927398716) \$\endgroup\$ Commented Feb 1, 2011 at 10:51
  • \$\begingroup\$ nm. I forgot to use echo -n \$\endgroup\$ Commented Feb 1, 2011 at 11:49
  • 1
    \$\begingroup\$ @gnibbler: Haha, fail. :-P (Seriously, I got stung by that one too, in my initial testing.) \$\endgroup\$ Commented Feb 1, 2011 at 13:06
  • 1
    \$\begingroup\$ A few places to save a few easy characters here. -1% 2/ can be combined into -2/. 1, can be replaced with 0 (0 is coerced to an array, then + is concatenate). 9>9*- can be replaced with 9>+ (since we're only concerned with the last digit). Also, the checking for odd lengths is a bit long, using .,2%,\+ is shorter. After doing this, we can also change {16%}% and (0円= into {16}/ (inside the loop). Once you've done all that, it'll look something like this: .,2%,\+-2/0\+{{16%}/2*.9>+++}*10%!. \$\endgroup\$ Commented Feb 2, 2011 at 17:15
  • \$\begingroup\$ @Nabb: Thanks! I'll incorporate those into my solution, although it looks like you already have one that's kicking serious arse. :-) \$\endgroup\$ Commented Feb 2, 2011 at 17:40
10
\$\begingroup\$

C# 119 characters:

bool l(string n){return(String.Join("",n.Reverse().Select((x,i)=>(x-48)*(i%2<1?1:2)+"").ToArray()).Sum(x=>x-48))%10<1;}

Not too bad for a code golf n00b in a statically typed language, I hope.

This can be reduced to 100:

bool l(string n){return String.Join("",n.Reverse().Select((x,i)=>(x-48)*(i%2+1))).Sum(x=>x+2)%10<1;}
answered Jan 28, 2011 at 6:54
\$\endgroup\$
3
  • \$\begingroup\$ It's a good idea, and an interesting approach, but it doesn't appear to work. At least not with my few tests. It looks like the "i" in your first lambda is supposed to be the index of the character in the string. Does that work as it should? If so, why do you reverse the string only to then modify it based on index position? Seems a bit redundant, no? \$\endgroup\$ Commented Feb 1, 2011 at 23:48
  • \$\begingroup\$ I only tested one of my credit cards and a few off by one errors from it TBH. (Using the VS 2008 debugger) The algorithm is supposed to double every second digit starting with the LAST digit. If I didn't reverse the string, it would be incorrect for strings with odd lengths. \$\endgroup\$ Commented Feb 2, 2011 at 1:37
  • \$\begingroup\$ Turns out I did have the result of i%2<1?1:2 backwards. Thanks. \$\endgroup\$ Commented Feb 2, 2011 at 1:59
10
\$\begingroup\$

Python 3, 77 bytes

c=lambda a:sum(sum(divmod(int(a[-e-1])<<e%2,10))for e in range(len(a)))%10==0
Toto
1,0311 gold badge14 silver badges25 bronze badges
answered Jan 27, 2011 at 22:07
\$\endgroup\$
0
8
\$\begingroup\$

Golfscript - 34 chars

{15&}%.-2%\);-2%{.+(9%)}%+{+}*10%!

Example number from wikipedia page 4992739871

{15&}% does a bitwise and of each ascii digit with 00001111
 now I have a list of digits 
 [4 9 9 2 7 3 9 8 7 1 6]
. makes a copy of the list, now I have two identical lists
 [4 9 9 2 7 3 9 8 7 1 6] [4 9 9 2 7 3 9 8 7 1 6]
-2% like [::-2] in python takes every second element in reverse
 [4 9 9 2 7 3 9 8 7 1 6] [6 7 9 7 9 4]
\ swap the two lists around
 [6 7 9 7 9 4] [4 9 9 2 7 3 9 8 7 1 6]
); drop the last digit off the list
 [6 7 9 7 9 4] [4 9 9 2 7 3 9 8 7 1]
-2% same as before
 [6 7 9 7 9 4] [1 8 3 2 9]
{ for each item in the list ...
.+ ... double it ...
( ... subtract 1 ...
9% ... mod 9 ...
)}% ... add 1 ...
 [6 7 9 7 9 4] [2 7 6 4 9]
+ join the two lists
 [6 7 9 7 9 4 2 7 6 4 9]
{+}* add the elements up
 70
10% mod 10
 0
! invert the result
 1
answered Feb 1, 2011 at 11:59
\$\endgroup\$
4
  • \$\begingroup\$ The .+(9%) is very innovative (for me, anyway). I like! +1 \$\endgroup\$ Commented Feb 1, 2011 at 13:09
  • \$\begingroup\$ Srsly though, GolfScript needs a partitioning operator, so you don't need to do this "drop end item off and repeat" nonsense. :-) \$\endgroup\$ Commented Feb 1, 2011 at 13:14
  • 1
    \$\begingroup\$ @Chris, I learned about that many years ago called "casting out nines". It is a neat way to double check longhand additions and multiplications \$\endgroup\$ Commented Feb 1, 2011 at 13:22
  • 3
    \$\begingroup\$ This won't work for a value of 0 being doubled (0(9%) is 9 and not 0). \$\endgroup\$ Commented Feb 2, 2011 at 16:59
8
\$\begingroup\$

PHP, 108 bytes

<?function v($s,$t=0){for($i=strlen($s);$i>=0;$i--,$c=$s[$i])$t+=$c+$i%2*(($c>4)*-4+$c%5);return!($t % 10);}
Toto
1,0311 gold badge14 silver badges25 bronze badges
answered Jan 27, 2011 at 22:51
\$\endgroup\$
7
\$\begingroup\$

Ruby - 85 characters

def f s
l=s.size
s.chars.map{|k|(i=k.to_i*((l-=1)%2+1))%10+i/10}.inject(:+)%10==0
end
answered Jan 31, 2011 at 8:08
\$\endgroup\$
1
  • \$\begingroup\$ You probably know about this, but you could do .sum instead of .inject(:+) to save 7 bytes \$\endgroup\$ Commented Feb 1, 2018 at 18:58
7
\$\begingroup\$

Haskell, 96 bytes

There must be a better/shorter way, but here's my Haskell solution in 96 characters:

l=(==0).(`mod`10).sum.zipWith($)(cycle[id,\x->x`mod`5*2+x`div`5]).reverse.map((+(-48)).fromEnum)

Sadly the digitToInt function can only be used if you import Data.Char first. Otherwise I could get down to 88 characters by replacing ((+(-48)).fromEnum) with digitToInt.

answered Jan 31, 2011 at 5:14
\$\endgroup\$
6
\$\begingroup\$

Windows PowerShell, 82

filter f{!((''+($_[($_.length)..0]|%{+"$_"*($i++%2+1)})-replace'.','+$&'|iex)%10)}

History:

  • 2011年02月13日 03:08 (84) First attempt.
  • 2011年02月13日 12:13 (82) I don't need to join, as spaces do not hurt. +1 + +3 can still be evaluated.
answered Feb 13, 2011 at 2:08
\$\endgroup\$
6
\$\begingroup\$

D, 144 bytes

bool f(S)(S s){int t(C)(C c){return to!int(c)-'0';}int n,v;foreach(i,c;array(retro(s))){v=i&1?t(c)*2:t(c);n+=v>=10?v%10+v/10:v;}return n%10==0;}

More legibly:

bool f(S)(S s)
{
 int t(C)(C c)
 {
 return to!int(c) - '0';
 }
 int n, v;
 foreach(i, c; array(retro(s)))
 {
 v = i & 1 ? t(c) * 2 : t(c);
 n += v >= 10 ? v % 10 + v / 10 : v;
 }
 return n % 10 == 0;
}
answered Feb 3, 2011 at 22:29
\$\endgroup\$
6
\$\begingroup\$

Jelly, (削除) 12 (削除ここまで) (削除) 11 (削除ここまで) 9 bytes

ṚḤÐeDFS5ḍ

Try it online!

How it works

ṚḤÐeDFS5ḍ - Main link. Takes an integer n on the left
Ṛ - Cast n to digits and reverse
 Ðe - To values at even indices:
 Ḥ - Unhalve; Double
 D - Cast to digits
 F - Flatten
 S - Sum
 ḍ - Divisible by:
 5 - 10?
answered Oct 21, 2017 at 15:57
\$\endgroup\$
5
\$\begingroup\$

PowerShell 123

filter L($x){$l=$x.Length-1;$l..0|%{$d=$x[$_]-48;if($_%2-eq$l%2){$s+=$d}elseif($d-le4){$s+=$d*2}else{$s+=$d*2-9}};!($s%10)}
answered Feb 13, 2011 at 1:34
\$\endgroup\$
5
\$\begingroup\$

Q, 63

{0=mod[(+/)"I"$(,/)($)($)@["I"$'x;1+2*'(!)(_)((#)x)%2;*;2];10]}

usage

q){0=mod[(+/)"I"$(,/)($)($)@["I"$'x;1+2*'(!)(_)((#)x)%2;*;2];10]}"79927398711"
0b
q){0=mod[(+/)"I"$(,/)($)($)@["I"$'x;1+2*'(!)(_)((#)x)%2;*;2];10]}"79927398712"
0b
q){0=mod[(+/)"I"$(,/)($)($)@["I"$'x;1+2*'(!)(_)((#)x)%2;*;2];10]}"79927398713"
1b
answered Apr 7, 2012 at 18:35
\$\endgroup\$
1
  • \$\begingroup\$ 47 bytes with {0=mod[sum"J"$raze($)($)x*#:[x]#1 2]10}"I"$'(|) a different way to double the odd indices. \$\endgroup\$ Commented Oct 21, 2017 at 23:21
5
\$\begingroup\$

APL, 28 bytes

{0=10|+/⍎ ̈∊⍕ ×ばつ⌽2-2|⍳⍴v←⍎ ̈⍵}

Exploded view

{ v←⍎ ̈⍵} ⍝ turn the string into a numeric vector of its digits, v
 2-2|⍳⍴v ⍝ make a vector of the same length, with 2 in every 2nd place
 ×ばつ⌽ ⍝ multiply it with v, starting from the right
 ∊⍕ ̈ ⍝ turn each component into a string and collect all the digits
 +/⍎ ̈ ⍝ turn each digit again into a number and sum them
 0=10| ⍝ check whether the sum is a multiple of 10

Examples

 {0=10|+/⍎ ̈∊⍕ ×ばつ⌽2-2|⍳⍴v←⍎ ̈⍵} '79927398713'
1
 {0=10|+/⍎ ̈∊⍕ ×ばつ⌽2-2|⍳⍴v←⍎ ̈⍵} '123456789'
0
answered Feb 1, 2014 at 22:55
\$\endgroup\$
1
  • 1
    \$\begingroup\$ -2: {0=10|+/⍎¨∊⍕¨⍵×⌽2-2|⍳⍴⍵}⍎¨ \$\endgroup\$ Commented Mar 30, 2017 at 12:47
5
\$\begingroup\$

x86-16 machine code, 27 bytes

Binary:

00000000: bb00 0103 f1fd ac2c 30f6 df78 06d0 e0d4 .......,0..x....
00000010: 0a02 dc02 d8e2 ef93 d40a c3 ...........

Listing:

BB 0100 MOV BX, 100H ; running sum (BL) to 0, BH to a positive value
03 F1 ADD SI, CX ; start at end of input string
FD STD ; set LODSB direction to decrement
 DIGIT_LOOP: 
AC LODSB ; load next digit into AL, decrement SI
2C 30 SUB AL, '0' ; convert ASCII char to binary value 
F6 DF NEG BH ; flip sign of BH to alternate odd/even index
78 06 JS IS_EVEN ; if not odd index, do not double
D0 E0 SHL AL, 1 ; double the value
D4 0A AAM ; split digits (ex: 18 --> AH = 1, AL = 8) 
02 DC ADD BL, AH ; add tens digit to running sum
 IS_EVEN:
02 D8 ADD BL, AL ; add ones digit to running sum 
E2 EF LOOP DIGIT_LOOP 
93 XCHG AX, BX ; sum is in BL, move to AL for mod 10 check
D4 0A AAM ; ZF = ( AL % 10 == 0 ) 
C3 RET ; return to caller

Uses (abuses) x86's BCD-to-binary instruction AAM to handle the individual digit splitting and modulo 10 check.

Callable function, input card num string pointer in SI, length in CX. Output: ZF if valid.

Example test program output:

enter image description here

Alternate version, 29 bytes

33 DB XOR BX, BX ; running sum (BL) to 0
03 F1 ADD SI, CX ; start at end of string
4E DEC SI ; align for WORD
FD STD ; set LODSB direction to decrement
 DIGIT_LOOP:
AD LODSW ; load next two digits into AH:AL, dec SI by 2
25 0F0F AND AX, 0F0FH ; convert ASCII chars to binary value
02 DC ADD BL, AH ; add ones digit of even index to sum
49 DEC CX ; decrement counter for WORD
74 0A JZ DONE ; if input is odd length, will be 0 at the end
D0 E0 SHL AL, 1 ; double the value
D4 0A AAM ; split digits (ex: 18 --> AH = 1, AL = 8)
02 DC ADD BL, AH ; add tens digit to sum
02 D8 ADD BL, AL ; add ones digit to sum
E2 ED LOOP DIGIT_LOOP
 DONE:
93 XCHG AX, BX ; move sum to AL for mod 10 check
D4 0A AAM ; ZF = ( AL % 10 == 0 )
C3 RET ; return to caller

This version loads two digits at the same time, eliminating the flip/flop branch. The issue here is having to check for an odd number of digits on the input and discard the value in memory right before the beginning of the string once it reaches the last word. Obviously not shorter, but perhaps someone clever can golf it more!

answered Mar 26, 2019 at 16:03
\$\endgroup\$
8
  • 1
    \$\begingroup\$ I haven't spent very long staring at this, but... Why do you need to iterate through the string backwards? If you iterated forwards, it seems like you'd get the same result, but be able to shave off those initial 3 bytes (setting SI and the direction flag [which you can assume is always clear]). \$\endgroup\$ Commented Jan 13, 2021 at 1:40
  • \$\begingroup\$ @CodyGray IIRC I think it has to do with having an odd number of digits, and the LUHN algorithm expecting you to operate on the last digits first. Maybe I should take another look though... \$\endgroup\$ Commented Jan 13, 2021 at 1:57
  • \$\begingroup\$ Yeah, I realize it's an old answer. The Luhn algorithm was brought up in a chat room earlier, so I was considering it, dreaming of it in x86 asm. I decided to check Code Golf, and sure enough, it was already here. With a pretty solid-looking x86 asm answer. Thus, I immediately set out to nitpick. ;-) We were specifically discussing whether it is necessary to iterate backwards. The reference implementation in pseudocode on Wikipedia doesn't appear to go backwards. Didn't even notice about the missing ret though. Would take some effort to clean up, probably more than it's worth. \$\endgroup\$ Commented Jan 13, 2021 at 4:58
  • \$\begingroup\$ @CodyGray my quick reading of the Wiki implementation, it seems to first determine if the number of digits is odd or even (parity := nDigits modulus 2) and then checks every time through the loop if that index corresponds to a relative odd or even index (if i modulus 2 = parity). Maybe that could be golfed, though I tend to think the 3 bytes to start from the end isn't too bad. Also, it looks like I'm going to have to take a +4 bytes penalty to get this submission up to spec with the rules. I guess I didn't quite understand them when I wrote this one. :) \$\endgroup\$ Commented Jan 13, 2021 at 14:16
  • \$\begingroup\$ @CodyGray I played with the idea of iterating forwards using the Wiki-inspired if ( originalCx & 1 == currentCx & 1 ) then..., but got a bit fiddly in ASM and was longer for me. Then I tried loading two bytes at a time (while still going backwards) and got it down to +2 bytes longer than the original. Posted it anyway in case it inspires another idea. Would love to see the answer you "dreamed" up too! :) \$\endgroup\$ Commented Jan 13, 2021 at 18:36
4
\$\begingroup\$

Perl, (削除) 46 (削除ここまで) (削除) 42 (削除ここまで) 41 bytes

Includes +1 for -p

Give input on STDIN:

luhn.pl <<< 79927398713

luhn.pl:

#!/usr/bin/perl -p
s%.%$=-=-$&-$&*1.2*/\G(..)+$/%eg;$_=/0$/
answered Apr 21, 2016 at 20:23
\$\endgroup\$
2
  • \$\begingroup\$ Can you please explain how this works? You seem to be decrementing by the match and then also by the match times 1.2 but only in the correct positions. Why 1.2? Shouldn't it be $=-=-$&-$&*/\G(..)+$/? \$\endgroup\$ Commented Apr 22, 2016 at 21:33
  • 3
    \$\begingroup\$ @msh210: It encodes the effect of the multiplication by 2. 0..4 * 2 gives 0, 2, 4, 6, 8 but 5..9 gives 10,12,14,16,18 which sum to 1 3 5 7 9 which have the same last digits as 11 13 15 17 19 which are the same values as 0..9 * 2.2 if you truncate to integer. The first $& already contributes a factor 1, so a correction by 1.2 is still needed. $= can only hold integers and starts with a value that ends on 0 so takes care of the truncating. The negative values are needed since the /\G/ regex changes any $& still on the evaluation stack so they need to be changed \$\endgroup\$ Commented Apr 22, 2016 at 21:58
4
\$\begingroup\$

Powershell, 74 bytes

param($s)$s[$s.Length..0]|%{(1+$i++%2)*"$_"}|%{$r+=$_-9*($_-gt9)}
!($r%10)

Explanation

  1. for each char of an argument string, in reverse order
  2. get a digit of double value of a digit
  3. a double value of a digit can not be greater then 18. Therefore, we accumulate a value minus 9 if value> 9
  4. return true if the remainder of the division by 10 is 0

Test script

$f = {
param($s)$s[$s.Length..0]|%{(1+$i++%2)*"$_"}|%{$r+=$_-9*($_-gt9)}
!($r%10)
}
@(
 ,("49927398716" , $True)
 ,("49927398717" , $False)
 ,("1234567812345670" , $True)
 ,("1234567812345678" , $False)
 ,("79927398710" , $False)
 ,("79927398711" , $False)
 ,("79927398712" , $False)
 ,("79927398713" , $True)
 ,("79927398714" , $False)
 ,("79927398715" , $False)
 ,("79927398716" , $False)
 ,("79927398717" , $False)
 ,("79927398718" , $False)
 ,("79927398719" , $False)
 ,("374652346956782346957823694857692364857368475368" , $True)
 ,("374652346956782346957823694857692364857387456834" , $False)
 ,("8" , $False)
 ,("0" , $True)
) | % {
 $s, $expected = $_
 $result = &$f $s
 "$($result-eq$expected): $result : $s"
}

Output

True: True : 49927398716
True: False : 49927398717
True: True : 1234567812345670
True: False : 1234567812345678
True: False : 79927398710
True: False : 79927398711
True: False : 79927398712
True: True : 79927398713
True: False : 79927398714
True: False : 79927398715
True: False : 79927398716
True: False : 79927398717
True: False : 79927398718
True: False : 79927398719
True: True : 374652346956782346957823694857692364857368475368
True: False : 374652346956782346957823694857692364857387456834
True: False : 8
True: True : 0
answered Nov 12, 2018 at 12:42
\$\endgroup\$
4
\$\begingroup\$

Perl 6, 37 bytes

(*.flip.comb >>*>>[1,2]).comb.sum%%10

Try it online!

A Whatever lambda that takes a number and returns a boolean.

Explanation:

 *.flip # Reverse the number
 .comb # Split to list of digits
 >>*>> # Multiply each element by:
 [1,2] # The list 1,2,1,2,1,2...
( ).comb # Split the list to a list of digits
 .sum # Get the digit sum
 %%10 # Return if it is divisible by 10
answered Nov 13, 2018 at 8:47
\$\endgroup\$
4
\$\begingroup\$

05AB1E, (削除) 12 (削除ここまで) 10 bytes

RSāÈ>*SOTÖ

Try it online! or as a Test Suite

Explanation

R # reverse input
 S # split to list of digits
 ā # push range[1 ... len(input)]
 È # map isEven on each
 > # increment
 * # multiply doubling every other item
 SO # sum digits
 TÖ # mod 10 == 0
answered Feb 20, 2017 at 15:21
\$\endgroup\$
4
\$\begingroup\$

C (clang), (削除) 264 (削除ここまで) (削除) 212 (削除ここまで) (削除) 196 (削除ここまで) (削除) 175 (削除ここまで) 172 bytes

l,x;main(i){char*s;gets(s);int n[i=l=strlen(s)];for(;i--;)n[i]=s[i]-48;for(;(i+=2)<l;n[i]>9?n[i]=n[i]/10+n[i]%10:0)n[i]*=2;for(;l--;)x+=n[l];printf(x%10<1?"True":"False");}

Try it online!

Simply the basic way of programming the algorithm, but all whitespace is removed. All I could say is that there are a bunch of for-loops in this one.

Thanks to ceilingcat for golfing 52 bytes, another 16 bytes, and another 21 bytes, and another 3 bytes.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ 156 bytes \$\endgroup\$ Commented Jun 13, 2021 at 9:10
4
\$\begingroup\$

JavaScript (ES6), 61 bytes

Sum of digits of 2*n is 2*n if n in 0..4, 2*n-9 if n in 5..9. That said, all the sum can be computed in one step.

s=>!([...s].reduceRight((t,d)=>t-d-i++%2*(d>4?d-9:d),i=0)%10)
emanresu A
46.2k5 gold badges111 silver badges257 bronze badges
answered Apr 20, 2016 at 20:41
\$\endgroup\$
3
\$\begingroup\$

JavaScript 1.8: 106 characters

This is an original solution I came up with before I found this post:

function(n){return!(n.split('').reverse().reduce(function(p,c,i){return(+c&&((c*(1+i%2)%9)||9))+p},0)%10)}

Readable form:

function luhnCheck(ccNum) {
 return !( // True if the result is zero.
 ccNum.split('').
 reverse(). // Iterate over the string from rtl.
 reduce(function(prev, cur, idx) {
 return prev + // Sum the results of each character.
 (+cur && // If the current digit is 0, move on.
 ((cur * (1 + idx % 2) // Double cur at even indices.
 % 9) || 9)); // Sum the digits of the result.
 }, 0)
 % 10); // Is the sum evenly divisible by 10?
}
answered Jan 19, 2013 at 20:10
\$\endgroup\$
0
3
\$\begingroup\$

GolfScript, 31 bytes

.,2%0\@{48-..4>+@!:a*++a}/;10%!

Try it online!

Golfscript pushes the input as a list of characters to the stack at the start of the program.

. Duplicate the top of the stack (input)
 ,2% Compute the modulo 2 of the length of the input,
 pushed 1/0 on the stack if the number if odd/even
 0 Push an accumulater var on the stack, set to 0
 
 \ Flip the even/odd and accumulator order on the stack
 @ Bring the 3rd entry forward, input to the top
 { } Code block...
 / Apply the block of code to the elements of the list at
 the top of the stack. Each time through the loop, the
 next element from the list if pushed to the top of the
 stack
 48- Subtract "0" from the top of stack (which is a codepoint
 for the current digit/character)
 .. Duplicate the top of stack (the current digit) twice
 4>+ If the current digit is >4 add one to the digit
 @ Pull the 3rd entry, the even/odd indicator, to the top
 !:a Flip the value and save in variable "a"
 * Multiple the top two stack entries, will be "0" or the
 value of current digit, with or without "1" more for the
 "+10" rule
 ++ Add the current digit, to the possible "double" and
 "even/odd" bump
 a Push the flipped even/odd indicated to the top
 ; Drop the stop of stack, the even/odd indicator 
 10% Find the modulo 10 of the accumulated digits
 ! Invert the result

The only thing on the stack is the answer, which is printed by default

answered Dec 20, 2020 at 0:59
\$\endgroup\$
3
\$\begingroup\$

K (ngn/k), 26 bytes

{~10!+//10\(1+2!!#x)*.'|x}

Try it online!

Another k variant, with a major debt to @streetster's answer.

ngn
15.6k2 gold badges45 silver badges90 bronze badges
answered Dec 8, 2020 at 20:16
\$\endgroup\$
2
  • 2
    \$\begingroup\$ 1+2!!#x -> 1+=\~x \$\endgroup\$ Commented Jan 10, 2021 at 21:28
  • \$\begingroup\$ i rolled back the above change because ` =\ ` works differently now \$\endgroup\$ Commented Nov 7, 2022 at 7:42
3
\$\begingroup\$

Julia 1.0, (削除) 51 (削除ここまで) 50 bytes

~=digits;!n=(k=~n;k[2:2:end]*=2;sum(sum,.~k)%10<1)

Try it online!

answered Nov 7, 2022 at 9:56
\$\endgroup\$
3
\$\begingroup\$

Factor + validators, 5 bytes

luhn?

Try it online!

answered Nov 7, 2022 at 13:26
\$\endgroup\$
3
\$\begingroup\$

bash, (削除) 109 (削除ここまで) (削除) 106 (削除ここまで) (削除) 90 (削除ここまで) 71 bytes

Output of 0 means True. Anything else means False.

for x in `rev<<<1ドル|fold -1`;{((s+=++i%2?x:(x*22/10)%10));}
echo $[s%10]

Try it online! (削除) 90bytes (削除ここまで) (削除) Alt-90bytes-backwards (削除ここまで)

answered Mar 23, 2019 at 4:05
\$\endgroup\$
0
3
\$\begingroup\$

Rust, (削除) 83 (削除ここまで) 81 bytes

|d|(0..).zip(d).fold(0,|a,(b,&c)|a+[(c-48)*2%10+(c-48)/5,c-48][b+d.len()&1])%10<1

Attempt This Online!

answered Aug 6, 2024 at 15:04
\$\endgroup\$
1
  • \$\begingroup\$ Suggest (c-3)*2%10 instead of (c-48)*2%10 \$\endgroup\$ Commented Jun 22 at 19:36
2
\$\begingroup\$

Scala: 132

def q(x:Int)=x%10+x/10
def c(i:String)={val s=i.reverse
(s(0)-48)==10-(s.tail.sliding(2,2).map(n=>(q((n(0)-48)*2)+n(1)-48)).sum%10)}

invocation:

c("79927398713")
  • reverse ("79927398713") = 31789372997
  • s(0), s.tail: (3)(1789372997)
  • sliding (2,2) = (17 89 37 29 97)
  • map (q((n(0)-48*2 + n(1)-48)) => q(('1'-'0')*2)+ '7'-'0')=1*2+7
answered Apr 8, 2012 at 4:03
\$\endgroup\$
1
2 3

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.