From: Yossef Mendelssohn on
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}"


From: Jeremy Hinegardner on
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

--
========================================================================
Jeremy Hinegardner jeremy(a)hinegardner.org

From: Philip Gatt on
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

From: Chris Shea on
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

--
Posted via http://www.ruby-forum.com/.

From: Mark Day on
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