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.
Related
I have this data structure resulted from a query grouping
{
[0, "AR"]=>2,
[0, nil]=>1,
[0, "AQ"]=>6,
[1, nil]=>4,
[1, "AQ"]=>3,
[2, "BG"]=>1,
[2, nil]=>1,
}
I want to manipulate it so I end up with a structure grouped like this
{
0 => {
'AR' => 2,
'AQ' => 6,
nil => 1
},
1 => {
'AQ' => 1,
nil => 4
},
2 => {
'BG' => 1,
nil => 1
}
}
input = {
[0, "AR"]=>2,
[0, nil]=>1,
[0, "AQ"]=>6,
[1, nil]=>4,
[1, "AQ"]=>3,
[2, "BG"]=>1,
[2, nil]=>1,
}
result = {}
input.each do |k, v|
if result[k[0]]
result[k[0]].merge!({ k[1] => v })
else
result[k[0]] = { k[1] => v }
end
end
puts result
#{0=>{"AR"=>2, nil=>1, "AQ"=>6}, 1=>{nil=>4, "AQ"=>3}, 2=>{"BG"=>1, nil=>1}}
I think this is not the most succinct way, I hope some advice!
hash = {
[0, "AR"]=>2,
[0, nil]=>1,
[0, "AQ"]=>6,
[1, nil]=>4,
[1, "AQ"]=>3,
[2, "BG"]=>1,
[2, nil]=>1,
}
new_hash = {}
hash.each{|k, v| new_hash[k[0]] ||= {}; new_hash[k[0]].merge!({k[1] => v})}
puts new_hash # {0=>{"AR"=>2, nil=>1, "AQ"=>6}, 1=>{nil=>4, "AQ"=>3}, 2=>{"BG"=>1, nil=>1}}
Here is one more very similar to previous answers but with using of #each_with_object:
hash = {
[0, "AR"]=>2,
[0, nil]=>1,
[0, "AQ"]=>6,
[1, nil]=>4,
[1, "AQ"]=>3,
[2, "BG"]=>1,
[2, nil]=>1,
}
result_hash = Hash.new { |h,k| h[k] = {} }
hash.each_with_object(result_hash) do |((parrent_key, key), value), res|
res[parrent_key].merge!(key => value)
end
=> {0=>{"AR"=>2, nil=>1, "AQ"=>6}, 1=>{nil=>4, "AQ"=>3}, 2=>{"BG"=>1, nil=>1}}
I came up with an answer that doesn't require additional variable assignments in its enclosing scope (it has "referential transparency": https://en.wikipedia.org/wiki/Referential_transparency)
input
.group_by { |(arr, num)| arr.first }
.each_with_object(Hash.new) do |(key, vals), hsh|
vals.each do |((key, innerkey), innerval)|
hsh[key] ||= {}
hsh[key][innerkey] = innerval
end
hsh
end
# {0=>{"AR"=>2, nil=>1, "AQ"=>6}, 1=>{nil=>4, "AQ"=>3}, 2=>{"BG"=>1, nil=>1}}
Two high-level steps:
I noticed the output object is grouped by the first array element (here, 0/1/2). I use #group_by to create a hash with that structure.
# output of `#group_by` on first array element:
key: 0, vals: [ [[0, "AR"], 2], [[0, nil], 1], [[0, "AQ"], 6] ]
key: 1, vals: [ [[1, nil], 4], [[1, "AQ"], 3] ]
key: 2, vals: [ [[2, "BG"], 1], [[2, nil], 1] ]
I use #each_with_object to construct the nested hashes. For each vals array above, I extracted the second and third values by destructuring the arrays in the block parameter (((key, innerkey), innerval)) and then the hash assignment was straightforward.
I have:
array1 = [[1,2,3,4,5],[7,8,9,10],[11,12,13,14]]
#student_ids = [1,2,3]
I want to replace elements in array1 that are included in #student_ids with 'X'. I want to see:
[['X','X','X',4,5],[7,8,9,10],[11,12,13,14]]
I have code that is intended to do this:
array1.collect! do |i|
if i.include?(#student_ids) #
i[i.index(#student_ids)] = 'X'; i # I want to replace all with X
else
i
end
end
If #student_ids is 1, then it works, but if #student_ids has more than one element such as 1,2,3, it raises errors. Any help?
It's faster to use a hash or a set than to repeatedly test [1,2,3].include?(n).
arr = [[1,2,3,4,5],[7,8,9,10],[11,12,13,14]]
ids = [1,2,3]
Use a hash
h = ids.product(["X"]).to_h
#=> {1=>"X", 2=>"X", 3=>"X"}
arr.map { |a| a.map { |n| h.fetch(n, n) } }
#=> [["X", "X", "X", 4, 5], [7, 8, 9, 10], [11, 12, 13, 14]]
See Hash#fetch.
Use a set
require 'set'
ids = ids.to_set
#=> #<Set: {1, 2, 3}>
arr.map { |a| a.map { |n| ids.include?(n) ? "X" : n } }
#=> [["X", "X", "X", 4, 5], [7, 8, 9, 10], [11, 12, 13, 14]]
Replace both maps with map! if the array is to be modified in place (mutated).
Try following, (taking #student_ids = [1, 2, 3])
array1.inject([]) { |m,a| m << a.map { |x| #student_ids.include?(x) ? 'X' : x } }
# => [["X", "X", "X", 4, 5], [7, 8, 9, 10], [11, 12, 13, 14]]
You can use each_with_index and replace the item you want:
array1 = [[1,2,3,4,5],[7,8,9,10],[11,12,13,14]]
#student_ids = [1,2,3]
array1.each_with_index do |sub_array, index|
sub_array.each_with_index do |item, index2|
array1[index][index2] = 'X' if #student_ids.include?(item)
end
end
You can do the following:
def remove_student_ids(arr)
arr.each_with_index do |value, index|
arr[index] = 'X' if #student_ids.include?(value) }
end
end
array1.map{ |sub_arr| remove_student_ids(sub_arr)}
I have an array:
a = [1, 2, 3, 3, 6, 8, 1, 9]
I want to display each unique element value and its associated element count like this:
1: 2
2: 1
3: 2
6: 1
8: 1
9: 1
So far I have:
a.sort.group_by { |x| x }
{
1 => [
[0] 1,
[1] 1
],
2 => [
[0] 2
],
3 => [
[0] 3,
[1] 3
],
6 => [
[0] 6
],
8 => [
[0] 8
],
9 => [
[0] 9
]
}
So each element of the Hash contains an array. I can use that array's count to get my answer, but I'm having trouble figuring out how to process the hash concisely.
Is this a horrible implementation?
a.sort.group_by { |x| x }.each {|x| puts "#{x[0]} #{x[1].count}" }
How about:
a.inject({}) { |a,e| a[e] = (a[e] || 0) + 1; a }
=> {1=>2, 2=>1, 3=>2, 6=>1, 8=>1, 9=>1}
For example:
h = a.inject({}) { |a,e| a[e] = (a[e] || 0) + 1; a }
=> {1=>2, 2=>1, 3=>2, 6=>1, 8=>1, 9=>1}
h.keys.sort.each { |k| puts "#{k}: #{h[k]}" }
1: 2
2: 1
3: 2
6: 1
8: 1
9: 1
From comments of others below:
a.each_with_object(Hash.new(0)) { |e,a| a[e] += 1 }
=> {1=>2, 2=>1, 3=>2, 6=>1, 8=>1, 9=>1}
Use uniq to get the unique array values and sort to sort them in ascending order. Then for each of these values x, display a.count(x).
a = [1, 2, 3, 3, 6, 8, 1, 9]
a.uniq.sort.each {|x| puts '%d: %d' % [x, a.count(x)] }
For greater efficiency, make a hash that maps a value to the number of times it appears in the array. An easy way to do this is to initialize a Hash object that maps keys to zero by default. Then you can increment each value's count by one as you iterate through the array.
counts = Hash.new(0)
a.each {|x| counts[x] += 1 }
counts.keys.sort.each {|x| puts '%d: %d' % [x, counts[x]] }
Consider this:
a = [1, 2, 3, 3, 6, 8, 1, 9]
a.group_by{ |n| n } # => {1=>[1, 1], 2=>[2], 3=>[3, 3], 6=>[6], 8=>[8], 9=>[9]}
a.group_by{ |n| n }.map{ |k, v| [k, v.size ] } # => [[1, 2], [2, 1], [3, 2], [6, 1], [8, 1], [9, 1]]
Finally:
a.group_by{ |n| n }.map{ |k, v| [k, v.size ] }.to_h # => {1=>2, 2=>1, 3=>2, 6=>1, 8=>1, 9=>1}
Try this
module Enumerable
def freq
hash = Hash.new(0)
each { |each| hash[each] += 1 }
hash
end
end
And then
[1, 2, 3, 3, 6, 8, 1, 9].freq
# => {1=>2, 2=>1, 3=>2, 6=>1, 8=>1, 9=>1}
I have an array, and i need to split it into two, one after another scenario.
number = [1,2,3,4,5,6,7,8,9]
I need to slip it into two like following
split1 = [2,4,6,8]
split2 = [1,3,5,7,9]
A modification on Arup's answer:
split1, split2 = [1,2,3,4,5,6,7,8,9].partition.with_index{|_, i| i.odd?}
split1 # => [2, 4, 6, 8]
split2 # => [1, 3, 5, 7, 9]
split1, split2 = %i[a b c d e].partition.with_index{|_, i| i.odd?}
split1 # => [:b, :d]
split2 # => [:a, :c, :e]
I've got an array of hashes and would like to sum up selected values. I know how to sum all of them or one of them but not how to select more than one key.
i.e.:
[{"a"=>5, "b"=>10, "active"=>"yes"}, {"a"=>5, "b"=>10, "active"=>"no"}, {"a"=>5, "b"=>10, "action"=>"yes"}]
To sum all of them I using:
t = h.inject{|memo, el| memo.merge( el ){|k, old_v, new_v| old_v + new_v}}
=> {"a"=>15, "b"=>30, "active"=>"yesnoyes"} # I do not want 'active'
To sum one key, I do:
h.map{|x| x['a']}.reduce(:+)
=> 15
How do I go about summing up values for keys 'a' and 'b'?
You can use values_at:
hs = [{:a => 1, :b => 2, :c => ""}, {:a => 2, :b => 4, :c => ""}]
keys = [:a, :b]
hs.map { |h| h.values_at(*keys) }.inject { |a, v| a.zip(v).map { |xy| xy.compact.sum }}
# => [3, 6]
If all required keys have values it will be shorter:
hs.map { |h| h.values_at(*keys) }.inject { |a, v| a.zip(v).map(&:sum) }
# => [3, 6]
If you want Hash back:
Hash[keys.zip(hs.map { |h| h.values_at(*keys) }.inject{ |a, v| a.zip(v).map(&:sum) })]
# => {:a => 3, :b => 6}
I'd do something like this:
a.map { |h| h.values_at("a", "b") }.transpose.map { |v| v.inject(:+) }
#=> [15, 30]
Step by step:
a.map { |h| h.values_at("a", "b") } #=> [[5, 10], [5, 10], [5, 10]]
.transpose #=> [[5, 5, 5], [10, 10, 10]]
.map { |v| v.inject(:+) } #=> [15, 30]
How is this ?
h = [{"a"=>5, "b"=>10, "active"=>"yes"}, {"a"=>5, "b"=>10, "active"=>"no"}, {"a"=>5, "b"=>10, "action"=>"yes"}]
p h.map{|e| e.reject{|k,v| %w(active action).include? k } }.inject{|memo, el| memo.merge( el ){|k, old_v, new_v| old_v + new_v}}
# >> {"a"=>15, "b"=>30}