Reputation: 25
I have to make a method that will add commas to numbers. I cannot use regular expressions in the method. After a lot of tinkering and research, I found this solution, that works:
def separate_comma(number)
c = { :value => "", :length => 0 }
r = number.to_s.reverse.split("").inject(c) do |t, e|
iv, il = t[:value], t[:length]
iv += ',' if il % 3 == 0 && il != 0
{ :value => iv + e, :length => il + 1 }
end
r[:value].reverse!
end
Can someone help me out here and explain the syntax for each line? There's a lot going on here, and I'm having a hard time grasping all of it. Also, any ideas on how to refactor it and make it more elegant-without using regex's. Thanks guys
Upvotes: 0
Views: 143
Reputation: 1749
def separate_comma(number)
c = { :value => "", :length => 0 }
c is a hash with :value = "" and length = 0 (it will be used later)
r = number.to_s.reverse.split("").
the number is converted to a string, reversed and split into digits. Example:
19874 --> ["4","7","8","9","1"]
.inject(c) do |t, e|
Inject "Combines all elements of enum by applying a binary operation, specified by a block or a symbol that names a method or operator"
It transforms a 'memo' using the current array item, resulting in a new memo that is used for the next array item.
http://ruby-doc.org/core-2.2.1/Enumerable.html#method-i-inject
In this case each array item will modify the 'c' hash
iv, il = t[:value], t[:length]
iv initially equals c:value and il equals c:length
iv += ',' if il % 3 == 0 && il != 0
this will add a comma before the current array item if the current count is a multiple of three ( im my example this will only apply to the 4th array item (this will be clear later)
{ :value => iv + e, :length => il + 1 }
the (possibly modified) array item is appended to the string in our t[:value] hash and the :length is incremented by one
end
working through my example
1
iv = ""
il = 0
e = "4"
returns { :value "4", :length 1}
2
iv = "4"
il = 1
e = "7"
returns { :value "47", :length 2}
3
iv = "47"
il = 2
e = "8"
returns { :value "478":, :length 3}
4
iv = "478"
il = 3
e = "9," (the if statement returns true and so a comma is appended to the 9)
returns { :value "478,9", :length 4}
5
iv = "478,9"
il = 4
e = 1
returns { :value "478,91", :length 5}
(this is returned by the inject statement)
r[:value].reverse!
takes the value returned by the inject statement and reverses it's characters. "19,874" end
In terms of making the code easier to understand, you could use these more descriptive variable names:
c = number_hash_memo
r = result
t = number_hash
e = char
iv = hash_val
il = hash_length
Upvotes: 2
Reputation: 6564
this would be easier
here is the code i've written
def comma_seperate(number)
s = number.to_s.reverse.split(//).each_slice(3).to_a
i = 0
while i<s.length-1
s[i].push(",")
i+=1
end
s.join().reverse
end
Upvotes: 0
Reputation: 659
c = { :value => "", :length => 0 }
c is a hash that contains two key-pairs, the first key is value
an empty string, the second is length
which is 0.
The purpose of this line was used to create the initial state of the object entered into the inject method.
number.to_s.reverse.split("")
Will return an array of 'number' in reversed order, for example:
number = 12345
number.to_s.reverse.split("")
=> ["5", "4", "3", "2", "1"]
r = number.to_s.reverse.split("").inject(c) do |t, e|
Enumerable#inject is a fancy method in the ruby community. It's taking in a hash and a block. The initial state of the object is the hash
you input inside the inject method. The 't' in |t,e|
is an object set by the return value of the last line of code in the inject block and its initial state is the hash entered into the inject method. And 'e' in |t,e|
is the element in the array you are iterating over.
iv, il = t[:value], t[:length]
These are values you are setting throughout each iteration.
iv += ',' if il % 3 == 0 && il != 0
Under the condition that the length is 3 and not 0 add a comma. So after every three iteration a comma is added into your value object.
{ :value => iv + e, :length => il + 1 }
The return value of the last line in the Enumerable#inject method is passed to the 't' object.
r[:value].reverse!
The block of code will return 't'. Then finally the value is reversed back to its original state and returned.
Upvotes: 1
Reputation: 12412
What you posted works, but it's a very inefficient solution. Rather than refactoring it, it's better to just rewrite it:
def separate_comma(number)
digits = number.to_s.reverse.split('')
buffer = []
buffer << digits.shift(3).join until digits.empty?
buffer.join(',').reverse
end
This should also be much easier to follow.
Upvotes: 0