Checking Credit Cards (#122)

This quiz reminded me of my days in credit card processing. The
weighted checksum for routing numbers is more interesting, but the
sort-of-two-pass aspect of the Luhn algorithm is a great stumbling
block. My solution follows. You’ll notice I liked your ARGV.join
trick for the input.

class Array
def cycle!
push(shift)
end
end

class CCNum < String
PATTERNS = {
‘AMEX’ => { :start => [‘34’, ‘37’], :length => 15 },
‘Discover’ => { :start => [‘6011’, ‘65’], :length => 16 },
‘MasterCard’ => { :start => (51…55).to_a.collect { |n|
n.to_s }, :length => 16 },
‘Visa’ => { :start => ‘4’, :length => [13, 16] },
}.freeze

def initialize(*args)
super
gsub!(/\D/, ‘’)
@factors = [1,2]
@factors.cycle! if (length % 2) == 1
end

def type
return @type if @type
PATTERNS.each do |name, pat|
@type = name if [pat[:start]].flatten.any? { |s| match /
^#{s}/ } and [pat[:length]].flatten.any? { |l| length == l }
end
@type ||= ‘Unknown’
end

def luhn_sum
@luhn_sum ||= split(’’).inject(0) do |sum, digit|
@factors.cycle!
sum += (digit.to_i * @factors.first).to_s.split(’’).inject(0) { |
s,d| s += d.to_i }
end
end

def luhn_valid?
(luhn_sum % 10) == 0
end
end

card_num = CCNum.new(ARGV.join)
puts “#{card_num} is a(n) #{card_num.luhn_valid? ? ‘V’ : ‘Inv’ }alid
#{card_num.type}”

Here is my solution, including unit tests. I’m pretty pleased with my
luhn check.

require ‘enumerator’

class CardType < Struct.new(:name, :pattern, :lengths)
def match(cc)
(cc =~ pattern) and lengths.include?(cc.length)
end

def to_s
name
end
end

class CardValidator
@types = [
CardType.new(‘AMEX’, /^(34|37)/, [15]),
CardType.new(‘Discover’, /^6011/, [16]),
CardType.new(‘MasterCard’, /^5[1-5]/, [16]),
CardType.new(‘Visa’, /^4/, [13,16])
]

def self.card_type(cc)
@types.find {|type| type.match(cc) }
end

def self.luhn_check(cc)
# I like functional-style code (though this may be a bit over the
top)
(cc.split(’’).reverse.enum_for(:each_slice, 2).inject(’’) do |s, (a,
b)|
s << a + (b.to_i * 2).to_s
end.split(’’).inject(0) {|sum, n| sum + n.to_i}) % 10 == 0
end
end

require ‘test/unit’

class CardValidatorTest < Test::Unit::TestCase
def test_card_type
assert_equal(‘AMEX’,
CardValidator.card_type(‘341122567979797’).name)
assert_equal(‘AMEX’,
CardValidator.card_type(‘371122567979797’).name)
assert_equal(‘Discover’,
CardValidator.card_type(‘6011123456781122’).name)
assert_equal(‘MasterCard’,
CardValidator.card_type(‘5115666677779999’).name)
assert_equal(‘MasterCard’,
CardValidator.card_type(‘5315666677779999’).name)
assert_equal(‘Visa’,
CardValidator.card_type(‘4408041234567893’).name)
assert_equal(‘Visa’,
CardValidator.card_type(‘4417123456789112’).name)
assert_equal(‘Visa’, CardValidator.card_type(‘4417123456789’).name)
assert_nil(CardValidator.card_type(‘3411225679797973’))
assert_nil(CardValidator.card_type(‘601112345678112’))
assert_nil(CardValidator.card_type(‘51156666777799’))
assert_nil(CardValidator.card_type(‘5615666677779989’))
assert_nil(CardValidator.card_type(‘1111222233334444’))
assert_nil(CardValidator.card_type(‘44171234567898’))
end

def test_luhn_check
assert(CardValidator.luhn_check(‘1111222233334444’))
assert(CardValidator.luhn_check(‘4408041234567893’))
assert(!CardValidator.luhn_check(‘4417123456789112’))
assert(!CardValidator.luhn_check(‘6011484800032882’))
end
end

if $0 == FILE
abort(“Usage: #$0 or -t to run unit tests”) if
ARGV.length < 1
if not ARGV.delete(’-t’)
Test::Unit.run = true

cc = ARGV.join.gsub(/\s*/, '')

type = CardValidator.card_type(cc)
puts "Card type is: #{type ? type : 'Unknown'}"
puts "The card is #{CardValidator.luhn_check(cc) ? 'Valid' : 

‘Invalid’}"
end
end

Hi all,

I took the library writing approach and since I have recently been using
Austin’s excellent mime-types library, I took a similar approach with
CreditCard Types. That is, a global registration of types that are
described in a here document.

If there is interest I’ll polish it up a bit and release it as a gem.

Comments are welcome.

enjoy,

-jeremy

This is my first rubyquiz. Here is my solution.

class CreditCard
class CardType < Struct.new(:name, :regex, :accepted_lengths)
def valid_length?(length)
if accepted_lengths.is_a?(Array)
return accepted_lengths.include?(length)
else
return accepted_lengths == length
end
end
end

CARD_TYPES = [CardType.new(‘AMEX’, /^3[47]/, 15),
CardType.new(‘Discover’, /^6011/, 16),
CardType.new(‘MasterCard’, /^5[1-5]/, 16),
CardType.new(‘Visa’, /^4/, [13, 16]),
CardType.new(‘Unknown’, /.*/, 0)]

def initialize(number)
@number = number
@card_type = CARD_TYPES.find {|t| @number =~ t.regex }
end

def card_type
@card_type.name
end

def valid?
return false unless @card_type.valid_length?(@number.length)
numbers = @number.split(//).collect {|x| x.to_i}
i = numbers.length - 2
while i >= 0
numbers[i] *= 2
i -= 2
end
numbers = numbers.to_s.split(//)
sum = 0; numbers.each {|x| sum += x.to_i}
sum % 10 == 0
end
end

abort “Usage: #{$0} card_number […]” if ARGV.empty?
ARGV.each do |card_number|
c = CreditCard.new(card_number)
out = "#{card_number}: "
out += (c.valid? ? "Valid " : "Invalid ")
out += “#{c.card_type}”
puts out
end

Here’s what I came up with. I hope it’s as short and to the point as I
think it is.

Chris

#!/usr/local/bin/ruby

class CreditCard

TYPES = {
:visa => {:length => [13, 16], :start => [4]},
:discover => {:length => [16], :start => [6011]},
:mastercard => {:length => [16], :start => 51…55},
:amex => {:length => [15], :start => [34, 37]}
}

def initialize(number)
@number = number.gsub(/\D/,’’)
end

def valid?
adjusted_numbers = ‘’
@number.reverse.split(’’).each_with_index do |x, i|
adjusted_numbers << (i % 2 == 0 ? x : (x.to_i * 2).to_s)
end
adjusted_numbers.split(’’).inject(0) {|sum, x| sum += x.to_i} % 10
== 0
end

def card_type
TYPES.each do |type, criteria|
if criteria[:start].any? {|n|
@number.match(Regexp.compile(’^’+n.to_s))}
if criteria[:length].include? @number.length
return type
end
end
end
:unknown
end

end

if FILE == $0
test_card = CreditCard.new(ARGV.join(’’))
puts “Card type: #{test_card.card_type}”
print test_card.valid? ? “Passes” : “Fails”
puts " the Luhn algorithm."
end

Thanks for another simple but fun quiz! James, thanks for your
critiques on last week’s quiz; they were educational.

I did two versions of the Luhn algorithm. The first one seems more
readable to me (though I’m a Ruby nuby), but the second was fun to
put together.

Comments are welcome.

-Mark

Ruby Q. #122: Credit card validation

require ‘enumerator’

class Array
def sum(initial=0)
inject(initial) { |total, elem| total + elem }
end

Compute the pairwise product of two arrays.

That is: result[i] = self[i] * other[i] for i in 0…self.length

def pairwise_product(other)
result = []
each_index {|i| result << self[i]*other[i] }
return result
end
end

class Integer
def digits
self.to_s.split(’’).map { |digit| digit.to_i }
end
end

class CreditCard
@@types = [[“AMEX”, /^3[47]\d{13}$/],
[“Discover”, /^6011\d{12}$/],
[“MasterCard”, /^5[1-5]\d{14}$/],
[“Visa”, /^4\d{12}(\d{3})?$/],
[“Unknown”, //]]
attr_reader :type

def initialize(str)
num = str.delete(" ")

 # Disallow card "numbers" with non-digits
 if num =~ /\D/
   @type = "Unknown"
   @valid = false
   return
 end

 # See which of the patterns match the string
 @type = @@types.find {|name, regexp| num =~ regexp }[0]

 # See if the card number is valid according to the Luhn algorithm
 @valid = num.reverse.split('').enum_slice(2).inject(0) do
   |sum, (odd, even)|
   sum + odd.to_i + (even.to_i*2).digits.sum
 end % 10 == 0

=begin
#
# This works, too. But it seems awfully long and complicated.
#
# The idea is to combine the digits of the credit card number with
# a sequence of 1’s and 2’s so that every other digit gets doubled.
# Then sum up the digits of each product.
#
# BTW, the “[1,2]*num.length” construct builds an array that’s
twice
# as long as necessary. The entire array only needs num.length
# elements, but having more is OK. This was the easy way of making
# sure it was big enough.
#
@valid = num.reverse.to_i.digits.pairwise_product([1,2]
*num.length).
map{|x| x.digits.sum}.sum % 10 == 0
=end
end

def valid?
@valid
end
end

if FILE == $0
cc = CreditCard.new(ARGV.join)
print cc.valid? ? “Valid” : “Invalid”, " #{cc.type}\n"
end

This is my first quiz submission. The only testing I did was running
the
cards I had in my wallet. It seems to work. I’m sure there’s a much
better
way to implement this, but here it is:

#card_check.rb

def check_type(number)
#Returns a string representing the type of card if the length and
leading

digits are valid. Otherwise returns “Unknown”.

valid = {
/^(34|37)/ => [15,“AMEX”],
/^6011/ => [16,“Discover”],
/^(51|52|53|54|55)/ => [16,“MasterCard”],
/^4/ => [13,16,“Visa”]
}
number.gsub!(/ /,"")
valid.each_key do |i|
if number =~ i
return valid[i][-1] if valid[i].include? number.length
end
end
return “Unknown”
end

def luhn(number)

Returns “valid” if the number passes the Luhn algorihm criteria.

Returns

“invalid” if the algorithm fails.

number = number.gsub(/ /,"").split(//).reverse
new_number = “”
number.each_index do |i|
new_number << (number[i].to_i*2).to_s if (i+1) % 2 == 0
new_number << number[i] if (i+1) % 2 == 1
end
new_number = new_number.split(//)
sum = 0
new_number.each_index { |i| sum += new_number[i].to_i }
return “valid” if sum % 10 == 0 unless number.length == 0
return “invalid”
end

def validate_card(number)
puts “Type: #{check_type(number)}”
puts “Status: #{luhn(number)}”
end

validate_card(ARGV.join("")) if FILE == $0

Here is my solution a bit schoolboyish :frowning:

BEGIN : quiz122.rb

#!/usr/bin/ruby

def is_luhn? cardno

digits = cardno.split(//).reverse!

sum = ‘’
digits.each_index do |i|
if i%2 == 0
sum << digits[i]
else
sum << (2 * digits[i].to_i).to_s
end

end
s1 = 0
sum.split(//).inject(s1) { |s1, v| s1 += v.to_i }
if s1 % 10 == 0
return “Valid”
else
return “InValid”
end

end

if FILE == $0:

abort(“Usage: ruby quiz122.rb #CARDNO”) if ARGV.length != 1

cardno = ARGV[0].gsub(/[^0-9]/, ‘’)

is_luhn? cardno

print "card number "#{cardno}" is "
case cardno.length
when 13
if cardno.match(/^4/)
print "Visa : "
else
print "Unknown : "
end
puts is_luhn?(cardno)

when 14
if
cardno.match(/(^30[0-2][0-9])|(^30[4-5][0-9])|(^36)|(^38(1[5-9]|[2-9]))/)
print "Diners : "
else
print "Unknown : "
end

when 15
if cardno.match(/^3(4|7)/)
print "Amex : "
else
print "Unknown : "
end

when 16
if cardno.match(/^6011/)
print "Discover : "
elsif cardno.match(/^4/)
print "Visa : "
elsif cardno.match(/^5[1-5]/)
print "MasterCard : "
elsif cardno.match(/^35(2[8-9]|[3-8][0-9])/)
print "JCB : "
else
print "Unknown : "
end

else
print "Unknown : "

end

puts is_luhn?(cardno) + " CC "

end

On 4/27/07, Ruby Q. [email protected] wrote:

    | MasterCard | 51-55       | 16            |
    2. Sum all doubled and untouched digits in the number

Let’s try one more, 4417 1234 5678 9112:
algorithm.


अभिजीत

[ written in http://www.paahijen.com/scratchpad ]

[ http://www.paahijen.com ]

On Apr 27, 6:59 pm, Ruby Q. [email protected] wrote:

Suggestion: A [QUIZ] in the subject of emails about the problem helps everyone
on Ruby T. follow the discussion. Please reply to the original quiz message,
if you can.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
=-=-=-=-=

    | Card Type  | Begins With | Number Length |

All of these card types also generate numbers such that they can be validated by
Step 1: 8 4 0 8 0 4 2 2 6 4 10 6 14 8 18 3

That card is not valid.

This week’s Ruby Q. is to write a program that accepts a credit card number as
a command-line argument. The program should print the card’s type (or Unknown)
as well a Valid/Invalid indication of whether or not the card passes the Luhn
algorithm.

Here is my solution.
#!/usr/bin/ruby

credit_card_number = ARGV.join

case
when (credit_card_number=~/^(34|37)\d{13}$/): print 'AMEX ’
when (credit_card_number=~/^6011\d{12}$/): print 'Discover ’
when (credit_card_number=~/^5[1-5]\d{14}$/): print 'MasterCard ’
when (credit_card_number=~/^4(\d{12}|\d{15})$/): print 'Visa ’
else print 'Unknown ’
end

i = 0
luhl_number = ‘’
credit_card_number.reverse.each_byte {|char|
if (i%2==1) then
char = (char.chr.to_i * 2).to_s
else
char = char.chr
end
luhl_number = char + luhl_number
i += 1
}

sum_total = 0

luhl_number.each_byte {|char|
sum_total += char.chr.to_i
}

if (sum_total%10==0) then
print “Valid\n”
else
print “Invalid\n”
end

anansi wrote:

I’ll wait with my solution for a further hour :wink: but a comment to your
solution:
try out: ruby ccc.rb 5508 0412 3456 7893
it should show: Diners Club US & Canada or Mastercard
but shows just Mastercaards
Fixed, and resubmitting. Thanks for the lookout. I didn’t include that
initially because I didn’t think there would be overlap. I didn’t even
bother to look myself and see if that assumption was true.

ccc.rb

Checking Credit Cards

class String
def begins_with?(str)
temp = self.slice(0…str.length)
temp == str
end
end

class Array
def collect_with_index
self.each_with_index do |x, i|
self[i] = yield(x, i)
end
end
end

class CCNumber
#This data was taken from

TYPES = [
Hash[‘type’ => ‘American Express’, ‘key’ => [34,
37], ‘length’ => [15]],
Hash[‘type’ => ‘China Union Pay’, ‘key’ =>
(622126…622925).to_a, ‘length’ => [16]],
Hash[‘type’ => ‘Diners Club Carte Blanche’, ‘key’ =>
(300…305).to_a, ‘length’ => [14]],
Hash[‘type’ => ‘Diners Club International’, ‘key’ =>
[36], ‘length’ => [14]],
Hash[‘type’ => ‘Diners Club US & Canada’, ‘key’ =>
[55], ‘length’ => [16]],
Hash[‘type’ => ‘Discover’, ‘key’ =>
[6011, 65], ‘length’ => [16]],
Hash[‘type’ => ‘JCB’, ‘key’ =>
[35], ‘length’ => [16]],
Hash[‘type’ => ‘JCB’, ‘key’ =>
[1800, 2131], ‘length’ => [15]],
Hash[‘type’ => ‘Maestro’, ‘key’ =>
[5020, 5038, 6759], ‘length’ => [16]],
Hash[‘type’ => ‘MasterCard’, ‘key’ =>
(51…55).to_a, ‘length’ => [16]],
Hash[‘type’ => ‘Solo’, ‘key’ =>
[6334, 6767], ‘length’ => [16, 18, 19]],
Hash[‘type’ => ‘Switch’, ‘key’ =>
[4903, 4905, 4911, 4936, 564182, 633110, 6333, 6759],

‘length’ => [16, 18, 19]],
Hash[‘type’ => ‘Visa’, ‘key’ =>
[4], ‘length’ => [13, 16]],
Hash[‘type’ => ‘Visa Electron’, ‘key’ => [417500,
4917, 4913], ‘length’ => [16]]
]

#number should be an array of numbers as strings e.g. [“1”, “2”, “3”]
def initialize(array)
@number = array.collect{|num| num.to_i}
end

def type
names = Array.new
TYPES.each do |company|
company[‘key’].each do |key|
if company[‘length’].include?(@number.length) and
@number.join.begins_with?(key.to_s)
names << company[‘type’]
end
end
end
names.empty? ? [“Unknown”] : names
end

def valid?
temp = @number.reverse.collect_with_index{|num, index| index%2 == 0
? num*2 : num}
sum = temp.collect{|num|num > 9 ? [1, num%10] :
num}.flatten.inject{|s, n| s+n}
sum%10 == 0
end

def process
puts “The card type is #{self.type.join(’ or ')}”
puts “The card number is #{self.valid? ? ‘valid’ : ‘invalid’}”
end
end

if $0 == FILE
abort “You must enter a number!” if ARGV.empty?
CCNumber.new(ARGV.join.strip.split(//)).process
end

This week’s Ruby Q. is to write a program that accepts a credit card
number as a command-line argument. The program should print the card’s
type (or Unknown) as well a Valid/Invalid indication of whether or not the
card passes the Luhn algorithm.

This is just a class, with a bit of starter code at the bottom to get
going.
It will take any number of CC numbers on the command line, but they must
be
quoted. The luhn method seems tacky, but seems to work.

#################################
class CCNumberError < StandardError
end

class CardValidate
attr_reader :cc_number, :cc_type, :luhn_valid
def initialize(cc_number)
@cc_number = cc_number
normalise_cc_number
card_type
luhn
end

private
def normalise_cc_number
@cc_number = @cc_number.gsub(" ", “”)
if @cc_number =~ /\D/
raise CCNumberError, “Credit Card numbers may not contain non
digit
characters except spaces”, caller
end
@cc_length = @cc_number.length
end

def card_type
if @cc_length == 15
if @cc_number[0…1].to_i == 34 or @cc_number[0…1].to_i == 37
@cc_type = “American Express”
end
elsif @cc_length == 16 and @cc_number[0…3] == 6011
@cc_type = “Discover”
elsif @cc_length == 16 and (51…55) === @cc_number[0…1].to_i
@cc_type = “MasterCard”
elsif @cc_length == 16 or @cc_length == 13
if @cc_number.index(“4”) == 0
@cc_type = “Visa”
end
else
@cc_type = “Unknown”
end
end

def luhn
ccn = @cc_number.reverse.scan(/\d/)
ccn_luhn_sum = 0
i = 0
ccn.length.times do
if i % 2 == 0
ccn_luhn_sum += ccn[i].to_i
else
if ccn[i].to_i * 2 >= 10
n = (ccn[i].to_i * 2).to_s
ccn_luhn_sum += n[0].chr.to_i
ccn_luhn_sum += n[1].chr.to_i
else
ccn_luhn_sum += ccn[i].to_i * 2
end
end
i += 1
end
ccn_luhn_sum % 10 == 0 ? @luhn_valid = true : @luhn_valid = false
end
end

ARGV.each do |n|
card = CardValidate.new(n)
puts “Card number: #{card.cc_number}”
puts “Card type: #{card.cc_type}”
puts “Luhn valid: #{card.luhn_valid}”
puts
end
#######################

-d

My solution is below.

file: credit_card.rb

author: Drew O.

class CreditCard
def initialize num
@number = num
end

check specified conditions to determine the type of card

def type
length = @number.size
if length == 15 && @number =~ /^(34|37)/
“AMEX”
elsif length == 16 && @number =~ /^6011/
“Discover”
elsif length == 16 && @number =~ /^5[1-5]/
“MasterCard”
elsif (length == 13 || length == 16) && @number =~ /^4/
“Visa”
else
“Unknown”
end
end

determine if card is valid based on Luhn algorithm

def valid?
digits = ‘’
# double every other number starting with the next to last
# and working backwards
@number.split(’’).reverse.each_with_index do |d,i|
digits += d if i%2 == 0
digits += (d.to_i*2).to_s if i%2 == 1
end

# sum the resulting digits, mod with ten, check against 0
digits.split('').inject(0){|sum,d| sum+d.to_i}%10 == 0

end
end

if FILE == $0
card = CreditCard.new(ARGV.join.chomp)
puts “Card Type: #{card.type}”
if card.valid?
puts “Valid Card”
else
puts “Invalid Card”
end
end

My second ever Ruby Q…
TIA for any suggestions for making it more Ruby-like.
/Bob

#!/usr/bin/env ruby -w

class CreditCard
attr_reader :number, :type, :validity
def initialize(cardnumber)
@number = cardnumber.gsub(/\s/,’’)
@type = case @number
when /^3[47]\d{13}$/ then “AMEX”
when /^6011\d{12}$/ then “Discover”
when /^5[12345]\d{14}$/ then “Mastercard”
when /^4\d{12}$/ then “VISA”
when /^4\d{15}$/ then “VISA”
else “Unknown”
end
sum = 0
digits = @number.to_s.split(’’).reverse.map {|i| i.to_i}
digits.each_index {|i| i%2==0 ? sum+=add_digits(digits[i]) : sum
+=add_digits(digits[i]*2)}
@validity = sum%10 == 0 ? “Valid” : “Invalid”
end
def add_digits(n)
return n.to_s.split(’’).inject(0) {|sum, i| sum += i.to_i}
end
end #CreditCard

c = CreditCard.new(ARGV.join)
puts “#{c.number}: #{c.type}\t#{c.validity}”

Ruby Q. [email protected] writes:

This week’s Ruby Q. is to write a program that accepts a credit card number as
a command-line argument. The program should print the card’s type (or Unknown)
as well a Valid/Invalid indication of whether or not the card passes the Luhn
algorithm.

#!ruby

def cardtype(n)
case n.delete(“^0-9”)
when /\A3[37]\d{13}\z/: “AMEX”
when /\A6011\d{12}\z/: “Discover”
when /\A5[1-4]\d{14}\z/: “Master Card”
when /\A4\d{12}\d{3}?\z/: “Visa”
else “Unknown”
end
end

def luhn?(n)
f = 2
(n.delete(“^0-9”).reverse.split(//).map{|d|d.to_i}.
inject(0) { |a,e| f=3-f; a + (ef > 9 ? ef-9 : e*f) } % 10).zero?
end

puts cardtype(ARGV.join)
puts luhn?(ARGV.join) ? “valid” : “invalid”

END

Am 02.05.2007 um 17:13 schrieb Christian N.:

#!ruby

END


Christian N. [email protected] http://
chneukirchen.org

Wow, very nice solution! Impressive how short it is…

Here is my solution:

#!/usr/bin/env ruby -W

Assign a regular expression that checks first characters and length

PROVIDERINFO = {
“AMEX” => /^(34|37)\d{13}$/,
“Discover” => /^6011\d{12}$/,
“MasterCard” => /^5[1-5]\d{14}$/,
“Visa” => /^4(\d{12}|\d{15})$/,
}

class CreditCard
attr_reader :provider, :number

def initialize(number)
@number = []
# split credit card number and store in array
number.scan(/\d/){|c| @number.push c.to_i}

 # Check Provider Infos
 @provider = "Unknown"
 PROVIDERINFO.each_pair {|k, v| @provider = k if

@number.join.match(v) }
end

def luhn_passed?
sum = 0
@number.reverse.each_with_index do |num, i|
# double the nummer if necessary and subtract 9 if the result
# consists of 2 numbers (here same as summing up both numbers)
num = num * 2 - ((num > 4) ? 9 : 0) if i % 2 == 1
sum += num
end
sum % 10 == 0
end

def to_s
“Creditcard number #{@number}\n” +
" Provider: #{self.provider}\n" +
" Luhn Algorithm #{'not ’ unless self.luhn_passed?}passed"
end
end

puts CreditCard.new(ARGV.join)

Regards,

Dennis Frommknecht

Just one minor nit:

Christian N. [email protected] writes:

def luhn?(n)
f = 2
(n.delete(“^0-9”).reverse.split(//).map{|d|d.to_i}.
inject(0) { |a,e| f=3-f; a + (ef > 9 ? ef-9 : e*f) } % 10).zero?
end

You do know that (ef > 9 ? ef-9 : ef) is equivalent to ef%9,
right? So that makes this method:

def luhn?(n)
f = 2
(n.delete(“^0-9”).reverse.split(//).map{|d|d.to_i}.
inject(0) { |a,e| f=3-f; a + e*f%9 } %10).zero?
end

(I still like my short version better, but too each his own)

On May 2, 2007, at 3:18 PM, Daniel M. wrote:

You do know that (ef > 9 ? ef-9 : ef) is equivalent to ef%9,
right?

(0…9).map { |n| n * 2 % 9 }
=> [0, 2, 4, 6, 8, 1, 3, 5, 7, 0]

(0…9).map { |n| n * 2 > 9 ? n * 2 - 9 : n * 2 }
=> [0, 2, 4, 6, 8, 1, 3, 5, 7, 9]

James Edward G. II

Thank you James for another fun quiz.

My solution is a more verbose version of the metaprogramming-based
routing used in why’s excellent camping. But instead of regexen
matching urls to controller classes, here they are matching card
numbers to the card classes.

What I like about this approach is that you can add more card patterns
just by adding more classes with the appropriate regexen, and you can
override validation or other functionality as needed.

Regards,

Paul.

cardvalidator.rb

require ‘metaid’

module Card
@cards=[]

def Card.Base *u
c = @cards
Class.new {
meta_def(:patterns){u}
meta_def(:validity){|x|Card.validity(x)}
meta_def(:inherited){|x|c<<x}
}
end
def Card.validate cardnum
@cards.map { |k|
k.patterns.map { |x|
if cardnum =~ /^#{x}/?$/
return [k.name.upcase, k.validity(cardnum)].join( " “)
end
}
}
raise “Unexpected Card Number pattern: ‘#{cardnum}’”
end
def Card.sum_of_digits s
s.split(”").inject(0){|sum,x|sum + Integer(x)}
end
def Card.luhnSum s
temp = “”
r = 0…s.size
a = s.split("").reverse
r.each do |i|
if i%2==1
x = (Integer(a[i])*2).to_s
else
x = a[i]
end
temp << x.to_s
end
sum_of_digits temp
end
def Card.validity cardnum
if (Card.luhnSum(cardnum) % 10)==0
return “Valid”
else
return “Invalid”
end
end
end

patterns will be tested for match in order defined

class Visa < Card.Base /4[0-9]{12,15}/
end
class Amex < Card.Base /3[4,7][0-9]{13}/
end
class Mastercard < Card.Base /5[1-5][0-9]{13}/
end
class Discover < Card.Base /6011[0-9]{12}/
end

catch-all

class Unknown < Card.Base /.+/
end

p Card.validate(ARGV[0])

Daniel M. [email protected] writes:

You do know that (ef > 9 ? ef-9 : ef) is equivalent to ef%9,
right? So that makes this method:

I first thought that too, and it cost me 10min to find that 9%9 == 0.