Question

I'm trying to build a check-digit calculation in Ruby for FedEx tracking numbers.

Here is info and the steps for the check-digit calculation:

  • Digit positions are labeled from right to left.
  • Digit 1 is the check character.
  • Digits 16 through 22 are not used.

Steps:

  1. Starting from position 2, add up the values of the even numbered positions.
  2. Multiply the results of step one by three.
  3. Starting from position 3, add up the values of the odd numbered positions. Remember – position 1 is the check digit you are trying to calculate.
  4. Add the result of step two to the result of step three.
  5. Determine the smallest number which when added to the number from step four results in a multiple of 10. This is the check digit.

Here is an example of the process (provided by FedEx): enter image description here

So, how do I implement this in Ruby?

Was it helpful?

Solution

When you have your number as string (or if you have your digit as integer, just #to_s on it and get string), and then you can simply extract digits from there with:

number_string[idx].to_i

or if you use Ruby 1.8

number_string[idx..idx].to_i

#to_i is to convert it to integer, so you can add it to others. Then just proceed with steps provided to calculate your number.

All you have to do to implement it is correctly map positions provided in instruction to idx index position in your string representation of number. Just do it on paper with counting in head or use negative idx (it counts from end of the string) in Ruby.

EDIT:

The solution could be something like this:

bar_code_data = "961102098765431234567C"
digits_with_position = bar_code_data.reverse[1..14].split(//).map(&:to_i).zip(2..1/0.0)

this goes as follow:

  • reverse - reverse string, so now we can count from left to right instead of reverse
  • [1..14] - select substrig of characters, which we're interested in (Ruby counts from 0)
  • split(//) - split one string into substrings of length 1 character, in other words - separate digits
  • map(&:to_i) - call #to_i on every element of array, in other words convert to integer
  • zip(2..1/0.0) - add position starting from 2 to Infinity, to every element

Now we should have something like this:

[[7, 2], [6, 3], [5, 4], [4, 5], [3, 6], [2, 7], [1, 8], [3, 9], [4, 10], [5, 11], [6, 12], [7, 13], [8, 14], [9, 15]]

sum = digits_with_position.map{|i| i[0] * (i[1].even? ? 3 : 1)}.reduce(+:)

We made little change in algorithm, which should not be hard to you to follow:

instead of:

sum = (in[2] + in[4] + in[6] + ...)*3 + (in[3] + in[5] + in[7] + ...)

we made:

sum = in[2]*3 + in[3]*1 + in[4]*3 + in[5]*1 + in[6]*3 + in[7]*1 + ...

which is the same result, but with changed order of operations.

Also:

  • map {|i| ... } - map every value of list, i is tuple in our case, pair of [digit,pos]
  • i[1].even? - check if position is even
  • i[1].even? ? 3 : 1 - for even position use 3, for opposite (odd) use just 1
  • reduce(:+) - reduce resulting array to single value using + operation (add all results)

Now fun part :-)

check_code = 10 - (sum % 10)
  • sum % 10 - module 10 of sum value, return reminder of division sum by 10, which in our case is last digit
  • 10 - (sum % 10) - complement to nearest not smaller multiple of 10

There is error in description, because if you would have 130 as result, then next bigger multiple of 10 is 140 and difference is 10, which is not correct result for digit (it should probably be 0).

Other faster solution would be like this (unroll all loops, just hardcode everything):

d = "961102098765431234567C".split(//) # avoid having to use [-2..-2] in Ruby 1.8
sum_even = d[-2].to_i + d[-4].to_i + d[-6].to_i + d[-8].to_i + d[-10].to_i + d[-12].to_i + d[-14].to_i
sum_odd = d[-3].to_i + d[-5].to_i + d[-7].to_i + d[-9].to_i + d[-11].to_i + d[-13].to_i + d[-15].to_i
sum = sum_even * 3 + sum_odd
check_code = 10 - sum % 10

It's just dead simple solution, not worth explaining, unless someone asks for it

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top