Can this check digit method be refactored? - ruby

I have the following method for doing a check digit on a tracking number, but it just feels lengthy/sloppy. Can it be refactored and just generally cleaned up?
I'm running Ruby 1.8.7.
def is_fedex(number)
n = number.reverse[0..14]
check_digit = n.first.to_i
even_numbers = n[1..1].to_i + n[3..3].to_i + n[5..5].to_i + n[7..7].to_i + n[9..9].to_i + n[11..11].to_i + n[13..13].to_i
even_numbers = even_numbers * 3
odd_numbers = n[2..2].to_i + n[4..4].to_i + n[6..6].to_i + n[8..8].to_i + n[10..10].to_i + n[12..12].to_i + n[14..14].to_i
total = even_numbers + odd_numbers
multiple_of_ten = total + 10 - (total % 10)
remainder = multiple_of_ten - total
if remainder == check_digit
true
else
false
end
end
EDIT: Here are valid and invalid numbers.
Valid: 9612019950078574025848
Invalid: 9612019950078574025847

def is_fedex(number)
total = (7..20).inject(0) {|sum, i| sum + number[i..i].to_i * ( i.odd? ? 1 : 3 ) }
number[-1].to_i == (total / 10.0).ceil * 10 - total
end
I believe you should keep your code. While it's not idiomatic or clever, it's the one you will have the least trouble to understand a few months from now.

I'm not a ruby programmer, so if any of the syntax is off, I apologize but you should get the general idea. A few things I see: First, you don't need to slice the array, a single index should be sufficient. Second, Instead of splitting even and odd, you could do something like this:
total = 0
for i in (1..14)
total += n[i].to_i * ( i % 2 == 1 ? 1 : 3 )
end
Third, remainder could be simplified to 10 - (total % 10).

