```For my solution, I created a Card class that holds a Set of Integers/Ranges
for acceptable prefixes and lengths. I added a prefix_of? method to both
Integer and Range for checking whether they are prefixes of a String.
Card#initialize is pretty flexible in arguments it takes thanks to an
add_set_ivar method that turns numbers/sets/arrays/ranges into the kind of
Sets that I want. From there, the Card#valid? method is straightforward -
just call any? on the prefixes and lengths and make sure both are true.

The Card.luhn_valid? method checks if a card number passes the Luhn
algorithm. I messed around with a few different ways of doing it, and
settled on a rather dense 4-liner.

#!/usr/bin/env ruby
# check_credit_card.rb
# Ruby Quiz 122: Checking Credit Cards

require 'set'

class Integer
# Determine if this number is a prefix of the given string for the given base.
def prefix_of? str, base = 10
not /^#{to_s(base)}/.match(str).nil?
end
end

class Range
# Determine if any numbers within this range is a prefix of the given string
# for the given base.
def prefix_of? str, base = 10
# We could use the first case every time, but the second is much quicker
# for large ranges.
if str[0].chr == '0'
any? { |num| num.prefix_of? str, base }
else
num = str.slice(0..(max.to_s(base).length-1)).to_i
num >= min and num <= max
end
end
end

class Card
attr_accessor :name

# Turn arg into a Set instance variable based on its class.
# This is so initialize can easily accept a few different argument types.
case arg
when Set;   instance_variable_set ivar, arg
when Array; instance_variable_set ivar, Set.new(arg)
else;       instance_variable_set ivar, Set[arg]
end
end

# prefixes can be:
#   - a single number
#   - an Array of numbers
#   - a Range of numbers
#   - an Array of numbers and Ranges
#   - a Set of numbers
#   - a Set of numbers and Ranges
#
# lengths can be:
#   - a single number
#   - an Array of numbers
#   - a Set of numbers
def initialize name, prefixes, lengths
@name = name
end

# Determine if a number is valid for this card.
def valid? num
num = num.to_s
@prefixes.any? { |pre| pre.prefix_of? num } and
@lengths.any? { |len| len == num.length  }
end

# Determine if the given number passes the Luhn algorithm.
# This is pretty damn dense.. perhaps I should spread it out more..
def Card.luhn_valid? num
digits = num.to_s.split(//).map! { |d| d.to_i }    # separate digits
(digits.size-2).step(0,-2) { |i| digits[i] *= 2 }  # double every other
digits.map! { |d| d < 10 ? d : [1,d-10] }.flatten! # split up those > 10
(digits.inject { |sum, d| sum + d } % 10).zero?    # sum divisible by 10?
end
end

if \$0 == __FILE__
CardPool = Set[
Card.new('AMEX',       [34,37],      15),
Card.new('Discover',   6011,         16),
Card.new('MasterCard', (51..55),     16),
Card.new('Visa',       4,            [13,16]),
Card.new('JCB',        (3528..3589), 16),
Card.new('Diners',     [(3000..3029),(3040..3059),36,(3815..3889),389], 14)
]

card_num = \$stdin.gets.chomp.gsub! /\s/, ''
cards = CardPool.select { |c| c.valid? card_num }

if cards.size.zero?
puts "Unknown card."
else
puts "Number matched #{cards.size} cards:"
cards.each { |c| puts "  #{c.name}" }
end

if Card.luhn_valid?(card_num); puts 'Passed Luhn algorithm'
else;                          puts 'Failed Luhn algorithm'; end
end

--
Jesse Merriman
jessemerriman / warpmail.net
http://www.jessemerriman.com/

```