Luhn test of credit card numbers

From Rosetta Code
Revision as of 14:06, 10 March 2010 by Toucan (talk | contribs) (luhn in ocaml)
Task
Luhn test of credit card numbers
You are encouraged to solve this task according to the task description, using any language you may know.

The Luhn test is used by some credit card companies to distinguish valid credit card numbers from what could be a random selection of digits.

Those companies using credit card numbers that can be validated by the Luhn test have numbers that pass the following test:

  1. Reverse the order of the digits in the number.
  2. Take the first, third, ... and every other odd digit in the reversed digits and sum them to form the partial sum s1
  3. Taking the second, fourth ... and every other even digit in the reversed digits:
  1. Multiply each digit by two and sum the digits if the answer is greater than nine to form partial sums for the even digits
  2. Sum the partial sums of the even digits to form s2
  1. If s1 + s2 ends in zero then the original number is in the form of a valid credit card number as verified by the Luhn test.

For example, if the trail number is 49927398716:

Reverse the digits:
  61789372994
Sum the odd digits:
  6 + 7 + 9 + 7 + 9 + 4 = 42 = s1
The even digits:
    1,  8,  3,  2,  9
  Two times each even digit:
    2, 16,  6,  4, 18
  Sum the digits of each multiplication:
    2,  7,  6,  4,  9
  Sum the last:
    2 + 7 + 6 + 4 + 9 = 28 = s2

s1 + s2 = 70 which ends in zero which means that 49927398716 passes the Luhn test

The task is to write a function/method/procedure/subroutine that will validate a number with the Luhn test, and use it to validate the following numbers:

49927398716
49927398717
1234567812345678
1234567812345670


C.f. SEDOL

Clojure