I realize you're running 1.8.7, but here's my attempt using each_slice and inject in conjunction, a 1.9.2 feature:
def is_fedex(number)
total = number.reverse[1..14].split(//).map(&:to_i).each_slice(2).inject(0) do |t, (e,o)|
t += e*3 + o
end
10 - (total % 10) == number[-1].to_i
end
It passes both tests

Give this a try:
#assuming number comes in as a string
def is_fedex(number)
n = number.reverse[0..14].scan(/./)
check_digit = n[0].to_i
total = 0
n[1..14].each_with_index {|d,i| total += d.to_i * (i.even? ? 3 : 1) }
check_digit == 10 - (total % 10)
end
> is_fedex("12345678901231") => true
edit incorporating simplified remainder logic as Kevin suggested

Something like this?
def is_fedex(number)
even_arr, odd_arr = number.to_s[1..13].split(//).map(&:to_i).partition.with_index { |n, i| i.even? }
total = even_arr.inject(:+) * 3 + odd_arr.inject(:+)
number.to_s.reverse[0..0].to_i == (total + 10 - (total % 10)) - total
end
If you can give me a valid and invalid number I can test if it works and maybe tweak it further :)

This function should to:
def is_fedex(number)
# sanity check
return false unless number.length == 15
data = number[0..13].reverse
check_digit = number[14..14].to_i
total = (0..data.length-1).inject(0) do |total, i|
total += data[i..i].to_i * 3**((i+1)%2)
end
(10 - total % 10) == check_digit
end
The arithmetic expression 3**((i+1)%2) might look a bit complex, but is essentially the same as (i.odd? ? 1 : 3). Both variants are correct, which you use is up to you (and might affect speed...)
Also note, that if you use Ruby 1.9, you can use data[i] instead of data[i..i] which is required for for Ruby 1.8.

Related

How can I count number of iterations/steps to find answers of a method - RUBY

How can I get the number of iterations/steps that this method takes to find an answer?
def binary_search(array, n)
min = 0
max = (array.length) - 1
while min <= max
middle = (min + max) / 2
if array[middle] == n
return middle
elsif array[middle] > n
max = middle - 1
elsif array[middle] < n
min = middle + 1
end
end
"#{n} not found in this array"
end
One option to use instead of a counter is the .with_index keyword. To use this you'll need to use loop instead of while, but it should work the same. Here's a basic example with output.
arr = [1,2,3,4,5,6,7,8]
loop.with_index do |_, index| # The underscore is to ignore the first variable as it's not used
if (arr[index] % 2).zero?
puts "even: #{arr[index]}"
else
puts "odd: #{arr[index]}"
end
break if index.eql?(arr.length - 1)
end
=>
odd: 1
even: 2
odd: 3
even: 4
odd: 5
even: 6
odd: 7
even: 8
Just count the number of iterations.
Set a variable to 0 outside the loop
Add 1 to it inside the loop
When you return the index, return the count with it (return [middle, count]).
I assume the code to count numbers of interations required by binary_search is to be used for testing or optimization. If so, the method binary_search should be modified in such a way that to produce production code it is only necessary to remove (or comment out) lines of code, as opposed to modifying statements. Here is one way that might be done.
def binary_search(array, n)
# remove from production code lines marked -> #******
_bin_srch_iters = 0 #******
begin #******
min = 0
max = (array.length) - 1
loop do
_bin_srch_iters += 1 #******
middle = (min + max) / 2
break middle if array[middle] == n
break nil if min == max
if array[middle] > n
max = middle - 1
else # array[middle] < n
min = middle + 1
end
end
ensure #******
puts "binary_search reqd #{_bin_srch_iters} interations" #******
end #******
end
x = binary_search([1,3,6,7,9,11], 3)
# binary_search reqd 3 interations
#=> 1
binary_search([1,3,6,7,9,11], 5)
# binary_search reqd 3 interations
#=> nil

I have a numeric value like 30.6355 that represents money, how to round to 2 decimal places?

I have a numeric value like 30.6355 that represents money, how to round to 2 decimal places?
You should not use double or float types when dealing with currency: they have both too many decimal places and occasional rounding errors. Money can fall through those holes and it'll be tough to track down the errors after it happens.
When dealing with money, use a fixed decimal type. In Ruby (and Java), use BigDecimal.
Ruby 1.8:
class Numeric
def round_to( places )
power = 10.0**places
(self * power).round / power
end
end
(30.6355).round_to(2)
Ruby 1.9:
(30.6355).round(2)
In 1.9, round can round to a specified number of digits.
This will round for some useful cases - not well written but it works! Feel free to edit.
def round(numberString)
numberString = numberString.to_s
decimalLocation = numberString.index(".")
numbersAfterDecimal = numberString.slice(decimalLocation+1,numberString.length-1)
numbersBeforeAndIncludingDeciaml = numberString.slice(0,decimalLocation+1)
if numbersAfterDecimal.length <= 2
return numberString.to_f
end
thingArray = numberString.split("")
thingArray.pop
prior = numbersAfterDecimal[-1].to_i
idx = numbersAfterDecimal.length-2
thingArray.reverse_each do |numStr|
if prior >= 5
numbersAfterDecimal[idx] = (numStr.to_i + 1).to_s unless (idx == 1 && numStr.to_i == 9)
prior = (numStr.to_i + 1)
else
prior = numStr.to_i
end
break if (idx == 1)
idx -= 1
end
resp = numbersBeforeAndIncludingDeciaml + numbersAfterDecimal[0..1]
resp.to_f
end
round(18.00) == 18.0
round(18.99) == 18.99
round(17.9555555555) == 17.96
round(17.944444444445) == 17.95
round(15.545) == 15.55
round(15.55) == 15.55
round(15.555) == 15.56
round(1.18) == 1.18
round(1.189) == 1.19

Loop doesn't terminate upon checking a condition

This loop does not terminate after I type x. I'm really new to Ruby, and so far, it is so much different than what I learned before - quite interesting,
total = 0
i = 0
while ((number = gets) != "x")
total += number.to_i
i += 1
end
puts "\nAverage: " + (total / i).to_s
Any help is greatly appreciated.
Because gets gives you the newline as well. You need to chomp it.
Try:
while ((number = gets.chomp) != "x")
and you'll see it starts working:
pax> ruby testprog.rb
1
5
33
x
Average: 13

ruby - simplify string multiply concatenation

s is a string, This seems very long-winded - how can i simplify this? :
if x === 2
z = s
elsif x === 3
z = s+s
elsif x === 4
z = s+s+s
elsif x === 5
z = s+s+s+s
elsif x === 6
z = s+s+s+s+s
Thanks
Something like this is the simplest and works (as seen on ideone.com):
puts 'Hello' * 3 # HelloHelloHello
s = 'Go'
x = 4
z = s * (x - 1)
puts z # GoGoGo
API links
ruby-doc.org - String: str * integer => new_str
Copy—Returns a new String containing integer copies of the receiver.
"Ho! " * 3 #=> "Ho! Ho! Ho! "
z=''
(x-1).times do
z+=s
end
Pseudo code (not ruby)
if 1 < int(x) < 7 then
z = (x-1)*s
For example for a rating system up to 5 stars you can use something like this:
def rating_to_star(rating)
'star' * rating.to_i + 'empty_star' * (5 - rating.to_i)
end

Ruby max integer

I need to be able to determine a systems maximum integer in Ruby. Anybody know how, or if it's possible?
FIXNUM_MAX = (2**(0.size * 8 -2) -1)
FIXNUM_MIN = -(2**(0.size * 8 -2))
Ruby automatically converts integers to a large integer class when they overflow, so there's (practically) no limit to how big they can be.
If you are looking for the machine's size, i.e. 64- or 32-bit, I found this trick at ruby-forum.com:
machine_bytes = ['foo'].pack('p').size
machine_bits = machine_bytes * 8
machine_max_signed = 2**(machine_bits-1) - 1
machine_max_unsigned = 2**machine_bits - 1
If you are looking for the size of Fixnum objects (integers small enough to store in a single machine word), you can call 0.size to get the number of bytes. I would guess it should be 4 on 32-bit builds, but I can't test that right now. Also, the largest Fixnum is apparently 2**30 - 1 (or 2**62 - 1), because one bit is used to mark it as an integer instead of an object reference.
Reading the friendly manual? Who'd want to do that?
start = Time.now
largest_known_fixnum = 1
smallest_known_bignum = nil
until smallest_known_bignum == largest_known_fixnum + 1
if smallest_known_bignum.nil?
next_number_to_try = largest_known_fixnum * 1000
else
next_number_to_try = (smallest_known_bignum + largest_known_fixnum) / 2 # Geometric mean would be more efficient, but more risky
end
if next_number_to_try <= largest_known_fixnum ||
smallest_known_bignum && next_number_to_try >= smallest_known_bignum
raise "Can't happen case"
end
case next_number_to_try
when Bignum then smallest_known_bignum = next_number_to_try
when Fixnum then largest_known_fixnum = next_number_to_try
else raise "Can't happen case"
end
end
finish = Time.now
puts "The largest fixnum is #{largest_known_fixnum}"
puts "The smallest bignum is #{smallest_known_bignum}"
puts "Calculation took #{finish - start} seconds"
In ruby Fixnums are automatically converted to Bignums.
To find the highest possible Fixnum you could do something like this:
class Fixnum
N_BYTES = [42].pack('i').size
N_BITS = N_BYTES * 8
MAX = 2 ** (N_BITS - 2) - 1
MIN = -MAX - 1
end
p(Fixnum::MAX)
Shamelessly ripped from a ruby-talk discussion. Look there for more details.
There is no maximum since Ruby 2.4, as Bignum and Fixnum got unified into Integer. see Feature #12005
> (2 << 1000).is_a? Fixnum
(irb):322: warning: constant ::Fixnum is deprecated
=> true
> 1.is_a? Bignum
(irb):314: warning: constant ::Bignum is deprecated
=> true
> (2 << 1000).class
=> Integer
There won't be any overflow, what would happen is an out of memory.
as #Jörg W Mittag pointed out: in jruby, fix num size is always 8 bytes long. This code snippet shows the truth:
fmax = ->{
if RUBY_PLATFORM == 'java'
2**63 - 1
else
2**(0.size * 8 - 2) - 1
end
}.call
p fmax.class # Fixnum
fmax = fmax + 1
p fmax.class #Bignum

Resources