The Artima Developer Community
Sponsored Link

Ruby Buzz Forum
Dynamic String#sprintf width

0 replies on 1 page.

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 0 replies on 1 page
Eric Hodel

Posts: 660
Nickname: drbrain
Registered: Mar, 2006

Eric Hodel is a long-time Rubyist and co-founder of Seattle.rb.
Dynamic String#sprintf width Posted: Sep 9, 2010 4:15 PM
Reply to this message Reply

This post originated from an RSS feed registered with Ruby Buzz by Eric Hodel.
Original Post: Dynamic String#sprintf width
Feed Title: Segment7
Feed URL: http://blog.segment7.net/articles.rss
Feed Description: Posts about and around Ruby, MetaRuby, ruby2c, ZenTest and work at The Robot Co-op.
Latest Ruby Buzz Posts
Latest Ruby Buzz Posts by Eric Hodel
Latest Posts From Segment7

Advertisement

When outputting columns of data it's nice to have them all line up prettily. String#sprintf (I prefer it's alias String#%) is the best tool for this!

Here's a list of pets that we'd like to output prettily:

data = [
  ['cats',         5],
  ['dogs',        10],
  ['giraffes', 1_000],
]

We'd like the output to look much like this nicely-formatted Array. Here's a basic sprintf format string that will print this out:

puts "pet amount"
data.each do |record|
  puts "%s %d" % record
end

Which, when run, looks like:

pet amount
cats 5
dogs 10
giraffes 1000

Not very pretty ☹. Fortunately sprintf knows about field widths. We can add some to the format string and space out our header a bit while we're at it:

puts "pet      amount"
data.each do |record|
  puts "%8s %4d" % record
end

So we now have:

pet      amount
    cats    5
    dogs   10
giraffes 1000

The names would be a bit easier to read if the were flush-left. A negative width will take care of that:

puts "pet      amount"
data.each do |record|
  puts "%-8s %4d" % record
end

Now, let's buy another pet!

data << ['crocodiles', 10_001]

Not so nice, we've come out of alignment ☹.

pet      amount
cats        5
dogs       10
giraffes 1000
crocodiles 10001

I know what you're thinking now! Let's calculate the width of each field and add it to the format string! Ok:

max_name_length = data.map { |n,| n.length }.max
max_count_length = data.map { |_,c| c }.max.to_s.length

puts "%-#{max_name_length}s %s" % ['Pet name', 'Amount']
data.each do |record|
  puts "%-#{max_name_length}s %#{max_count_length}d" % record
end

While I was at it I made the header line up better too. The output is much better:

Pet name   Amount
cats           5
dogs          10
giraffes    1000
crocodiles 10001

The format strings are a bit ugly now. The interpolation inside the format string is unreadable. Fortunately sprintf also supports retrieving widths from the data. The * flag for a field has sprintf use the next argument as the field width:

puts "%-*s %s" % [max_name_length, 'Pet name', 'Amount']
data.each do |name, count|
  puts "%-*s %*d" % [max_name_length, name, max_count_length, count]
end

Which has the same output as the interpolated version:

Pet name   Amount
cats           5
dogs          10
giraffes    1000
crocodiles 10001

If you want to get super fancy you can use the [digit]$ flag to specify the position of the field data and width in the record:

widths = [max_name_length, max_count_length]

puts "%-*s %s" % [max_name_length, 'Pet name', 'Amount']
data.each do |record|
  puts "%1$-*3$s %s %2$*4$d" % (record + widths)
end

This is a bit confusing to read though. A record for the above format string would look like ['cats', 5, 10, 5] but the values are accessed from the record in the opposite order from relative positions.

With relative positions first the field width is accessed then the data to format. With absolute positions first the data to format is accessed then the field width.

The downside of the [digit]$ flag is that every argument must be absolutely specified. Adding a relatively positioned field to such a format string results in the error in `%': numbered(1) after unnumbered(1) (ArgumentError) or in `%': unnumbered(1) mixed with numbered (ArgumentError)

Read: Dynamic String#sprintf width

Topic: Ruby Gurus to blog on RubyLearning Previous Topic   Next Topic Topic: The Musical Intruments Museum

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use