57

I am generating a script that is outputting information to the console. The information is some kind of statistic with a value. So much like a hash.

So one value's name may be 8 characters long and another is 3. when I am looping through outputting the information with two \t some of the columns aren't aligned correctly.

So for example the output might be as such:

long value name          14
short              12
little             13
tiny               123421
long name again          912421

I want all the values lined up correctly. Right now I am doing this:

puts "#{value_name} - \t\t #{value}"

How could I say for long names, to only use one tab? Or is there another solution?

John Topley
  • 107,187
  • 45
  • 188
  • 235

7 Answers7

57

Provided you know the maximum length to be no more than 20 characters:

printf "%-20s %s\n", value_name, value

If you want to make it more dynamic, something like this should work nicely:

longest_key = data_hash.keys.max_by(&:length)
data_hash.each do |key, value|
  printf "%-#{longest_key.length}s %s\n", key, value
end
Lars Haugseth
  • 13,872
  • 2
  • 40
  • 46
24

There is usually a %10s kind of printf scheme that formats nicely.
However, I have not used ruby at all, so you need to check that.


Yes, there is printf with formatting.
The above example should right align in a space of 10 chars.
You can format based on your widest field in the column.

printf ([port, ]format, arg...)

Prints arguments formatted according to the format like sprintf. If the first argument is the instance of the IO or its subclass, print redirected to that object. the default is the value of $stdout.

Community
  • 1
  • 1
nik
  • 12,528
  • 3
  • 36
  • 53
  • 1
    There is a printf and sprintf that use the C arguments to format a string. They are methods on Kernal (effectively built-in). See http://www.ruby-doc.org/core/classes/Kernel.html#M005962. – Kathy Van Stone Jul 06 '09 at 15:44
  • 3
    remember [printf(*args)](http://apidock.com/ruby/Kernel/printf) has got an [implementation on string#](http://ruby-doc.org/docs/ProgrammingRuby/html/ref_c_string.html#String._pc):%: `"%s %10s" % [value_name, value]` looks great. Anyway, don't deface your code with large terms of this! – abstraktor Oct 13 '11 at 21:39
  • predhme: after all these years, and I didn't know that you could left align with negative values. Thanks for that! +1 – Kalle Apr 24 '13 at 09:38
19

String has a built-in ljust for exactly this:

x = {"foo"=>37, "something long"=>42, "between"=>99}
x.each { |k, v| puts "#{k.ljust(20)} #{v}" }
# Outputs:
#  foo                  37
#  something long       42
#  between              99

Or, if you want tabs, you can do a little math (assuming tab display width of 8) and write a short display function:

def tab_pad(label, tab_stop = 4)
  label_tabs = label.length / 8
  label.ljust(label.length + tab_stop - label_tabs, "\t")
end

x.each { |k, v| puts "#{tab_pad(k)}#{v}" }
# Outputs: 
#  foo                  37
#  something long       42
#  between              99
Kyle VanderBeek
  • 877
  • 8
  • 7
9

There was few bugs in it before, but now you can use most of printf syntax with % operator:

1.9.3-p194 :025 > " %-20s %05d" % ['hello', 12]
 => " hello                00012" 

Of course you can use precalculated width too:

1.9.3-p194 :030 > "%-#{width}s %05x" % ['hello', 12]
  => "hello          0000c" 
sergeych
  • 704
  • 7
  • 9
3

I wrote a thing

  • Automatically detects column widths
  • Spaces with spaces
  • Array of arrays [[],[],...] or array of hashes [{},{},...]
  • Does not detect columns too wide for console window

    lists = [ [ 123, "SDLKFJSLDKFJSLDKFJLSDKJF" ], [ 123456, "ffff" ], ]

array_maxes

def array_maxes(lists)
  lists.reduce([]) do |maxes, list|
    list.each_with_index do |value, index|
      maxes[index] = [(maxes[index] || 0), value.to_s.length].max
    end
    maxes
  end
end

array_maxes(lists)
# => [6, 24]

puts_arrays_columns

def puts_arrays_columns(lists)
  maxes = array_maxes(hashes)
  lists.each do |list|
    list.each_with_index do |value, index|
      print " #{value.to_s.rjust(maxes[index])},"
    end
    puts
  end
end

puts_arrays_columns(lists)

# Output:
#     123, SDLKFJSLDKFJSLDKFJLSDKJF,
#  123456,                     ffff,

and another thing

hashes = [
  { "id" => 123,    "name" => "SDLKFJSLDKFJSLDKFJLSDKJF" },
  { "id" => 123456, "name" => "ffff" },
]

hash_maxes

def hash_maxes(hashes)
  hashes.reduce({}) do |maxes, hash|
    hash.keys.each do |key|
      maxes[key] = [(maxes[key] || 0), key.to_s.length].max
      maxes[key] = [(maxes[key] || 0), hash[key].to_s.length].max
    end
    maxes
  end
end

hash_maxes(hashes)
# => {"id"=>6, "name"=>24}

puts_hashes_columns

def puts_hashes_columns(hashes)
  maxes = hash_maxes(hashes)

  return if hashes.empty?

  # Headers
  hashes.first.each do |key, value|
    print " #{key.to_s.rjust(maxes[key])},"
  end
  puts

  hashes.each do |hash|
    hash.each do |key, value|
      print " #{value.to_s.rjust(maxes[key])},"
    end
    puts
  end

end

puts_hashes_columns(hashes)

# Output:
#      id,                     name,
#     123, SDLKFJSLDKFJSLDKFJLSDKJF,
#  123456,                     ffff,

Edit: Fixes hash keys considered in the length.

hashes = [
  { id: 123,    name: "DLKFJSDLKFJSLDKFJSDF", asdfasdf: :a  },
  { id: 123456, name: "ffff",                 asdfasdf: :ab },
]

hash_maxes(hashes)
# => {:id=>6, :name=>20, :asdfasdf=>8}

Want to whitelist columns columns?

hashes.map{ |h| h.slice(:id, :name) }
# => [
#  { id: 123,    name: "DLKFJSDLKFJSLDKFJSDF" },
#  { id: 123456, name: "ffff"                 },
#]
Nate
  • 11,885
  • 4
  • 52
  • 74
1

For future reference and people who look at this or find it... Use a gem. I suggest https://github.com/wbailey/command_line_reporter

Brett Hardin
  • 3,762
  • 2
  • 16
  • 22
0

You typically don't want to use tabs, you want to use spaces and essentially setup your "columns" your self or else you run into these types of problems.

ThaDon
  • 7,212
  • 9
  • 45
  • 77