# [SOLUTION] [QUIZ] Checking Credit Cards (#122)

Discussion in 'Ruby' started by Todd Benson, Apr 29, 2007.

1. ### Todd BensonGuest

Spartan solution (along with its obscure sister below)

# usage: ruby quiz122.rb <credit card number with no
dashes or spaces>
# example: ruby quiz122.rb 341275084937123

# some variables
user_input = \$*[0]
sum_is_valid = card_is_known = false
sum = 0

# the limited database
db = {
3.4*10**14..3.5*10**14-1 => "AMEX",
3.7*10**14..3.8*10**14-1 => "AMEX",
6.011*10**15..6.012*10**15-1 => "Discover",
5.1*10**15..5.6*10**15-1 => "MasterCard",
4*10**12..5*10**12-1 => "Visa",
4*10**15..5*10**15-1 => "Visa",
}

# check the database
number = user_input.to_i
type = 'unknown'
db.each { |key,value| type = value if key === number }
card_is_known ||= type != 'unknown'

# ugly way to code the Luhn algorithm
#
# 1. initialize sum
# 2. reverse the supplied string
# 3. turn the string into an array with one digit per
index
# 4. turn the individual digits into integers
# 5. sum everything up ...
# 5a. add the digit to the sum if the array index is
odd
# 5b. add the digits making up twice the value of the
digit if the array index is even
# 6. test 2 is valid if the sum is divisible by 10

(user_input.reverse.scan(/\d/).map! { |digit|
digit.to_i }).each_with_index { |digit,index| sum += (
index % 2 == 0 ? digit : digit.divmod(5)[1] * 2 +
digit.divmod(5)[0] ) }
puts sum
sum_is_valid ||= sum % 10 == 0

# print results
card_is_known = (card_is_known ? "is" : "is not")
sum_is_valid = (sum_is_valid ? "is" : "is not")
puts "The card #{card_is_known} known."
puts "The card type is #{type}."
puts "The card number sum #{sum_is_valid} valid."

--------------------------------------------------

Solution, obscure version

# preliminary data
sum=known=false;type='';t=0
db = {
3.4*10**14..3.5*10**14-1 => "AMEX",
3.7*10**14..3.8*10**14-1 => "AMEX",
6.011*10**15..6.012*10**15-1 => "Discover",
5.1*10**15..5.6*10**15-1 => "MasterCard",
4*10**12..5*10**12-1 => "Visa",
4*10**15..5*10**15-1 => "Visa",
}

# check stuff
db.each { |k,v| type=v if
k===\$*[0].to_i};known||=type!=''
(\$*[0].reverse.scan(/\d/).map!{|d|d.to_i}).each_with_index{|d,i|t+=(i%2==0
? d : d.divmod(5)[1]*2+d.divmod(5)[0])};sum||=t%10==0

# cryptically print results
puts "Known: #{known}\nType: #{type}\nValid sum: #{sum}"

__________________________________________________
Do You Yahoo!?
Tired of spam? Yahoo! Mail has the best spam protection around
http://mail.yahoo.com

Todd Benson, Apr 29, 2007

2. ### Rolando AbarcaGuest

Hi all,
Here's my solution...

(pastie: http://pastie.caboo.se/57591)

regards,
rolando.-

#!/usr/bin/env ruby

# RubyQuiz #122
# Solution by Rolando Abarca M.
# rabarca (at) scio.cl

# small hack to allow intervals as a cc length
class Fixnum
def include?(n)
self == n
end
end

module CChecker
# prefixes taken from wikipedia
# http://en.wikipedia.org/wiki/Credit_card_number
PREFIXES = [
# regexp, length, name, checking algorithm
# length can be a fixnum, array or range.
# the algorithm must be in the CChecker module
[/^(34|37)\d+\$/, 15, "AMEX", :luhn],
[/^30[0-5]\d+\$/, 14, "Diners Club Carte Blanche", :luhn],
[/^36\d+\$/, 14, "Diners Club International", :luhn],
[/^55\d+\$/, 16, "Diners Club US & Canada", :luhn],
[/^(6011|65)\d+\$/, 16, "Discover", :luhn],
[/^35\d+\$/, 16, "JCB", :luhn],
[/^(1800|2131)\d+\$/, 15, "JCB", :luhn],
[/^(5020|5038|6759)\d+\$/, 16, "Maestro", :luhn],
[/^(51|54|55)\d+\$/, 16, "Mastercard", :luhn],
[/^(6334|6767)\d+\$/, [16,19], "Solo", :luhn],
[/^4\d+\$/, [13,16], "Visa", :luhn],
[/^(417500|4917|4913)\d+\$/, 16, "Visa Electron", :luhn]
]
UNKNOWN_PREFIX = [nil, 0, "Unknown", :luhn]

def CChecker.usage(doexit = false)
puts "usage: cchecker.rb <ccnumber>"
exit if doexit
end

# try to identify the card
def CChecker.check_prefix(ccnumber)
pr = PREFIXES.detect {|p| p[0].match(ccnumber) &&
p[1].include?(ccnumber.length)}
(pr.nil?) ? UNKNOWN_PREFIX : pr
end

# do the complete check of the cc:
# 1.- try to identify
# 2.- apply algorithm (should return true/false)
# returns an array: [isvalid, card_identifier]
def CChecker.check(ccnumber)
ccnumber = ccnumber.to_s.delete(" ")
pr = check_prefix(ccnumber)
[send(pr[3], ccnumber), pr[2]]
end

# classic Luhn's algorithm
def CChecker.luhn(ccnumber)
sum = 0
ccnumber.reverse.split(//).each_with_index do |c, i|
cx = c[0]-48; # this should be faster than c.to_i, right?
next if cx > 9 || cx < 0 # only numbers, please
if (i+1) & 1 == 0
cx *= 2
cx = (cx/10 + cx%10) if cx > 9
end
sum += cx
end
sum % 10 == 0
end
end

CChecker::usage(true) if ARGV.size != 1
valid, card = CChecker::check(ARGV[0])
if valid
puts "#{card} Valid"
else
puts "#{card} Invalid"
end

Rolando Abarca, Apr 30, 2007