Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a Ruby method to grab the ones/tenths/hundredths place for an integer?

Tags:

ruby

I'm doing a Ruby kata that asks me to find the sum of the digits of all the numbers from 1 to N (both ends included).

So if I had these inputs, I would get these outputs:

For N = 10 the sum is 1+2+3+4+5+6+7+8+9+(1+0) = 46

For N = 11 the sum is 1+2+3+4+5+6+7+8+9+(1+0)+(1+1) = 48

For N = 12 the sum is 1+2+3+4+5+6+7+8+9+(1+0)+(1+1) +(1+2)= 51

Now I know in my head what needs to be done. Below is the code that I have to solve this problem:

def solution(n)
  if n <= 9
    return n if n == 1
    solution(n-1) + n
  elsif n >= 10
    45 + (10..n) #How can I grab the ones,tenths, and hundreds?
  end
end

Basically everything is fine until I hit over 10.

I'm trying to find some sort of method that could do this. I searched Fixnum and Integer but I haven't found anything that could help me. I want is to find something like "string"[0] but of course without having to turn the integer back in forth between a string and integer. I know that there is a mathematical relationship there but I'm having a hard time trying to decipher that.

Any help would be appreciated.

like image 371
Dan Rubio Avatar asked Jun 13 '14 15:06

Dan Rubio


3 Answers

You can use modulo and integer division to calculate it recursively:

def sum_digits(n)
  return n if n < 10
  (n % 10) + sum_digits(n / 10)
end

sum_digits(123)
# => 6
like image 196
Uri Agassi Avatar answered Oct 23 '22 02:10

Uri Agassi


A beginner would probably do this:

123.to_s.chars.map(&:to_i)
# => [1, 2, 3]

but a more thoughtful person would do this:

n, a = 123, []
until n.zero?
  n, r = n.divmod(10)
  a.unshift(r)
end
a
# => [1, 2, 3]
like image 35
sawa Avatar answered Oct 23 '22 03:10

sawa


Rather than computing the sum of the digits for each number in the range, and then summing those subtotals, I have computed the total using combinatorial methods. As such, it is much more efficient than straight enumeration.

Code

SUM_ONES = 10.times.with_object([]) { |i,a| a << i*(i+1)/2 }
S = SUM_ONES[9]

def sum_digits_nbrs_up_to(n)
  pwr = n.to_s.size - 1
  tot = n.to_s.chars.map(&:to_i).reduce(:+)
  sum_leading_digits = 0
  pwr.downto(0).each do |p|
    pwr_term = 10**p
    leading_digit = n/pwr_term
    range_size = leading_digit * pwr_term
    tot += sum_leading_digits * range_size +
           sum_digits_to_pwr(leading_digit, p)
    sum_leading_digits += leading_digit
    n -= range_size
  end
  tot
end  

def sum_digits_to_pwr(d, p)
  case
  when d.zero? && p.zero?
    0
  when d.zero?
    10**(p-1) * S * d * p
  when p.zero?
    10**p * SUM_ONES[d-1]
  else
    10**p * SUM_ONES[d-1] + 10**(p-1) * S * d * p
  end
end

Examples

sum_digits_nbrs_up_to(456)   #=>   4809
sum_digits_nbrs_up_to(2345)  #=>  32109
sum_digits_nbrs_up_to(43021) #=> 835759
sum_digits_nbrs_up_to(65827359463206357924639357824065821)
                             #=> 10243650329265398180347270847360769369

These calculations were all essentially instantaneous. I verified the totals for the first three examples by straight enumeration, using @sawa's method for calculating the sum of digits for each number in the range.

Explanation

The algorithm can best be explained with an example. Suppose n equals 2345.

We begin by defining the following functions:

  • t(n) : sum of all digits of all numbers between 1 and n, inclusive (the answer)
  • sum(d): sum of all digits between 1 and d, inclusive, (for d=1..9, sum(d) = 0, 1, 3, 6, 10, 15, 21, 28, 36, 45).
  • g(i) : sum of digits of the number i.
  • f(i,j): sum of all digits of all integers between i and j-1, inclusive.
  • g(m) : sum of digits of the number m.
  • h(d,p): sum of all digits of all numbers between 0 and d*(10^p)-1 (derived below).

