Checking Credit Cards (#122)

On Apr 27, 2007, at 7:59 AM, Ruby Q. wrote:

Step 2: 8+4+0+8+0+4+2+2+6+4+1+0+6+1+4+8+1+8+3 = 70
Step 3: 70 % 10 == 0

Thus that card is valid.

Uh, this is probably just affecting me but…

In his example, after he doubles the second to last digit (call it
d), he uses mod10 on it (9*2 = 18 %10 = 8). That is the way to get
his numbers, but a) where does he say that and b) where do the 10 and
14 come from?

Help me, hyperactive ruby posters!
~ Ari
English is like a pseudo-random number generator - there are a
bajillion rules to it, but nobody cares.

On Fri, Apr 27, 2007 at 08:59:49PM +0900, Ruby Q. wrote:

| MasterCard | 51-55 | 16 |
±-----------±------------±--------------+
| Visa | 4 | 13 or 16 |
±-----------±------------±--------------+

Wikipedia has a great chart showing all the prefixes of most known
credit cards, along with lengths and overlap between cards (currently
none).

enjoy,

-jeremy

You move back to the beginning after doubling the 2nd to last. You
double every other one on the way back to the beginning. The 10 is
52 and the 14 is 72

On Apr 28, 9:04 pm, Ari B. [email protected] wrote:

Step 2: 8+4+0+8+0+4+2+2+6+4+1+0+6+1+4+8+1+8+3 = 70
Step 3: 70 % 10 == 0

Thus that card is valid.

Uh, this is probably just affecting me but…

In his example, after he doubles the second to last digit (call it
d), he uses mod10 on it (9*2 = 18 %10 = 8). That is the way to get
his numbers, but a) where does he say that and b) where do the 10 and
14 come from?

The confusing part (that I didn’t catch when I read it) is that step 2
is to sum all the digits, not the numbers.

So
step 1) 9 * 2 = 18
step 2) 1 + 8

He’s not modding the result of the multiplication by 10, but rather
adding up the resulting component digits. The same occurs with the 10
and 14 (which Philip pointed out are the result of 52 and 72,
respectively).

On Apr 28, 2007, at 10:50 PM, Phrogz wrote:

his numbers, but a) where does he say that and b) where do the 10 and
adding up the resulting component digits. The same occurs with the 10
and 14 (which Philip pointed out are the result of 52 and 72,
respectively).

Yes, most descriptions tell you to mod 10 the bigger numbers to get
the back to a single digit, but that adds a step that doesn’t have
any affect. I left it out.

James Edward G. II

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

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 Ruby Q. has been translated into Japanese.
Maybe there will be more people participating.

http://d.hatena.ne.jp/nappa_zzz/20070429

Harry

On Apr 28, 2007, at 8:04 PM, Ari B. wrote:

In his example, after he doubles the second to last digit (call it
d), he uses mod10 on it (9*2 = 18 %10 = 8). That is the way to get
his numbers, but a) where does he say that and b) where do the 10
and 14 come from?

This confused me at first, too. Let’s take just the last four
digits: 7893. Counting from the end, double every second digit,
leaving the others unchanged. That gives you: 14, 8, 18, 3 (where
14=72 and 18=92; the 8 and the 3 are unchanged). Now add up every
digit of the resulting sequence: 1+4 + 8 + 1+8 + 3. Note the “1
+4” comes from the 14, and the “1+8” comes from the 18.

Does that help?

-Mark

My second ever rubyquiz submission, so be nice.
Sorry if the submission is a little early (by about an hour and a half),
but I won’t be around a comp tomorrow.

Raj S.

ccc.rb

Checking Credit Cards

By Raj S.

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
TYPES.each do |company|
company[‘key’].each do |key|
if company[‘length’].include?(@number.length) and
@number.join.begins_with?(key.to_s)
return company[‘type’]
end
end
end
“Unknown”
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}”
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

Raj S. wrote:

My second ever rubyquiz submission, so be nice.
Sorry if the submission is a little early (by about an hour and a half),
but I won’t be around a comp tomorrow.

Raj S.

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


