Nicely formatting output to console, specifying number of tabs

RubyFormatted

Ruby Problem Overview


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?

Ruby Solutions


Solution 1 - Ruby

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

Solution 2 - Ruby

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.

Solution 3 - Ruby

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

Solution 4 - Ruby

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" 

Solution 5 - Ruby

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"                 },
#]

Solution 6 - Ruby

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

Solution 7 - Ruby

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionuser130532View Question on Stackoverflow
Solution 1 - RubyLars HaugsethView Answer on Stackoverflow
Solution 2 - RubynikView Answer on Stackoverflow
Solution 3 - RubyKyle VanderBeekView Answer on Stackoverflow
Solution 4 - RubysergeychView Answer on Stackoverflow
Solution 5 - RubyNateView Answer on Stackoverflow
Solution 6 - RubyBrett HardinView Answer on Stackoverflow
Solution 7 - RubyThaDonView Answer on Stackoverflow