Then (I explain the following below):

t(2345) = f(0-1999)+f(2000-2299)+f(2300-2339)+f(2340-2344)+g(2345)

f(   0-1999) =                           h(2,3) =       h(2,3)
f(2000-2299) = 2       * (2299-2000+1) + h(3,2) = 600 + h(3,2)
f(2300-2339) = (2+3)   * (2339-2300+1) + h(4,1) = 200 + h(4,1)
f(2340-2344) = (2+3+4) * (2344-2340+1) + h(5,0) =  45 + h(5,0)
g(2345)      = 2+3+4+5                          =  14

so

t(2345) = 859 + h(2,3) + h(3,2) + h(4,1) + h(5,0)

First consider f(2000-2299). The first digit, 2, appears in every number in the range (2000..2299); i.e., 300 times. The remaining three digits contribute (by definition) h(3,2) to the total:

f(2000-2299) = 2 * 300 + h(3,2)

For f(2300-2339) the first two digits, 2 and 3, are present in all 40 numbers in the range (2300..2339) and the remaining two digits contribute h(4,1) to the total:

f(2300-2339) = 5 * 40 + h(4,1)

For f(2340-2344), the first three digits, '2,3and4, are present in all four number in the range ``(2340-2344) and the last digit contributes h(5,0) to the total.

It remains to derive an expression for computing h(d,p). Again, this is best explained with an example.

Consider h(3,2), which is the sum of the all digits of all numbers between 0 and 299.

First consider the sum of digits for the first digit. 0, 1 and 2 are each the first digit for 100 numbers in the range 0-299. Hence, the first digit, summed, contributes

0*100 + 1*100 + 2*100 = sum(2) * 10^2

to the total. We now add the sum of digits for the remaining 2 digits. The 300 numbers each have 2 digits in the last two positions. Each of the digits 0-9 appears in 1/10th of 2 * 300 = 600 digits; i.e, 60 times. Hence, the sum of all digits in last 2 digit positions, over all 300 numbers, equals:

sum(9) * 2 * 300 / 10 = 45 * 2 * 30 = 2700.

More generally,

h(d,p) = sum(d-1) * 10**p + sum(9) * d * p * 10**(p-1) if d >  0 and p >  0
       = sum(d-1) * 10**p                              if d >  0 and p == 0
       =                    sum(9) * d * p * 10**(p-1) if d == 0 and p >  0
       = 0                                             if d == 0 and p == 0

Applying this to the above example, we have

h(2,3) = sum(1) * 10**3 + (45 * 2 * 3) * 10**2 =  1 * 1000 + 270 * 100 = 28000
h(3,2) = sum(2) * 10**2 + (45 * 3 * 2) * 10**1 =  3 *  100 + 270 *  10 =  3000
h(4,1) = sum(3) * 10**1 + (45 * 4 * 1) * 10**0 =  6 *   10 + 180 *   1 =   240
h(5,0) = sum(4) * 10**0                        = 10 *    1             =    10

Therefore

t(2345) = 859 + 28000 + 3000 + 240 + 10 = 32109

The code above implements this algorithm in a straightforward way.

I confirmed the results for the first three examples above by using using @sawa's code to determine the sum of the digits for each number in the range and then summed those totals:

def sum_digits(n)
  a = []
  until n.zero?
    n, r = n.divmod(10)
    a.unshift(r)
  end
  a.reduce(:+)
end

def check_sum_digits_nbrs_up_to(n)
  (1..n).reduce(0) {|t,i| t + sum_digits(i) }
end

check_sum_digits_nbrs_up_to(2345) #=> 32109
like image 33
Cary Swoveland Avatar answered Oct 23 '22 03:10

Cary Swoveland