How to split a String in a multidimensional Array in ruby? - ruby

I have an multidimensional Array in Ruby, like this:
[["2013-08-22 13:23:12 +0212", 100, 1], ["2013-09-22 14:25:12 +0123" , 123, 1]]
I would like to split the string in the first array position and the time to hours, minutes and seconds (and convert these to integers), so it will become:
[["2013-08-22", 13, 23, 12, "+0212", 100, 1], [.....]]
Does anyone know how to solve this problem?

If you would like more readability, I suggest this:
require 'date'
array = [["2013-08-22 13:23:12 +0212", 100, 1], ["2013-09-22 14:25:12 +0123" , 123, 1]]
array.map do |date_time, a, b|
date_time = DateTime.parse(date_time)
[
date_time.strftime('%F'), date_time.hour, date_time.min,
date_time.sec, date_time.strftime('%z'), a, b
]
end
It makes it very clear what each element of the resulting array is. You should replace a and b with meaningful names if you have them.

[["2013-08-22 13:23:12 +0212", 100, 1],
["2013-09-22 14:25:12 +0123" , 123, 1]].
map do |arr|
arr.shift.split(/[: ]/) + arr # first position only
# ⇓ this will convert hours, minutes and seconds to integers
# arr.shift.split(/[: ]/).map { |e| e[/\A\d+\z/] ? e.to_i : e } + arr
# ⇓ this would work for all strings
# arr.flat_map { |e| e.is_a?(String) ? e.split(/[: ]/) : e }
end
#⇒ [
# [0] [
# [0] "2013-08-22",
# [1] "13",
# [2] "23",
# [3] "12",
# [4] "+0212",
# [5] 100,
# [6] 1
# ],
# [1] [
# [0] "2013-09-22",
# [1] "14",
# [2] "25",
# [3] "12",
# [4] "+0123",
# [5] 123,
# [6] 1
# ]
# ]

Here's another option (converting hours, minutes and seconds to integers):
arr = [["2013-08-22 13:23:12 +0212", 100, 1], ["2013-09-22 14:25:12 +0123" , 123, 1]]
arr.map do |item|
date, time, zone = item[0].split.map { |e| e.split(":") }
[date, time.map(&:to_i), zone, item[1..-1]].flatten
end
#=> [["2013-08-22", 13, 23, 12, "+0212", 100, 1], ["2013-09-22", 14, 25, 12, "+0123", 123, 1]]

Non-destructive way. ref #mudasobwa answer
arr = [["2013-08-22 13:23:12 +0212", 100, 1], ["2013-09-22 14:25:12 +0123" , 123, 1]]
arr.map {|e| e[0].split(/[ :]/) + e[1..-1] }
#=> [["2013-08-22", "13", "23", "12", "+0212", 100, 1], ["2013-09-22", "14", "25", "12", "+0123", 123, 1]]

Related

Sort hash by key which is a string

