VictorO
VictorO

Reputation: 147

Ruby: How to add zeros to front of a number string using sprintf()

I want to normalize zipcodes to be 5 digits long with zeros replacing any missing characters like so:

"95616" >> "95616"
"854"  >> "00854"
"062" >> "00062"
"0016" >> "00016"

I have tried using sprintf like so sprintf("%05s", zipcode) and like so sprintf("%0.5d", zipcode). Both give incorrect answers. Using the s:

"95616" >> "95616"
"854"   >> "  854"
"062"   >> "  062"
"0016"  >> " 0016"

This is the correct number of characters, but using spaces, not zeros.

Using the d:

"95616" >> "95616"
"854"   >> "00854"
"062"   >> "00050"
"0016"  >> "00014"

What is the proper use of sprintf() in this case?

Upvotes: 11

Views: 9698

Answers (6)

BernardK
BernardK

Reputation: 3734

whirlwin's solution seems to me a valid (general padding) solution. Just improve it with max :

zipcode.insert 0, '0' * ([5 - zipcode.length, 0].max)

To check it :

az = ["95616", "854", "062", "0016", '1', '123456']
az.collect {|zipcode| zipcode.insert 0, '0' * (5 - zipcode.length) }

$ ruby -w t.rb
t.rb:2:in `*': negative argument (ArgumentError)

Add max to cope with negative values :

az = ["95616", "854", "062", "0016", '1', '123456']
az.collect {|zipcode| zipcode.insert 0, '0' * ([5 - zipcode.length, 0].max) }
p az

$ ruby -w t.rb
["95616", "00854", "00062", "00016", "00001", "123456"]

Upvotes: 2

VictorO
VictorO

Reputation: 147

Thank you for your input everyone. I was looking to learn more about sprintf and specifically to use sprintf in the solution, and all the ideas were really helpful. I eventually figured out a bit of a hacky solution to my problem that seems to be flexible and that hasn't been mentioned yet, so I thought I would post it.

As I mentioned, sprintf("%05s", zipcode) would return spaces instead of zeros if a number string was not long enough, but it did return the correct number of spaces -- so I just chained a gsub to the end to replace the spaces with zeros.

sprintf("%05s", zipcode).gsub!(" ", "0")

Please let me know if there are any significant problems with this solution. I have tested it and so far it seems to work.

Upvotes: 0

leemeichin
leemeichin

Reputation: 3379

Your usage of sprintf is fine. Your problem is that 062 (and 0016) is octal (as is any integer that starts with a 0), and it becomes 50 when converted to base10.

The solution is to get rid of that 0 before it hits your Ruby app. Presuming that it's a string (because your examples show strings), you can do something like this:

"062".gsub /^0/, ''

And then carry on with the padding and print formatting.

The other way is to knowingly print it as an octal if you know it starts with a 0:

"%05o" % 062 # => "00062"

Of course, if you have control over the input, your best bet is to ensure people can't break your code by inputting numbers you don't expect. eg.

"%05s" % 0xff0055 # => 16711765
"%05x" % 0xff0055 # => "ff0055"

Checking inputs changes the problem from being a formatting one to a validation one, and it's better to prevent than treat.

(the quirky % syntax is sugar for printf/sprintf)

Upvotes: 8

pguardiario
pguardiario

Reputation: 54984

You want %05d. Also String#% as a shortcut:

"%05d" % "123" #=> 00123

Upvotes: 6

steenslag
steenslag

Reputation: 80065

Don't torture yourself with sprintf:

puts "123".rjust(5, '0') # => 00123

Upvotes: 27

whirlwin
whirlwin

Reputation: 16521

While this approach does in fact not use sprintf, it should solve your problem in 1 line of code:

zipcode.insert 0, '0' * (5 - zipcode.length)

Upvotes: 0

Related Questions