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

D

Daniel Martin

I was trying to go for "most compact and obfuscated version of the
Luhn algorithm", but unfortunately the complications of the algorithm
meant that I had to debug my implementation, which left it slightly
more readable. Oh well. It's still pretty dense and while my type
method might be readable it's as dense as I care to make it.

#!ruby -x
def type(s)
case s.gsub(/\D/,'')
when /^(?=34).{15}$/; "AMEX"
when /^(?=37).{15}$/; "AMEX"
when /^(?=6011).{16}$/; "Discover"
when /^(?=5[1-5]).{16}$/; "MasterCard"
when /^(?=4).{13}(...)?$/; "Visa"
else ; "Unknown"
end
end

def luhn(s)
s.scan(/\d/).inject([0,0]){|(a,b),c|[b+c.to_i,
a+c.to_i*2%9+(c=='9' ? 9 : 0)]}[0]%10 == 0
end

s = ARGV.join
puts "#{type(s)} #{luhn(s)?'V':'Inv'}alid"

__END__
 
B

Ball, Donald A Jr (Library)

------_=_NextPart_001_01C78AB6.663CF2D0
Content-Type: text/plain;
charset="iso-8859-1"
Content-Transfer-Encoding: quoted-printable

My submission is pastied here:

http://pastie.caboo.se/57536

and pasted below. I tried to come up with a solution that would allow =
easy manipulation of the list of credit card providers.

- donald

# Ruby Quiz 123
# Donald Ball
# version 1.0

require 'enumerator'

class Integer
def digits
self.to_s.scan(/\d/).map{|n| n.to_i}
end

def sum_digits
digits.inject{|sum, n| sum + n}
end

def luhn
digits.reverse.enum_slice(2).inject(0) do |sum, sl|
sum + sl[0].sum_digits + (sl.length =3D=3D 2 ? =
(2*sl[1]).sum_digits : 0)
end
end

def luhn?
luhn % 10 =3D=3D 0
end
end

module Credit
class Provider
attr_reader :name

def initialize(name, pattern)
@name =3D name
@pattern =3D pattern
end
=20
def valid?(number)
@pattern.match(number)
end

def to_s
@name
end
end

Providers =3D []
Providers << Provider.new('AMEX', /^(34|37)\d{13}$/)
Providers << Provider.new('Discover', /^6011\d{12}$/)
Providers << Provider.new('MasterCard', /^5(1|2|3|4|5)\d{14}$/)
Providers << Provider.new('Visa', /^4(\d{12}|\d{15})$/)
=20
class Card
attr_reader :number

def initialize(number)
if number.is_a? Integer
@number =3D number
elsif number.is_a? String
@number =3D number.gsub(/\s/, '').to_i
else
raise InvalidArgument, number
end
end
def provider
Providers.each do |provider|
return provider if provider.valid?(@number.to_s)
end
return 'Unknown'
end
def valid?
@number.luhn?
end
def to_s
@number.to_s
end
end
end

card =3D Credit::Card.new(ARGV[0])
puts card.provider.to_s << ' ' << (card.valid? ? 'valid' : 'invalid')

------_=_NextPart_001_01C78AB6.663CF2D0--
 
B

Benjamin Paul Kay

We all know that one can either define a method that has a name of the form
'method=' (or use attr_accessor/attr_writer), and when Ruby sees

obj.method = value

the equivalent of

obj.method=(value)

I'm finding that the parser never expects or allows more than one argument
to the 'method=' style of method naming. For example, you could define a
method interface of:

def method=(arg, &block)

but the parser doesn't want to let you pass in a block to this call. Both
of the following generate parse errors:

obj.method = 12 { <some_code_here> }
obj.method=(12) { <some_code_here> }

(Simply removing the '=' from the method name allows the second case to
work.) I couldn't find anything in the pickaxe book or in ruby-talk
archives about this restriction. I'm on Ruby version 1.6.5 -- has this
behavior changed in later versions, or is it a permanent trait of Ruby
syntax?

Thanks,

- jeff

Also consider the test case below, where a method with = in its name always returns the value passed to it. This is really frustrating, as I want to use = in method names as syntactic sugar but can't because the methods don't behave like they're supposed to.
Is this a bug or a "feature"?