Assuming I get back a string:
"27,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,12,17,17,41,17,17,17,17,17,17,17,17,17,17,17,17,17,26,26,26,26,26,26,26,26,26,29,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,40,48,28,28,28,28,28,28,28,28,28,28,28,28,28,28,29,29,29,29,29,29,29,29,29,29,29,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,34,34,34,34,34,34,36,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,40,40,40,40,40,40,40,40,41,41,41,41,41,41,41,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,43,43,43,43,43,43,43,43,43,43,43,43,43,44,44,44,44,48,49,29,41,6,30,11,29,29,36,29,29,36,29,43,1,29,29,29,1,41"
I turn that into an array by calling
str.split(',')
Then turning it into a hash by calling
arr.compact.inject(Hash.new(0)) { |h, e| h[e] += 1 ; h }
I would get back a hash that looks like
{"1"=>2, "6"=>1, "39"=>23, "36"=>23, "34"=>39, "32"=>31, "30"=>18, "3"=>8, "2"=>10, "28"=>36, "29"=>21, "26"=>41, "27"=>48, "49"=>1, "44"=>4, "43"=>14, "42"=>34, "48"=>2, "40"=>9, "41"=>10, "11"=>1, "17"=>15, "12"=>1}
However, I'd like to sort that hash by key.
I've tried the solutions listed here.
I believe my problem is related to the fact they keys are strings.
The closest I got was using
Hash[h.sort_by{|k,v| k.to_i}]
Hashes shouldn't be treated as a sorted data structure. They have other advantages and use case as to return their values sequentially. As Mladen Jablanović already pointed out a array of tuples might be the better data structure when you need a sorted key/value pair.
But in current versions of Ruby there actually exists a certain order in which key/value pairs are returned when you call for example each on a hash and that is the order of insertion. Using this behavior you can just build a new hash and insert all key/value pairs into that new hash in the order you want them to be. But keep in mind that the order will break when you add more entries later on.
string = "27,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,12,17,17,41,17,17,17,17,17,17,17,17,17,17,17,17,17,26,26,26,26,26,26,26,26,26,29,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,40,48,28,28,28,28,28,28,28,28,28,28,28,28,28,28,29,29,29,29,29,29,29,29,29,29,29,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,34,34,34,34,34,34,36,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,34,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,40,40,40,40,40,40,40,40,41,41,41,41,41,41,41,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,43,43,43,43,43,43,43,43,43,43,43,43,43,44,44,44,44,48,49,29,41,6,30,11,29,29,36,29,29,36,29,43,1,29,29,29,1,41"
sorted_number_count_tupels = string.split(',').
group_by(&:itself).
map { |k, v| [k, v.size] }.
sort_by { |(k, v)| k.to_i }
#=> [["1",2],["2",10],["3",8],["6",1],["11",1],["12",1],["17",15],["26",41],["27",48],["28",36],["29",21],["30",18],["32",31],["34",39],["36",23],["39",23],["40",9],["41",10],["42",34],["43",14],["44",4],["48",2],["49",1]]
sorted_number_count_hash = sorted_number_count_tupels.to_h
#=> { "1" => 2, "2" => 10, "3" => 8, "6" => 1, "11" => 1, "12" => 1, "17" => 15, "26" => 41, "27" => 48, "28" => 36, "29" => 21, "30" => 18, "32" => 31, "34" => 39, "36" => 23, "39" => 23, "40" => 9, "41" => 10, "42" => 34, "43" => 14, "44" => 4, "48" => 2, "49" => 1}
Suppose you started with
str = "27,2,2,2,41,26,26,26,48,48,41,6,11,1,41"
and created the following hash
h = str.split(',').inject(Hash.new(0)) { |h, e| h[e] += 1 ; h }
#=> {"27"=>1, "2"=>3, "41"=>3, "26"=>3, "48"=>2, "6"=>1, "11"=>1, "1"=>1}
I removed compact because the array str.split(',') contains only (possibly empty) strings, no nils.
Before continuing, you may want to change this last step to
h = str.split(/\s*,\s*/).each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
#=> {"27"=>1, "2"=>3, "41"=>3, "26"=>3, "48"=>2, "6"=>1, "11"=>1, "1"=>1}
Splitting on the regex allows for the possibility of one or more spaces before or after each comma, and Enumerable#each_with_object avoids the need for that pesky ; h. (Notice the block variables are reversed.)
Then
h.sort_by { |k,_| k.to_i }.to_h
#=> {"1"=>1, "2"=>3, "6"=>1, "11"=>1, "26"=>3, "27"=>1, "41"=>3, "48"=>2}
creates a new hash that contains h's key-value pairs sorted by the integer representations of the keys. See Hash#sort_by.
Notice we've created two hashes. Here's a way to do that by modifying h in place.
h.keys.sort_by(&:to_i).each { |k| h[k] = h.delete(k) }
#=> ["1", "2", "6", "11", "26", "27", "41", "48"] (each always returns the receiver)
h #=> {"1"=>1, "2"=>3, "6"=>1, "11"=>1, "26"=>3, "27"=>1, "41"=>3, "48"=>2}
Lastly, another alternative is to sort str.split(',') before creating the hash.
str.split(',').sort_by(&:to_i).each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
#=> {"1"=>1, "2"=>3, "6"=>1, "11"=>1, "26"=>3, "27"=>1, "41"=>3, "48"=>2}
Notes
compact
String#split cannot return a nil element. compact won't be useful, here. split might return an empty string, though :
p "1,,2,3".split(',')
# ["1", "", "2", "3"]
p "1,,2,3".split(',').compact
# ["1", "", "2", "3"]
p "1,,2,3".split(',').reject(&:empty?)
# ["1", "2", "3"]
inject
If you have to use two statements inside inject block, each_with_object might be a better idea :
arr.compact.inject(Hash.new(0)) { |h, e| h[e] += 1 ; h }
can be rewritten :
arr.compact.each_with_object(Hash.new(0)) { |e, h| h[e] += 1 }
Hash or Array?
If you need to sort results, an Array of pairs might be more suitable than a Hash.
String or Integer?
If you accept to have an integer as key, it might make your code easier to write.
Refactoring
Here's a possibility to rewrite your code :
str.split(',')
.reject(&:empty?)
.map(&:to_i)
.group_by(&:itself)
.map { |k, v| [k, v.size] }
.sort
It outputs :
[[1, 2], [2, 10], [3, 8], [6, 1], [11, 1], [12, 1], [17, 15], [26, 41], [27, 48], [28, 36], [29, 21], [30, 18], [32, 31], [34, 39], [36, 23], [39, 23], [40, 9], [41, 10], [42, 34], [43, 14], [44, 4], [48, 2], [49, 1]]
If you really want a Hash, you can add .to_h :
{1=>2, 2=>10, 3=>8, 6=>1, 11=>1, 12=>1, 17=>15, 26=>41, 27=>48, 28=>36, 29=>21, 30=>18, 32=>31, 34=>39, 36=>23, 39=>23, 40=>9, 41=>10, 42=>34, 43=>14, 44=>4, 48=>2, 49=>1}
You can assign the arr.compact.inject(Hash.new(0)) { |h, e| h[e] += 1 ; h } to a variable and sort it by key:
num = arr.compact.inject(Hash.new(0)) { |h, e| h[e] += 1 ; h }
num.keys.sort
That would sort the hash by key.
A Ruby hash will keep the order of keys added. If the array is small enough to sort I would just change
str.split(',').
to
str.split(',').sort_by(&:to_i)
in order to get the values, and therefore also you hash sorted...