<lang clojure>(defn- digits [n]

 (map #(Character/digit % 10) (str n)))

(defn luhn? [n]

 (let [sum (reduce + (map
                      (fn [d idx]
                        (if (even? idx)
                          (reduce + (digits (* d 2)))
                          d))
                      (reverse (digits n))
                      (iterate inc 1)))]
   (zero? (mod sum 10))))

(doseq [n [49927398716 49927398717 1234567812345678 1234567812345670]]

 (println (luhn? n)))</lang>

Forth

<lang forth>: luhn ( addr len -- ? )

 0 >r over +             ( R: sum )
 begin  1- 2dup <=
 while                   \ odd
        dup c@ [char] 0 -
        r> + >r
        1- 2dup <=
 while                   \ even
        dup c@ [char] 0 -
        2* 10 /mod +     \ even digits doubled, split, and summed
        r> + >r
 repeat then
 2drop  r> 10 mod 0= ;

s" 49927398716" luhn . \ -1 s" 49927398717" luhn . \ 0 s" 1234567812345678" luhn . \ 0 s" 1234567812345670" luhn . \ -1</lang>

Fortran

<lang fortran>program luhn

 implicit none
 integer              :: nargs
 character(len=20)    :: arg
 integer              :: alen, i, dr
 integer, allocatable :: number(:)
 integer, parameter   :: drmap(0:9) = [0, 2, 4, 6, 8, 1, 3, 5, 7, 9]
 ! Get number
 nargs = command_argument_count()
 if (nargs /= 1) then
    stop
 end if
 call get_command_argument(1, arg, alen)
 allocate(number(alen))
 do i=1, alen
    number(alen-i+1) = iachar(arg(i:i)) - iachar('0')
 end do
 ! Calculate number
 dr = 0
 do i=1, alen
    dr = dr + merge(drmap(number(i)), number(i), mod(i,2) == 0)
 end do
 if (mod(dr,10) == 0) then
    write(*,'(a,i0)') arg(1:alen)//' is valid'
 else
    write(*,'(a,i0)') arg(1:alen)//' is not valid'
 end if

end program luhn

! Results: ! 49927398716 is valid ! 49927398717 is not valid ! 1234567812345678 is not valid ! 1234567812345670 is valid</lang>

J

We can treat the odd digits the same as even digits, except that they are not doubled. Also, we do not need the intermediate sums.

<lang J>luhn=: 0 = 10 (| +/@,) 10 #.inv 1 2 *&|: _2 "."0\ |.</lang>

Example use: <lang J> luhn&> '49927398716';'49927398717';'1234567812345678';'1234567812345670' 1 0 0 1</lang>

<lang logo>to small? :list

 output or [empty? :list] [empty? bf :list]

end to every.other :list

 if small? :list [output :list]
 output fput first :list every.other bf bf :list

end to wordtolist :word

 output map.se [?] :word

end

to double.digit :digit

 output item :digit {0 2 4 6 8 1 3 5 7 9}@0
 ; output ifelse :digit < 5 [2*:digit] [1 + modulo 2*:digit 10]

end

to luhn :credit

 localmake "digits reverse filter [number? ?] wordtolist :credit
 localmake "s1 apply "sum every.other :digits
 localmake "s2 apply "sum map "double.digit every.other bf :digits
 output equal? 0 last sum :s1 :s2

end

show luhn "49927398716  ; true show luhn "49927398717  ; false show luhn "1234-5678-1234-5678  ; false show luhn "1234-5678-1234-5670  ; true</lang>

OCaml

<lang ocaml>let luhn s = let rec g r c = function | 0 -> r | i -> let d = c*((int_of_char s.[i-1]) - 48) in g (r + (d/10) + (d mod 10)) (3-c) (i-1) in (g 0 1 (String.length s)) mod 10 = 0;; </lang>

Sample output <lang ocaml># List.map luhn [ "49927398716"; "49927398717"; "1234567812345678"; "1234567812345670" ];; - : bool list = [true; false; false; true] </lang>

Oz

<lang oz>declare

 fun {Luhn N}
    {Sum {List.mapInd {Reverse {Digits N}}
          fun {$ Idx Dig}
             if {IsEven Idx} then {Sum {Digits 2*Dig}}
             else Dig
             end
          end}}
    mod 10 == 0
 end
 fun {Digits N}
    {Map {Int.toString N} fun {$ D} D - &0 end}
 end
 fun {Sum Xs}
    {FoldL Xs Number.'+' 0}
 end

in

 {Show
  {Map
   [49927398716 49927398717 1234567812345678 1234567812345670]
   Luhn}}</lang>

Python

The divmod in the function below conveniently splits a number into its two digits ready for summing: <lang python>>>> def luhn(n): r = [int(ch) for ch in str(n)][::-1] return (sum(r[0::2]) + sum(sum(divmod(d*2,10)) for d in r[1::2])) % 10 == 0

>>> for n in (49927398716, 49927398717, 1234567812345678, 1234567812345670): print(n, luhn(n))


49927398716 True 49927398717 False 1234567812345678 False 1234567812345670 True</lang>

Ruby

<lang ruby>def luhn(code)

 s1 = s2 = 0
 code.to_s.reverse.chars.each_slice(2) do |odd, even| 
   s1 += odd.to_i
   double = even.to_i * 2
   double = 1 + double % 10 if double >= 10
   s2 += double
 end
 (s1 + s2) % 10 == 0

end

[49927398716, 49927398717, 1234567812345678, 1234567812345670].each do |n|

 p [n, luhn(n)]

end</lang>

outputs

[49927398716, true]
[49927398717, false]
[1234567812345678, false]
[1234567812345670, true]

Tcl

Based on an algorithmic encoding for the test on Wikipedia. <lang tcl>package require Tcl 8.5 proc luhn digitString {

   if {[regexp {[^0-9]} $digitString]} {error "not a number"}
   set sum 0
   set flip 1
   foreach ch [lreverse [split $digitString {}]] {

incr sum [lindex { {0 1 2 3 4 5 6 7 8 9} {0 2 4 6 8 1 3 5 7 9} } [expr {[incr flip] & 1}] $ch]

   }
   return [expr {($sum % 10) == 0}]

}</lang> Driver: <lang tcl>foreach testNumber {

   49927398716
   49927398717
   1234567812345678
   1234567812345670

} {

   puts [format "%s is %s" $testNumber \

[lindex {"NOT valid" "valid"} [luhn $testNumber]]] }</lang> Output:

49927398716 is valid
49927398717 is NOT valid
1234567812345678 is NOT valid
1234567812345670 is valid

Ursala

<lang Ursala>#import std

  1. import nat

luhn = %nP; %np*hxiNCNCS; not remainder\10+ //sum:-0@DrlrHK32 ~&iK27K28TK25 iota10</lang> usage: <lang>#cast %bL

test = luhn* <49927398716,49927398717,1234567812345678,1234567812345670></lang> output:

<true,false,false,true>