tags:

views:

75

answers:

3

I found this snippet online and the purpose is to commify any number including numbers with decimals ... 99999999 => 99,999,999. I can see that it uses regex but I am confused by "$1.reverse, $2"

def commify(n)
  n.to_s =~ /([^\.]*)(\..*)?/
  int, dec = $1.reverse, $2 ? $2 : ""
  while int.gsub!(/(,|\.|^)(\d{3})(\d)/, '\1\2,\3')
  end
  int.reverse + dec
end

Can anyone explain what is going on in this code?

+5  A: 

n.to_s =~ /([^\.]*)(\..*)?/ takes the number as a string and stores everything before the decimal point (or simply everything if there is no decimal point) in $1 and everything after and including it in $2.

int, dec = $1.reverse, $2 ? $2 : "" stores the reverse of $1 in int and $2, or "" if $2 is nil, in dec. In other words int now contains the part before the decimal point reversed and dec contains the part after the point (not reversed).

The next line inserts a comma every three places into int. So by reversing int again we get the original integral part of the number with commas inserted every three places from the end. Now we add dec again at the end and get the original number with commas at the right places.

sepp2k
+2  A: 

$1, $2, $3 ... are Perl legacy. They are capture group variables, that is, they capture the groups inside the regular expression.

A named group is indicated by parentheses. So, the first capture group matches ([^\.]), which is any non dot character, and (\..*) matches a dot character \. and any other characters after it.

Note that the second group is optional, so in the line below you have the ternary expression $2 ? $2 : "", which is a crypty-ish way to get either the value of the capture of a blank string.

The int, dec = $1, $2_or_blank_string is a parallel assignment. Ruby supports assigning more than one variable at once, it's not different than doing int = $1.reversed then dec = $2 So int now holds the integer part (reversed) and dec the decimal part of the number. We are interested in the first one for now.

The next empty while does a string substitution. The method gsub! replaces all occurences of the regular expression for the value in the seconf argument. But it returns nil if no change happened, which ends the while.

The /(,|\.|^)(\d{3})(\d)/ expression matches:

  1. (,|\.|^) A comma, a point or the beginning of the string
  2. (\d{3}) Three digits
  3. (\d) A fourth digit

Then replaces it for \1\2,\3. The \n in a string substitution mean the nth capture group, just as the $n variables do. So, it basically does: if I have four digits, just add a comma after the third one. Repeat until no group of four digits is found

Then, just reverse the integer part again and append the decimal part.

Chubas
+2  A: 

Another way:

class Integer
  def commify
    self.to_s.gsub(/(\d)(?=(\d{3})+$)/,'\1,')
  end
end

Then you can do 12345678.commify and get the string 12,345,678

And here's one that handles floating point numbers:

class Float
  def commify
    self.to_s.reverse.gsub(/(\d\d\d)(?=\d)(?!\d*\.)/,'\1,').reverse
  end
end
Mark Thomas