Make the first row as the keys for hash for the next rows?

I am having a hard time figuring out how to make the next rows a hash with the key from the first row.
I have an array structured like this:
[["id", "name", "address"], [1, "James", "...."], [2, "John", "...."] ]
To be:
[{ id : 1, name: "James", address: "..."}, ...]
I used a gem "simple_xlsx_reader", I am extracting out only the first sheet.
wb.sheets.first.row
and got a similar array output from above.
thanks!
arr = [["id", "name"], [1, "Jack"], [2, "Jill"]]
[arr.first].product(arr.drop 1).map { |a| a.transpose.to_h }
#=> [{"id"=>1, "name"=>"Jack"}, {"id"=>2, "name"=>"Jill"}]
The steps:
b = [arr.first]
#=> [["id", "name"]]
c = arr.drop 1
#=> [[1, "Jack"], [2, "Jill"]]
d = b.product(c)
#=> [[["id", "name"], [1, "Jack"]], [["id", "name"], [2, "Jill"]]]
d.map { |a| a.transpose.to_h }
#=> [{"id"=>1, "name"=>"Jack"}, {"id"=>2, "name"=>"Jill"}]
The first element of d passed to map's block is:
a = d.first
[["id", "name"], [1, "Jack"]]
The block calculation is therefore:
e = a.transpose
#=> [["id", 1], ["name", "Jack"]]
e.to_h
#=> {"id"=>1, "name"=>"Jack"}
This is what you're looking for:
arr = [["id", "name", "address"], [1, "James", "...."], [2, "John", "...."] ]
keys, *values = arr
values.map {|vals| keys.zip(vals).to_h }
Enumerable#zip takes two arrays (the receiver and the argument) and "zips" them together, producing an array of tuples (two-element arrays) e.g.:
keys = [ "foo", "bar", "baz" ]
values = [ 1, 2, 3 ]
p keys.zip(values)
# => [ [ "foo", 1 ], [ "bar", 2 ], [ "baz", 3 ] ]
Array#to_h takes an array of tuples and turns it into a hash.
If you're using a version of Ruby earlier than 2.1 you'll need to use Hash[ *keys.zip(vals) ] instead.
P.S. If you want symbol keys instead of string keys you'll want to perform that conversion before the map, e.g.:
keys = keys.map(&:to_sym)
Or, if you don't mind modifying the original array:
keys.map!(&:to_sym)
You can try this very simple one line that make your work
arr =[["id", "name", "address"], [1, "James", "add 1"], [2, "John", "add2"] ]
arr.map {|a| arr.first.zip(a).to_h unless a == arr.first }.compact

