1

I have an array:

animals = [
  ["cats", "dogs"],
  ["verrylongcat", "dog"],
  ["shortcat", "verrylongdog"],
  ["cat", "dog"]
]

And I would like to display it nicely. Is there an easy way to make the colums a fixed width so I get something like this:

cats            dogs
verrylongcat    dog
shortcat        verrylongdog
cat             dog

animals is just an example, my array could also have 3, or 4 columns or even more.

2
  • Please clarify what the input array is. Is it an array of text lines, or an array of arrays having 2 strings (words)? Commented May 9, 2016 at 18:23
  • 2
    Why the rush to select an answer? Commented May 9, 2016 at 19:29

3 Answers 3

6

You are looking for String#ljust:

max_cat_size = animals.map(&:first).max_by(&:size).size
animals.each do |cat, dog|
  puts "#{cat.ljust(max_cat_size)} #{dog}"
end

If you want more than one space just add the corresponding amount in the interpolation.


Assuming your array is n × m and not 2 × m:

animal_max_sizes = animals.first.size.times.map do |index|
  animals.transpose[index].map(&:to_s).max_by(&:size).size
end

animals.map do |animal_line|
  animal_line.each.with_index.reduce('') do |animal_line, (animal, index)|
    animal_line + animal.to_s.ljust(animal_max_sizes[index].next)
  end
end.each { |animal_line_stringified| puts animal_line_stringified }

Note: The to_ses are used in case your arrays contain nils, numbers, etc.

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

21 Comments

but what if my array has 3 , or 4 columns or even more ? :)
Then you don't make any assumptions like cat, dog, you treat your input data as an array of arrays of variable size. You'd have to scan the data not only for the max size, but the max number of words (columns), and the max width of each of those columns' words.
so there isn't an easier , more readable way to do this ? :p damnit
@ndn thnx so much <3
@nbn I just copy pasted your code and it worked but for some reason I get the table twice! :P
|
4

Another way to do this is with printf-style formatting. If you know you will always have exactly 2 words in each line then you can do this:

#!/usr/bin/env ruby

lines = [
    '   cats dogs',
  '       verrylongcat dog',
  'shortcat verrylongdog  ',
    ' cat dog      ',
]

lines.map(&:strip).each do |line|
    puts "%-14s%s" % line.split
end

Outputs:

cats          dogs
verrylongcat  dog
shortcat      verrylongdog
cat           dog

If you need to calculate the column width based on the data, then you'd have to do a little more work:

# as @ndn showed:
first_col_width = lines.map(&:split).map(&:first).max_by(&:size).size + 2

lines.map(&:strip).each do |line|
    puts "%-#{first_col_width}s%s" % line.split
end

Comments

2

Here's another attempt for a variable numbers of columns. Given this array:

animals = [
  ['Cats', 'Dogs', 'Fish'],
  ['Mr. Tinkles', 'Buddy', 'Nemo'],
  ['Calico', 'Butch', 'Marlin'],
  ['Ginger', 'Ivy', 'Dory']
]

We can calculate the width of each column via transpose, map, length and max:

widths = animals.transpose.map { |x| x.map(&:length).max }
#=> [11, 5, 6]

Based on this, we can generate a format string that can be passed to sprintf (or its shortcut %):

row_format = widths.map { |w| "%-#{w}s" }.join('   ')
#=> "%-11s   %-5s   %-6s"

%s denotes a string argument, 11, 5 and 6 are our widths and - left-justifies the result.

Let's try it:

row_format % animals[0]  #=> "Cats          Dogs    Fish  "
row_format % animals[1]  #=> "Mr. Tinkles   Buddy   Nemo  "
row_format % animals[2]  #=> "Calico        Butch   Marlin"

That looks good, we should use a loop and wrap everything it in a method:

def print_table(array)
  widths     = array.transpose.map { |x| x.map(&:length).max }
  row_format = widths.map { |w| "%-#{w}s" }.join('   ')

  array.each do |row_values|
    puts row_format % row_values
  end
end

print_table(animals)

Output:

Cats          Dogs    Fish  
Mr. Tinkles   Buddy   Nemo  
Calico        Butch   Marlin
Ginger        Ivy     Dory  

More complex formatting

With a little tweaking, you can also output a MySQL style table:

def print_mysql_table(array)
  widths     = array.transpose.map { |x| x.map(&:length).max }
  row_format = '|%s|' % widths.map { |w| " %-#{w}s " }.join('|')
  separator  = '+%s+' % widths.map { |w| '-' * (w+2) }.join('+')

  header, *rows = array

  puts separator
  puts row_format % header
  puts separator
  rows.each do |row_values|
    puts row_format % row_values
  end
  puts separator
end

print_mysql_table(animals)

Output:

+-------------+-------+--------+
| Cats        | Dogs  | Fish   |
+-------------+-------+--------+
| Mr. Tinkles | Buddy | Nemo   |
| Calico      | Butch | Marlin |
| Ginger      | Ivy   | Dory   |
+-------------+-------+--------+

1 Comment

Nice one, even though if you continue down that path you might as well use a dedicated gem.

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.