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 Quiz #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