Multiply all even indexed integers by two

Wanting to take a fixnum of integers and multiply all even(indexed) integers by two. I figured the best way to do this is first turn fixnum into an array. So lets say the following number of 16 digits: a = 4408041234567901
I know I could:
a.to_s.split('')
Which will return 'a' to an array of 'stringed' numbers. But then I cant follow up with:
a.map!.with_index {|i,n| i.even? n*2}
Guess I'm kinda stuck on how to create a method to do this. So my question may even be how to turn that group of numbers into an array of fixnums/integers instead of strings.
I would prefer to remove the conditional altogether from the loop, by creating an Enumerator that contains the coefficients you want to multiply by (2 for even indexes and 1 for odd.)
coef = [2, 1].cycle
This essentially creates an Enumerator that alternately returns 2 and 1 when next is called on it. You can then use this to simplify your map to:
a.to_s.each_char.map { |v| v.to_i * coef.next }
To change it to an Array, you could do
a = 4408041234567901
arr = a.to_s.chars.map(&:to_i)
# => [4, 4, 0, 8, 0, 4, 1, 2, 3, 4, 5, 6, 7, 9, 0, 1]
You can also multiply alternate numbers by 2
arr = a.to_s.chars.map.with_index {|n,i| i.even? ? n.to_i * 2 : n.to_i }
# => [8, 4, 0, 8, 0, 4, 2, 2, 6, 4, 10, 6, 14, 9, 0, 1]
Improving a little bit, you can use a Hash to find the number to be multiplied.
h = {true => 2, false => 1}
a.to_s.each_char.map.with_index {|n,i| n.to_i * h[i.even?]}
EDIT
I can explain each step, But it will be better if you can try to figure it out on your own. Open irb, type a.to_s and check the output. Then type a.to_s.chars and inspect the output and so on..
a = 4408041234567901
even_odd = [:even, :odd].cycle
#=> #<Enumerator: [:even, :odd]:cycle>
If the indexing starts with the highest-order (leftmost) digit:
a.to_s.each_char.map { |d|
(even_odd.next == :even) ? 2*d.to_i : d.to_i }
#=> [8, 4, 0, 8, 0, 4, 2, 2, 6, 4, 10, 6, 14, 9, 0, 1]
If the indexing starts with the ones digit:
s = a.to_s
even_odd.next if s.size.even?
s.each_char.map { |d| ( even_odd.next == :even) ? 2*d.to_i : d.to_i }
#=> [4, 8, 0, 16, 0, 8, 1, 4, 3, 8, 5, 12, 7, 18, 0, 2]
Here are the steps for the example when zero-based indexing starts with the highest-order digit.
Array#cycle converts the array [:even, :odd] to an enumerator:
even_odd = [:even, :odd].cycle
even_odd.next #=> :even
even_odd.next #=> :odd
even_odd.next #=> :even
even_odd.next #=> :odd
...
b = a.to_s
#=> "4408041234567901"
enum0 = b.each_char
#=> #<Enumerator: "4408041234567901":each_char>
The enumerator enum0 passes the digits of b to map. I could have instead written:
b = a.to_s.chars
# => ["4", "4", "0", "8", "0", "4", "1", "2",
# "3", "4", "5", "6", "7", "9", "0", "1"]
but that creates an intermediate array. The enumerator does not and therefore is more efficient. Continuing...
enum1 = enum0.map
#=> #<Enumerator: #<Enumerator: "4408041234567901":each_char>:map>
You can think of this as a "compound enumerator". We can see its contents by converting it to an array:
enum1.to_a
#=> ["4", "4", "0", "8", "0", "4", "1", "2",
# "3", "4", "5", "6", "7", "9", "0", "1"]
The method each will pass each element of the enumerator into the block. Proof:
enum1.each { |d| (enum.next == :even) ? 2*d.to_i : d.to_i }
# => [8, 4, 0, 8, 0, 4, 2, 2, 6, 4, 10, 6, 14, 9, 0, 1]
We can manually step through the elements of enum1 by using Enumerator#next. We will assign the value to the block variable d and perform the calculation in the block to map the digit d:
d = enum1.next
#=> "4"
(enum.next == :even) ? 2*d.to_i : d.to_i
#=> (:even == :even) ? 2*"4".to_i : "4".to_i
#=> (true) ? 8 : 4
#=> 8 ("4" is mapped to 8)
d = enum1.next
#=> "4"
(enum.next == :even) ? 2*d.to_i : d.to_i
#=> (:odd == :even) ? 2*"4".to_i : "4".to_i
#=> (false) ? 8 : 4
#=> 4 ("4" is mapped to 4)
d = enum1.next
#=> "0"
#=> (:even == :even) ? 2*"0".to_i : "0".to_i
#=> (true) ? 0 : 0
#=> 8 ("0" is mapped to 0)
and so on.