This script:

class TestClass
def test=(number)
# 42 is the answer to life the universe and everything.
42
end
def test(number)
# 42 is the answer to life the universe and everything.
42
end
end

test = TestClass.new

puts 'With the = in the method name:'
puts test.test=7
puts test.test=("Doesn't work with parentheses either.")
puts test.test=[1,2,3,"Arrays are cool!"]

puts 'And without:'
puts test.test(7)
puts test.test("Doesn't work with parentheses either.")
puts test.test([1,2,3,"Arrays are cool!"])

Produces output:

With the = in the method name:
7
Doesn't work with parentheses either.
1
2
3
Arrays are cool!
And without:
42
42
42
 
D

Daniel Martin

Daniel Martin said:
I was trying to go for "most compact and obfuscated version of the
Luhn algorithm", but unfortunately the complications of the algorithm
meant that I had to debug my implementation, which left it slightly
more readable. Oh well. It's still pretty dense and while my type
method might be readable it's as dense as I care to make it.

You know, it's longer, but I think I like this definition of the luhn
algorithm better, in terms of its conceptual conciseness:

def luhn(s)
s.scan(/\d/).map{|x|x.to_i}.inject([0,0]){
|(a,b),c|[b+c%9,a+2*c%9]}[0]%10 == s.scan(/9/).size%10
end

(Now - the challenge is to figure out why that works)

An interesting observation based on this representation is that the
Luhn algorithm will fail to catch the transposition of an adjacent "0"
and "9":

both '446-667-6097' and '446-667-6907' pass.
 
W

Wil

This was my first rubyquiz. I took a meta programming approach, since
it seemed like an easy enough example for it, and I haven't done
enough meta programming yet. The only one of the solutions above that
I saw using some type of dynamic programming was the one using
method_missing above. Critiques welcome. For those interested, I did
manage to write a more details description on my blog:
http://webjazz.blogspot.com/2007/04/ruby-quiz-122-solution-checking-credit.html

Metaprogramming is probably overkill for this, since you don't get a
new type of credit card company all the time, but it's probably a nice
technique for when you have an object that needs lots of different
combinations of things each time.

Here is my solution:

require 'enumerator'

class CreditCardChecker

def self.metaclass; class << self; self; end; end

class << self
attr_reader :cards

# writes a method with the card_name? as a method name. The
method created would
# check what type of credit card a number is, based on the rules
given in the block.
# Use this function in the subclass
#
# class MyChecker < CreditCardChecker
# credit_card:)amex) { |cc| (cc =~ /^34.*/ or cc =~ /^37.*/)
and (cc.length == 15) }
# end
def credit_card(card_name, &rules)
@cards ||= []
@cards << card_name

metaclass.instance_eval do
define_method("#{card_name}?") do |cc_num|
return rules.call(cc_num) ? true : false
end
end
end

end

def cctype(cc_num)
self.class.cards.each do |card_name|
return card_name if self.class.send("#{card_name}?",
normalize(cc_num))
end
return :unknown
end

def valid?(cc_num)
rev_num = []
normalize(cc_num).split('').reverse.each_slice(2) do |pair|
rev_num << pair.first.to_i << pair.last.to_i * 2
end
rev_num = rev_num.to_s.split('')
sum = rev_num.inject(0) { |t, digit| t += digit.to_i }
(sum % 10) == 0 ? true : false
end

private
def normalize(cc_num)
cc_num.gsub(/\s+/, '')
end
end

class CreditCard < CreditCardChecker
credit_card:)amex) { |cc| (cc =~ /^34.*/ or cc =~ /^37.*/) and
(cc.length == 15) }
credit_card:)discover) { |cc| (cc =~ /^6011.*/) and (cc.length ==
16) }
credit_card:)mastercard) { |cc| cc =~ /^5[1-5].*/ and (cc.length ==
16) }
credit_card:)visa) { |cc| (cc =~ /^4.*/) and (cc.length == 13 or
cc.length == 16) }
end

CCnum = ARGV[0]

cccheck = CreditCard.new
puts cccheck.cctype(CCnum)
puts cccheck.valid?(CCnum)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,069
Latest member
SimplyleanKetoReviews

Latest Threads

Top