7

Is there a easy way to format a given Integer to a String with fixed length and leading zeros?

# convert numbers to strings of fixed length 3 
[1, 12, 123, 1234].map { |e| ??? }
=> ["001", "012", "123", "234"]

I found a solution but maybe there is a smarter way.

format('%03d', e)[-3..-1]
2
  • 2
    [-3..-1] can be replaced with [/...$/]. I find the latter a bit easier to read, but I guess that's subjective. Commented Nov 6, 2015 at 9:25
  • 1
    Using [-3..-1] is faster than using a regex. Commented Nov 6, 2015 at 14:30

5 Answers 5

7

How about getting the last three digits using % 1000 instead of doing string manipulations?

[1, 12, 123, 1234].map { |e| format('%03d', e % 1000) }

Update:

As suggested by the Tin Man in the comments, the original version is better in terms of readability and only abount 1.05x slower than this one, so in most cases it probably makes sense to use that.

Sign up to request clarification or add additional context in comments.

5 Comments

Nice, it looks better than my solution. There is still this kind dependency/duplication between the format string and the divisor.
@sschmeck Right. I feel that duplication is acceptable in this case, but if you were after a more general solution you could create a last_digits function like this.
While using % works, it's not intuitive or the usual way of truncating a string. At a minimum the code needs commenting to give a future maintainer a heads-up what's happening. I'd like to see a benchmark showing whether there is a significant speed up gained by using this, justifying its use.
@theTinMan You're right, the original version was better in this regard. As for performance, I think there's no need to worry about it unless this code is really performance critical. The difference is not big; here's a comparison: pastebin.com/jpsEFb2K
Instead of using off-site resources, it's more useful to include your results in your answer, if not an other answer.
3

Benchmarks:

require 'fruity'

VALUES = (1..9999).to_a.shuffle.take(100)

5.times do
  compare do
    original { VALUES.map { |e| format('%03d', e)[-3..-1] } }
    w0lf     { VALUES.map { |e| format('%03d', e % 1000) } }
    owade    { VALUES.map { |e| e.to_s.rjust(3,'0')[-3..-1]} }
    sdayal   { VALUES.map { |e| ("%03d" % e)[/...$/] } }
  end
  puts
end
# >> Running each test 64 times. Test will take about 1 second.
# >> owade is similar to w0lf
# >> w0lf is faster than original by 39.99999999999999% ± 10.0%
# >> original is faster than sdayal by 2x ± 0.1
# >> 
# >> Running each test 64 times. Test will take about 1 second.
# >> owade is similar to w0lf
# >> w0lf is similar to original
# >> original is faster than sdayal by 2x ± 0.1
# >> 
# >> Running each test 64 times. Test will take about 1 second.
# >> owade is similar to w0lf
# >> w0lf is similar to original
# >> original is faster than sdayal by 2x ± 1.0
# >> 
# >> Running each test 64 times. Test will take about 1 second.
# >> owade is similar to w0lf
# >> w0lf is similar to original
# >> original is faster than sdayal by 2x ± 1.0
# >> 
# >> Running each test 64 times. Test will take about 1 second.
# >> owade is similar to w0lf
# >> w0lf is similar to original
# >> original is faster than sdayal by 2x ± 0.1
# >> 

I used five iterations because the results weren't stabilizing. Possibly that's because there's a backup running on my machine right now, affecting the processing. The takeaway is consistent that using the regex to grab the last three values is not the way to go. While the difference between the various ways of doing it might not be significant for a single array, repeat that process thousands of times and it can add up.

2 Comments

owade can be optimized by 10% (on my PC) by freezing the "0" string. Freezing the '%03d' gains 20%.
I tried several variations using freeze on the fixed string vs. using frozen variables and frozen constants, and they're within the range that Fruity says is inconsequential. I could probably dig out the actual differences using Benchmark, but I think we're talking about very small differences, boarding on background noise. I'll convert the answer to a community-wiki if you want to add more detailed information.
1

You can use String#rjust. It right-pads (right-justifies) a string so that it becomes a given length, using a given padding character.

[1, 12, 123, 1234].map { |e| e.to_s.rjust(3,'0')[-3..-1]}

Comments

0
[1, 12, 123, 1234].map { |e| ("%03d" % e)[/...$/] }

1 Comment

Explain why this is preferable. I'd also benchmark it vs. regular slicing to see if this is sensible speed-wise.
0

You can use the modula division % in combination with the String#%-method. (You don't need the format-command):

[1, 12, 123, 1234].map { |e| "%03d" % (e % 1000)}

With some marker to explain the different %:

[1, 12, 123, 1234].map { |e| "%03d" % (e % 1000)}
#                            (1)   (2)   (3)
  • (1): format specification, 3 numerals with leading zero.
  • (2): String#%-method
  • (3): Modula division for integers

In your comment in w0lf's answer you mention: There is still this kind dependency/duplication between the format string and the divisor.

You can define the number of digits in a variable and use it in the format string and the divisor:

digits = 3
[1, 12, 123, 1234].map { |e| "%0*d" % [digits,e % 10**digits]}

1 Comment

Using both e % 1000 and e % 10**digits obfuscates the actual processing that's happening. I understand them, but then I come from an assembly-language/machine-language background through C and various other languages, but a lot of people don't know what modulo AKA % does, so using "%03d" % (e % 1000) or "%0*d" % [digits,e % 10**digits] will be bewildering with the multiple ways % is used. At that point, commenting in the code would be necessary to make sure future mods don't add bugs instead of remove them.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.