Sorting alpha-numeric array?

I would like to sort an alphanumeric array in Ruby and return a sorted array back.
For example:
["world", "jim", 4, 1, "apple"]
to return:
["apple", "jim", 1, 4, "world"]
so that where there is an object of the same class in the same position before the sort, just now it's numerical/alphabetical.
xs = ["world", "jim", 4, 1, "apple", 5, 6]
sorted_by_cls = xs.group_by(&:class).each { |k,vs| vs.sort!.reverse! }
sorted_xs = xs.map(&:class).map { |c| sorted_by_cls[c].pop }
Don't know how this compares to other solutions, but here's another one:
xs = ["world", "jim", 4, 1, "apple", 5, 6]
classes = xs.map(&:class)
sorts = Hash[*classes.uniq.map {|c| [c, xs.select {|x| x.class == c}.sort]}.flatten(1)]
classes.map {|c| sorts[c].shift} # => ["apple", "jim", 1, 4, "world", 5, 6]
A very interesting problem. Here's how I approached it assuming an alphanumeric array. Partition the array into alpha and numeric subarrays, sort them, and reconstitute based on the position and class of the object in the original array.
arr = ["world", "jim", 4, 1, "apple"]
alpha_num = arr.partition { |l| l.is_a? String }.map(&:sort)
arr.map { |l| l.is_a?(String) ? alpha_num.first.shift : alpha_num.last.shift }
My generalized solution is not much different than that of FMc:
arr = ["world", "jim", 4, 1, "apple", 5, 6]
sorted_hash = arr.group_by(&:class).each_value(&:sort!)
arr.map { |l| sorted_hash[l.class].shift }
arr = ["world", "jim", 4, 1, "apple"]
arr.each_with_index.group_by {|e| e.first.class}.values.map {|g|\
g.map(&:first).sort.zip(g.map(&:last).sort)}\
.each_with_object(Array.new(arr.size)) {|e,a| e.each {|f,i| a[i] = f}}
# => ["apple", "jim", 1, 4, "world"]
Let's go through this:
a1 = arr.each.with_index.group_by {|e| e.first.class} # => {String=>[["world", 0], ["jim", 1], ["apple", 4]], Fixnum=>[[4, 2], [1, 3]]}
a21, a22 = a1.values #=>[[["world",0],["jim",1],["apple",4]],[[4,2],[1,3]]]
a31 = a21.map(&:first) # => ["world", "jim", "apple"]
a41 = a31.sort # => ["apple", "jim", "world"]
a51 = a21.map(&:last).sort # => [0, 1, 4]
a61 = a41.zip(a51) # => [["apple", 0], ["jim", 1], ["world", 4]]
a62 = a22.map(&:first).sort.zip(a22.map(&:last).sort) # => [[1,2], [4,3]]
[a61, a62].each_with_object(Array.new(5)) {|e,a| e.each {|f,i| a[i] = f}}
a = ["world", "jim", 4, 1, "apple", "cabbage"]
is = a.each_index.group_by{|i| a[i].class}.values
.flat_map{|is| [is, is.sort_by{|i| a[i]}].transpose}
.sort.map(&:last)
a.values_at(*is)