greets
(
)
(
/\ .-"""-. /
//\/ , //\
|/| ,;;;;;, |/|
//\;-"""-;///\
// / . / \
(| ,-| \ | / |-, |)
//__\.-.-./__\
// /.-(() ())-.\ \
(\ |) ‘—’ (| /)
(| |)
jgs ) (/

one must still have chaos in oneself to be able to give birth to a
dancing star

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

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.

Check Starting numbers and length

def card_ok(str,info)
check = “UNKNOWN”
info.each do |t|
pref = t[0]
leng = t[1]
name = t[2]
pref.each do |x|
if x == str.slice(0…x.length)
leng.each do |y|
if y.to_i == str.length
check = name.dup
end
end
end
end
end
return check
end

Check Luhn algorithm

def luhn(str)
luhn_hash = Hash.new(“INVALID”)
if str.length != 0
luhn_hash[0] = “VALID”
arr = str.split(//).reverse
arr2 = []
arr3 = []
(0…arr.length).each do |u|
arr2 << arr[u] if u %2 == 0
arr2 << (arr[u].to_i * 2).to_s if u %2 != 0
end

arr2.each do |r|
arr3 << r.split(//).inject(0) {|sum,i| sum + i.to_i}
end

val = arr3.inject(0) {|sum,i| sum + i} % 10
end
return luhn_hash[val]
end

Card information

test = []
test << [[“31”,“34”],[“15”],“AMEX”]
test << [[“6011”],[“16”],“DISCOVER”]
test << [(“51”…“55”).to_a,[“16”],“MASTERCARD”]
test << [[“4”],[“13”,“16”],“VISA”]
#test << [(“3528”…“3589”).to_a,[“16”],“JCB”]
#test << [[(“3000”…“3029”).to_a + (“3040”…“3059”).to_a +
#(“3815”…“3889”).to_a + [“36”,“389”]].flatten!,[“14”],“DINERS”]

Main

str = ARGV.join
arr = []
arr << card_ok(str,test)
arr << luhn(str)
puts arr

Harry

Here is my solution to Quiz 122:

class CardChecker def initialize(card_num) @card_num = card_num @issuer = case when visa? then 'VISA' when mastercard? then 'MasterCard' when amex? then 'AMEX' when discover? then 'Discover' else 'UNKNOWN' end @valid = valid? end def visa? (@card_num.size == 13 || @card_num.size == 16) && @card_num =~ /^4/ end def mastercard? @card_num.size == 16 && @card_num =~ /^5[1-5]/ end def amex? @card_num.size == 15 && @card_num =~ /^3[47]/ end def discover? @card_num.size == 16 && @card_num =~ /^6011/ end def valid? digits = @card_num.reverse.split('') sum = 0 digits.each_with_index do |e, i| d = e.to_i if i & 1 == 0 sum += d else q, r = (d + d).divmod(10) sum += q + r end end sum % 10 == 0 end def to_s @issuer + (@valid ? " " : " IN") + "VALID" end end

if $0 == FILE
puts CardChecker.new(ARGV.join)
end

This was a very easy (I like easy) but fun quiz. I believe this is
the first time I actually completed a quiz solution, including
testing, in less than an hour.

Regards, Morton

48 hours are past so here is my first solution. I’m just coding ruby for
a few days so any feedback is welcome :slight_smile:

My script recognizes 12 different creditcard companys and of course says
if a card could belong to two different companys at the same time or if
it is unknown.

I think the companycheck part could be done better by calling check.call
from a loop but I couldn’t figure out how to pass such an hash:

hash = Hash.new { |hash, key| hash[key] = [] }
raw_data = [ [1,“American Express”],[1,/^34|^37/], [1, “15”],
[2,“Diners CLub Blanche”],[2,/^30[0-5]/], [2, “14”],
[3,“Solo”],[3,/^6334|^6767/],[3,“16”],[3,“18”],[3,“19”]]
raw_data.each { |x,y| hash[x] << y }

to

check = lambda{|reg,c,*d| if number =~ reg: @company += " or " + c if
d.include?(number.length) end }

in a loop. Any idea how to do this?

here the solution:

#!/usr/bin/ruby -w

validate.rb

######################################################

validator for creditcard numbers

by anansi

29/04/07 on comp.lang.rub

[QUIZ] Checking Credit Cards (#122)

######################################################

recognizes:

American Express,

Diners CLub Blanche,

Diners CLub International,

Diners Club US & Canada,

Discover,

JCB,

Maestro (debit card),

Mastercard,

Solo,

Switch,

Visa,

Visa Electron

######################################################

class CreditCard

def initialize(number)
number.scan(/\D/) { |x| # scans number for every not-digit
symbol
puts x + " is no valid credit card
symbol.\nJust digits allowed!!"
exit
}
end

def companycheck(number)
@company= “”
# block check compares the length and sets the company value
check = lambda{|reg,c,*d| if number =~ reg: @company += " or " + c
if d.include?(number.length) end }
# adding a new bank is quite easy, just put a new check.call in
with:
# check.call(regular expressions for the starting
bytes,“Company-name”,length1,length2,…)
# I’m sure this can be done somehow better invoking check.call by a
loop
# but I couldn’t figure out how to pass a array with dynamic
variable count into check.call
check.call( /^34|^37/ , “American Express” , 15 )
check.call( /^30[0-5]/ ,“Diners CLub Blanche”,14)
check.call( /^36/ , “Diners CLub International”,14)
check.call( /^55/ , “Diners Club US & Canada”,16)
check.call( /^6011|^65/ , “Discover”,16)
check.call( /^35/ , “JCB” , 16)
check.call( /^1800|^2131/ , “JCB” , 15)
check.call( /^5020|^5038|^6759/ , “Maestro (debit card)” , 16)
check.call( /^5[0-5]/ , “Mastercard” , 16)
check.call( /^6334|^6767/ , “Solo” , 16 , 18 , 19)
check.call( /^4903|^4905|^4911|^4936|^564182|^633110|^6333|^6759/ ,
“Switch” , 16 , 18 , 19)
check.call( /^4/ , “Visa” , 13 , 16)
check.call( /^417500|^4917|^4913/ , “Visa Electron” , 16)
if @company == “”
puts “Company : Unknown”
else
puts “Company : #{@company.slice([email protected])}”
end
end

def crossfoot(digit)
digit = “%2d” % digit # converts
integer to string
if digit[0] == 32
digit = (digit[1].to_i) -48
else # if the doubled
digit has more than 2 digits
digit= (digit[0].to_i) + (digit[1].to_i) -96 # adds the single
digits and converts back to integer
end
end

def validation(number)
math = lambda { |dig| number[@count-dig]-48 } # block math converts
str to int of the current digit
@duplex = false
@count = number.length
@result = 0
for i in (1…@count)
if @duplex == false # for every first digit from the
back
@result += math.call i # add to result
@duplex = true
else # for every second digit
from the back
@result += crossfoot((math.call i)*2) # mutl. digit with 2, do the
crossfoot and add to result
@duplex = false
end
end
@result.modulo(10)
end

end

begin

if ARGV.length == 0 # checks if argument is
passed
puts “no input\nusage, e.g.: ruby validate.rb 4408 0412 3456 7893”
exit
end

number = ARGV.join(’’).gsub(" “,”") # reads args and kills all
spaces and newlinefeed

my_creditcard = CreditCard.new(number) # checks if just digits are
inputed otherwise: abort.

my_creditcard.companycheck(number) # checks for known or unknown
company

if my_creditcard.validation(number) == 0 # checks validation with
luhn-algo
puts “Validation: successful”
else
puts “Validation: failure”
end

eof


greets
(
)
(
/\ .-"""-. /
//\/ , //\
|/| ,;;;;;, |/|
//\;-"""-;///\
// / . / \
(| ,-| \ | / |-, |)
//__\.-.-./__\
// /.-(() ())-.\ \
(\ |) ‘—’ (| /)
(| |)
jgs ) (/

one must still have chaos in oneself to be able to give birth to a
dancing star

My solution (the second I’ve submitted, so please be nice :).
Suggestions welcome.

BCTYPES = {
[[34,37],[15]] => “AMEX”,
[[6011],[16]] => “Discoverer”,
[(51…57).to_a,16] => “MasterCard”,
[[4],[13,16]] => “Visa”}

def ctype(num)
BCTYPES.each { |n,t| n[0].each { |s|
return t if num.grep(/^#{s}/).any? && n[1].include?(num.length)
} }
“Unknown”
end

def luhncheck(num)
e = false
num.split(//).reverse.collect { |a| e=!e
a.to_i*(e ? 1:2)
}.join.split(//).inject(0) {|a,b| a+b.to_i} % 10 == 0 ? “Valid” :
“Invalid”
end

card = ARGV.join.gsub(/ /, ‘’)
if card == “”
puts "Usage: #{$0} "
else
puts ctype(card)
puts luhncheck(card)
end

Joe

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 Q. 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.
def add_set_ivar ivar, arg
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
add_set_ivar :@prefixes, prefixes
add_set_ivar :@lengths, lengths
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

Ruby Q. wrote:

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.

I’m rather new to ruby and this is the first time I participated in this
quiz.
If found this one to be rather easy and fun to work with.
Here’s my solution:

#!/usr/bin/env ruby

class CreditCard
attr_reader :number
CardTypes = [
{ :name => “AMEX”, :regex => /(34|37)\d{13}/, :luhn => true},
{ :name => “Bankcard”, :regex => /5610\d{12}/, :luhn => true},
{ :name => “Bankcard”, :regex => /56022[1-5]\d{10}/, :luhn => true},
{ :name => “China Union Pay”, :regex => /622\d{13}/, :luhn =>
false},
{ :name => “DC-CB”, :regex => /30[0-5]\d{11}/, :luhn => true},
{ :name => “DC-eR”, :regex => /2(014|149)\d{11}/, :luhn => false},
{ :name => “DC-Int”, :regex => /36\d{12}/, :luhn => true},
{ :name => “DC-UC or MasterCard”, :regex => /55\d{14}/, :luhn =>
true},
{ :name => “Discover”, :regex => /6011\d{12}/, :luhn => true},
{ :name => “MasterCard”, :regex => /5[1-4]\d{14}/, :luhn => true},
{ :name =>“Maestro”, :regex => /(5020|5038|6759)\d{12}/, :luhn =>
true},
{ :name => “Visa”, :regex => /4(\d{13}|\d{16})/, :luhn => true},
{ :name => “Unknown”, :regex => //, :luhn => true} ]
# If the credit card is of unknown type, we’ll just assume
# that it can be verified using the Luhn algorithm.

def initialize(num)
self.number=num
end

def number=(num)
raise ArgumentError, “Supplied argument is not a number” unless
num.to_s =~
/^[-\s\d]+$/
@number=num.to_s.gsub(/(\s|
|-)/,’’)
@type=nil
@validity=nil
end

def card_type
@type||=CardTypes.detect {|i| i[:regex].match @number}
end

def to_s
“Number: #{@number}, Type: #{card_type[:name]}, Valid: #{valid?}”
end

def valid?
return @validity unless @validity.nil?
return @validity=“unknown” unless card_type[:luhn]
[email protected](//).reverse.map {|x| x.to_i}
arr.each_with_index{|v,i| arr[i]=v*2 if i%2==1}
sum=arr.join.split(//).map do |x| x.to_i end.inject {|s,i| i+s}
@validity = sum%10==0
end
end

if FILE==$0
card=CreditCard.new(if ARGV.empty?
puts “Please enter your credit card
number:”
gets.chomp
else
ARGV.join
end)
puts card
end

My solution:

#!/usr/bin/env ruby -w

class Numeric
def to_a
[self]
end
end

class CardType

@@cards = []

def initialize(name, prefix, length)
@name = name
@prefix = prefix.to_a
@length = length.to_a
end

def match(string)
@length.member?(string.length) && @prefix.find { |v| string =~ /
^#{v.to_s}/ }
end

def to_s
@name
end

def CardType.register(name, prefix, length)
@@cards << CardType.new(name, prefix, length)
end

def CardType.luhn_check(value)
value.reverse.scan(/…{0,1}/).collect do |s|
s[0…0] + (s[1…1].to_i * 2).to_s
end.join.scan(/./).inject(0) { |sum, s| sum + s.to_i } % 10 == 0
end

def CardType.find_card(string)
value = string.gsub(/[ -]/,’’)
return “Illegal Code” if value =~ /\W/
“#{@@cards.find { |c| c.match(value) } || “Unknown”} [#
{luhn_check(value) ? “Valid” : “Invalid” }]”
end

end

CardType.register “Maestro”, [5020, 5038, 6759], 16
CardType.register “VISA”, 4, [13, 16]
CardType.register “MasterCard”, 51…55, 16
CardType.register “Discover”, [6011, 65], 16
CardType.register “American Express”, [34, 37], 15
CardType.register “Diners Club International”, 36, 14
CardType.register “Diners Club Carte Blanche”, 300…305, 14
CardType.register “JCB”, 3528…3589, 16

number = ARGV.join " "
puts number + " => " + CardType.find_card(number)

Here’s my solution.

require ‘enumerator’

class CardProcessor

CARDS = {‘visa’ => {:length => [13,16], :begin => [4]},
‘amex’ => {:length => [15], :begin => [34,37]},
‘discover’ => {:length => [16], :begin => [6011]},
‘mastercard’ => {:length => [16], :begin => (51…55)},
‘jcb’ => {:length => [16], :begin => (3528…3589)},
‘diners club’ => {:length => [14], :begin =>
[(3000…3029).to_a, (3040…3059).to_a, 36, (3815…3889).to_a,
389].flatten}
}

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

def luhn_valid?
a = ‘’
@number.split(’’).reverse.each_slice(2){ |leave, double| a <<
leave << (double.to_i * 2).to_s }
a.split(’’).inject(0){|s,v| s + v.to_i } % 10 == 0
end

def length_valid?
CARDS[@name][:length].include? @number.size
end

def beginning_valid?
@number =~ /^#{CARDS[@name][:begin].to_a.join(’|’)}/
end

def valid?
beginning_valid? && length_valid? && luhn_valid?
end

def self.cards
CARDS.keys
end

end

if FILE == $0

if ARGV.empty?
puts "Usage ruby #{File.basename($0)} "
exit 0
end

number = ARGV.join

if CardProcessor.new(’’, number).luhn_valid?
puts “Your card appears to be a valid card.”
result = CardProcessor.cards.map {|card| card if
CardProcessor.new(card, number).valid? }.compact
puts “Vendor: #{(result.empty? ? ‘unknown’ :
result.first).capitalize}”
else
puts “Your card doesn’t appear to be valid.”
end

end

On Apr 28, 2007, at 11:30 PM, Philip G. wrote:

You move back to the beginning after doubling the 2nd to last. You
double every other one on the way back to the beginning. The 10 is
52 and the 14 is 72

Ohh, I see what he did now! Thanks for your help!

-------------------------------------------------------|
~ Ari
crap my sig won’t fit

On Apr 29, 2007, at 1:14 AM, Mark Day wrote:

This confused me at first, too. Let’s take just the last four
digits: 7893. Counting from the end, double every second digit,
leaving the others unchanged. That gives you: 14, 8, 18, 3 (where
14=72 and 18=92; the 8 and the 3 are unchanged). Now add up every
digit of the resulting sequence: 1+4 + 8 + 1+8 + 3. Note the
“1+4” comes from the 14, and the “1+8” comes from the 18.

Tons, your and philip’s responses have definitely cleared this up.
Now I can begin writing it.
--------------------------------------------|
If you’re not living on the edge,
then you’re just wasting space.

Yet another Great Ruby Q. :wink:
Well I thought this was a Quiz particularly suited to write nice
code. I guess I somehow failed as it is too long, but I put some of
the features I like most of Ruby, well I guess it is a solution which
is consistent with my style :wink:
And as I feel that this is not said frequently enough, I’ll just say
it: “Thank you James for all these Quizzes, and thanks to the Quiz
creators too of course.”

Cheers
Robert

#!/usr/bin/ruby

vim: sts=2 sw=2 expandtab nu tw=0:

class String
def to_rgx
Regexp.new self
end

def ccc
Checker.new{
amex [34,37], 15
discover 6011, 16
master 50…55, 16
visa 4, [13,16]
jcb 3528…3589, 16
}.check self
end
end

class Checker
UsageException = Class.new Exception
def initialize &blk
@cards = {}
instance_eval &blk
end

def check str
s = str.gsub(/\s/,"")
@cards.each do
|card, check_values|
return [ luhn( s ), card.to_s.capitalize ] if
check_values.first === s && check_values.last.include?( s.length
)
end
[ nil, “Unknown” ]
end

def luhn s
sum = 0
s.split(//).reverse.each_with_index{
| digit, idx |
sum += (idx%2).succ * digit.to_i
}
(sum % 10).zero? ? " Valid" : “n Invalid”
end

This is one of the rare examples where

the method_missing parametes are not

id, *args, &blk, trust me I know what

I am doing :wink:

def method_missing credit_card_name, regs, lens
raise UsageException, “#{card_name} defined twice” if
@cards[credit_card_name]
### Unifying Integer, Array and Range parameters
lens = [lens] if Integer === lens
lens = lens.to_a
### Genereating regular expressions
regs = [regs] if Integer === regs
regs = regs.map{ |r| “^#{r}” }.join("|").to_rgx
@cards[credit_card_name] = [ regs, lens ]
end
end

ARGV.each do
| number |
puts “Card with number #{number} is a%s %s card” %
number.ccc

end # ARGV.each do