How to quickly print Ruby hashes in a table format?

Is there a way to quickly print a ruby hash in a table format into a file?
Such as:
keyA keyB keyC ...
123 234 345
125 347
4456
...
where the values of the hash are arrays of different sizes. Or is using a double loop the only way?
Thanks
Try this gem I wrote (prints hashes, ruby objects, ActiveRecord objects in tables): http://github.com/arches/table_print
Here's a version of steenslag's that works when the arrays aren't the same size:
size = h.values.max_by { |a| a.length }.length
m = h.values.map { |a| a += [nil] * (size - a.length) }.transpose.insert(0, h.keys)
nil seems like a reasonable placeholder for missing values but you can, of course, use whatever makes sense.
For example:
>> h = {:a => [1, 2, 3], :b => [4, 5, 6, 7, 8], :c => [9]}
>> size = h.values.max_by { |a| a.length }.length
>> m = h.values.map { |a| a += [nil] * (size - a.length) }.transpose.insert(0, h.keys)
=> [[:a, :b, :c], [1, 4, 9], [2, 5, nil], [3, 6, nil], [nil, 7, nil], [nil, 8, nil]]
>> m.each { |r| puts r.map { |x| x.nil?? '' : x }.inspect }
[:a, :b, :c]
[ 1, 4, 9]
[ 2, 5, ""]
[ 3, 6, ""]
["", 7, ""]
["", 8, ""]
h = {:a => [1, 2, 3], :b => [4, 5, 6], :c => [7, 8, 9]}
p h.values.transpose.insert(0, h.keys)
# [[:a, :b, :c], [1, 4, 7], [2, 5, 8], [3, 6, 9]]
No, there's no built-in function. Here's a code that would format it as you want it:
data = { :keyA => [123, 125, 4456], :keyB => [234000], :keyC => [345, 347] }
length = data.values.max_by{ |v| v.length }.length
widths = {}
data.keys.each do |key|
widths[key] = 5 # minimum column width
# longest string len of values
val_len = data[key].max_by{ |v| v.to_s.length }.to_s.length
widths[key] = (val_len > widths[key]) ? val_len : widths[key]
# length of key
widths[key] = (key.to_s.length > widths[key]) ? key.to_s.length : widths[key]
end
result = ""
data.keys.each {|key| result += key.to_s.ljust(widths[key]) + " " }
result += "\n"
for i in 0.upto(length)
data.keys.each { |key| result += data[key][i].to_s.ljust(widths[key]) + " " }
result += "\n"
end
# TODO write result to file...
Any comments and edits to refine the answer are very welcome.

Resources