I am trying to turn an array of arrays into an array of hashes. Could someone try to explain what I am exactly doing wrong here? The first array within the array of arrays becomes the keys for the hash. I can get the method to return one hash or even three of the same hashes. But I can't get it return each different new hash within the final array.
table_data = [
["first_name", "last_name", "city", "state"],
["Elisabeth", "Gardenar", "Toledo", "OH"],
["Jamaal", "Du", "Sylvania", "OH"],
["Kathlyn", "Lavoie", "Maumee", "OH"]
]
def convert_table(table_array)
hash = {}
final_array = []
headers_array = table_array.shift
table_array.each_index do |x|
i = 0
until i == headers_array.length
hash[headers_array[i]] = table_array[x][i]
final_array << hash
i += 1
end
end
final_array
end
p convert_table(table_data)
#END GOAL
[ { "first_name" => "Elisabeth", "last_name" => "Gardenar", "city" => "Toledo", "state" => "OH" },
{ "first_name" => "Jamaal", "last_name" => "Du", "city" => "Sylvania", "state" => "OH" },
{ "first_name" => "Kathlyn", "last_name" => "Lavoie", "city" => "Maumee", "state" => "OH" }
Pair up keys (in table_data[0] and values (each other row in table_data) using zip, and map them to a hash:
table_data[1..-1].map { |values| Hash[table_data[0].zip(values)] }
EDIT: The part that doesn't work in your case is having one single hash that you keep reusing. When you do final_array << hash, it doesn't add a snapshot of the hash as it is then; it adds a reference to it. Thus, you don't have an array of three hashes, you have an array with three references to the same hash. You could avoid it by doing final_array << hash.clone to actually take a snapshot; or (much simpler) just make a new hash in each iteration of the loop (move hash = {} into the table_array.each_index loop).
As #Amadan has diagnosed your problem, I will suggest a more "Ruby-like" approach.
keys, *data = table_data
#=> [["first_name", "last_name", "city", "state"],
# ["Elisabeth", "Gardenar", "Toledo", "OH"],
# ["Jamaal", "Du", "Sylvania", "OH"],
# ["Kathlyn", "Lavoie", "Maumee", "OH"]
# ]
keys
#=> ["first_name", "last_name", "city", "state"]
data
#=> [["Elisabeth", "Gardenar", "Toledo", "OH"],
# ["Jamaal", "Du", "Sylvania", "OH"],
# ["Kathlyn", "Lavoie", "Maumee", "OH"]
# ]
[keys].product(data).map { |pair| pair.transpose.to_h }
#=> [{"first_name"=>"Elisabeth", "last_name"=>"Gardenar", "city"=>"Toledo",
# "state"=>"OH"},
# {"first_name"=>"Jamaal", "last_name"=>"Du", "city"=>"Sylvania",
# "state"=>"OH"},
# {"first_name"=>"Kathlyn", "last_name"=>"Lavoie", "city"=>"Maumee",
# "state"=>"OH"}
# ]
The steps are as follows.
a = [keys].product(data)
#=> [[["first_name", "last_name", "city", "state"],
# ["Elisabeth", "Gardenar", "Toledo", "OH"]
# ],
# [["first_name", "last_name", "city", "state"],
# ["Jamaal", "Du", "Sylvania", "OH"]],
# [["first_name", "last_name", "city", "state"],
# ["Kathlyn", "Lavoie", "Maumee", "OH"]
# ]
# ]
The first element of a is passed to map, the block variable pair is assigned and the block calculation is performed.
pair = a.first
#=> [["first_name", "last_name", "city", "state"],
# ["Elisabeth", "Gardenar", "Toledo", "OH"]
# ]
b = pair.transpose
#=> [["first_name", "Elisabeth"],
# ["last_name", "Gardenar"],
# ["city", "Toledo"],
# ["state", "OH"]
# ]
g = b.to_h
#=> {"first_name"=>"Elisabeth", "last_name"=>"Gardenar", "city"=>"Toledo",
# "state"=>"OH"}
Therefore, a.first is mapped to g. The remaining calculations are similar.
Your question has been properly answered by #CarySwoveland and #Amadan.
I'd just like to add that your table basically looks like a CSV table with header.
If your table_data does come from a file, you might as well read it directly with CSV :
csv_table = "first_name,last_name,city,state
Elisabeth,Gardenar,Toledo,OH
Jamaal,Du,Sylvania,OH
Kathlyn,Lavoie,Maumee,OH"
require 'csv'
CSV.parse(csv_table, headers: true).each do |row|
p row
end
It outputs
#<CSV::Row "first_name":"Elisabeth" "last_name":"Gardenar" "city":"Toledo" "state":"OH">
#<CSV::Row "first_name":"Jamaal" "last_name":"Du" "city":"Sylvania" "state":"OH">
#<CSV::Row "first_name":"Kathlyn" "last_name":"Lavoie" "city":"Maumee" "state":"OH">
You can work CSV::Row as with an Hash.
If you really want a Hash, you can use row.to_h :
{"first_name"=>"Elisabeth", "last_name"=>"Gardenar", "city"=>"Toledo", "state"=>"OH"}
{"first_name"=>"Jamaal", "last_name"=>"Du", "city"=>"Sylvania", "state"=>"OH"}
{"first_name"=>"Kathlyn", "last_name"=>"Lavoie", "city"=>"Maumee", "state"=>"OH"}
Related
Eg:
hash_var = ["1", "2", {"a" => ["aa", "bb", "cc"]}]
reject(hash_var, "1") # should return ["2", {"a" => ["aa", "bb", "cc"]}]
reject(hash_var, "a") # should return ["1", "2"]
reject(hash_var, {"a" => ["aa"]}) # should return ["2", {"a" => ["bb", "cc"]}]
Basically, I should be able to reject the element from the array, be it hash or array element.
you can use delete mehod on hash like
hash_var = ["1", "2", {"a" => ["aa", "bb", "cc"]}]
hash_var.delete("1") //this will delete 1 from hash then new value of hash will be ["2", {"a"=>["aa", "bb", "cc"]}]
hash_var[1]["a"].delete("aa") //this will give output ["2", {"a"=>["bb", "cc"]}]
and so on according to your use
That should do the job
hash_var.drop_while { |e| e == '1' }
hash_var.reject { |e| e == '1' }
hash_var.find_all { |e| e != '1' }
hash_var.select { |e| e != '1' }
You could define the instance method Enumerable#dig_drop:
arr = ["1", "2", {"a" => ["aa", "bb", "cc"]}, {"b" => ["aa", "bb"]}]
module Enumerable
def dig_drop(*elm)
self.each_with_index {|_, idx|
unless self[idx].respond_to? :dig
self.delete(elm[0])
next
end
last=elm.pop
if elm.empty?
self[idx].delete(last)
else
self[idx].dig(*elm).nil? ? nil : self[idx].dig(*elm).delete(last)
end
}
self.reject!(&:empty?)
end
end
arr.dig_drop "1" # arr equals ["2", {"a" => ["aa", "bb", "cc"]}, {"b" => ["aa", "bb"]}]
arr.dig_drop('a') # arr equals ["2", {"b" => ["aa", "bb"]}]
arr.dig_drop('b', 'bb') # arr equals ["2", {"b" => ["aa"]}
I think this method should work:
def reject(hash_var, ele)
# Find the index of the ele
# If it is found, just remove it and return hash_var
idx = hash_var.index(ele)
if idx
hash_var.delete_at(idx)
return hash_var
end
# Convert the hashes to arrays - 2D arrays
# { "b" => "2" } becomes [["b", "2"]]
(0...hash_var.length).each do |n|
if hash_var[n].class == Hash
hash_var[n] = hash_var[n].to_a
end
end
(0...hash_var.length).each_with_index do |n, i|
# Only operate if hash_var[n] is an array
# this means that this is 2D array i.e. hash
if hash_var[n].class == Array
# If this is just as element, delete the whole hash_key
# i.e. reject(hash_var, "a")
# here ele = "a", so since this is not hash
# we need to delete full element i.e. ["aa", "bb", "cc"]
if hash_var[n][0][0] == ele
hash_var.delete_at(i)
else
next if ele.class == String
ele_a = ele.to_a
if hash_var[n][0][0] == ele_a[0][0]
diff = hash_var[n][0][1] - ele_a[0][1]
hash_var[n][0][1] = diff
end
end
end
end
## Convert the 2D arrays back to hashes
(0...hash_var.length).each do |n|
if hash_var[n].class == Array
hash_var[n] = hash_var[n].to_h
end
end
# return final
return hash_var
end
Tests:
hash_var = ["1", "2", {"a" => ["aa", "bb", "cc"]}]
p reject(hash_var, "1")
# => ["2", {"a"=>["aa", "bb", "cc"]}]
p reject(hash_var, "a")
# => ["1", "2"]
p reject(hash_var, {"a" => ["aa"]})
# => ["1", "2", {"a"=>["bb", "cc"]}]
I have:
arr = ['test', 'testing', 'test123']
ht = {"test": "abc", "water": "wet", "testing": "fun"}
How can I select the values in ht whose key matches arr?
ht_new = ht.select {|hashes| arr.include? hashes}
ht_new # => "{"test": "abc", "testing": "fun"}"
Additionally, how can we return values from:
arr = ["abc", "123"]
ht = [{"key": "abc", "value": "test"}, {"key": "123", "value": "money"}, {"key": "doremi", "value": "rain"}}]
output # => [{"key": "abc", "value": "test"}, {"key": "123", "value": "money"}]
Only a slight change is needed:
ht.select { |k,_| arr.include? k.to_s }
##=> {:test=>"abc", :testing=>"fun"}
See Hash#select.
The block variable _ (a valid local variable), which is the value of key k, signifies to the reader that it is not used in the block calculation. Some prefer writing that |k,_v|, or some-such.
One option is mapping (Enumerable#map) the keys in arr:
arr.map.with_object({}) { |k, h| h[k] = ht[k.to_sym] }
#=> {"test"=>"abc", "testing"=>"fun", "test123"=>nil}
If you want to get rid of pairs with nil value:
arr.map.with_object({}) { |k, h| h[k] = ht[k.to_sym] if ht[k.to_sym] }
#=> {"test"=>"abc", "testing"=>"fun"}
This is an option for the last request:
ht.select{ |h| h if h.values.any? { |v| arr.include? v} }
# or
arr.map { |e| ht.find { |h| h.values.any?{ |v| v == e } } }
#=> [{:key=>"abc", :value=>"test"}, {:key=>"123", :value=>"money"}]
A straightforward way is:
ht.slice(*arr.map(&:to_sym))
# => {:test => "abc", :testing => "fun"}
I want to select items from each array of arr from index position 0, 2 and 4
Input array
arr = [
["name", "address", "contact", "company", "state"],
["n1", "add1", "c1", "cp1", "s1"],
["n2", "add2", "c2", "cp2", "s2"]
]
Output array
arr = [
["name", "contact", "company"],
["n1", "c1", "cp1"],
["n2", "c2", "cp2"]
]
as an alternative to deleting unneeded items, you can just select the needed items.
arr.map{|subarray| subarray.values_at(0, 2, 4) }
# => [["name", "contact", "state"], ["n1", "c1", "s1"], ["n2", "c2", "s2"]]
If you want tot take this more generic and only select the even columns you could do it like this
arr.map{|a| a.select.with_index { |e, i| i.even? }}
which gives
[["name", "contact", "state"], ["n1", "c1", "s1"], ["n2", "c2", "s2"]]
Original question:
I want to delete items from each array of arr from index position 1 and 5
We can use delete_if to achieve this. Here:
arr.map { |a| a.delete_if.with_index { |el, index| [1,5].include? index } }
# => [["name", "contact", "company", "state"], ["n1", "c1", "cp1", "s1"], ["n2", "c2", "cp2", "s2"]]
PS: the output in question is incorrect as for arrays at index 1 and 2, example is deleting element at index 4
Ruby has very nice destructuring syntax, so you can extract all your values in a one-liner:
a = 0.upto(5).to_a # => [0, 1, 2, 3, 4, 5]
x, _, y, _, z = a
x # => 0
y # => 2
z # => 4
The underscores are just placeholder for values you don't need.
It can be also performed with each_slice method.
If 0, 2, 4 values can be treated as a list with every second value omitted (_), it can be written like:
arr.map { |a| a.each_slice(2).map { |item, _| item } }
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 6 years ago.
Improve this question
I have this hash:
{"id" => [323, 324], info => ["Test Info", "Test Info2"]}
I would like to have a hash inside an array like this:
array = [
[{"id" => "323", info => "Test Info"}],
[{"id" => "324", info => "Test Info2"}]
]
I have duplicate data. I need to use uniq on id and info to get each id only once. Because of that, I need to join them afterwards.
h = {"id" => [323, 324], "info" => ["Test Info", "Test Info2"]}
h.map { |k, v| [k].product v }.transpose.map &:to_h
#⇒ [
# [0] {
# "id" => 323,
# "info" => "Test Info"
# },
# [1] {
# "id" => 324,
# "info" => "Test Info2"
# }
]
If you want to wrap each nested hash into it’s own array, one more action is required:
h.map { |k, v| [k].product v }.transpose.map(&:to_h).map { |e| [e] }
#⇒ [
# [0] [
# [0] {
# "id" => 323,
# "info" => "Test Info"
# }
# ],
# [1] [
# [0] {
# "id" => 324,
# "info" => "Test Info2"
# }
# ]
# ]
You can use this solution (maybe so OP in this case)
h = {"id" => [323, 324], "info" => ["Test Info", "Test Info2"]}
h.values.transpose.map do |a|
[Hash[h.keys.each_with_index.map { |k, i| [k, a[i]] }]]
end
# => [[{"id"=>323, "info"=>"Test Info"}], [{"id"=>324, "info"=>"Test Info2"}]]
This looks like a job for zip which zippers together two arrays:
hash = {
"id" => [323, 324],
"info" => ["Test Info", "Test Info2"]
}
keys = %w[ id info ]
array = hash['id'].zip(hash['info']).map do |id, info|
Hash[keys.zip(pair)]
end
# => [{"id"=>323, "info"=>"Test Info"}, {"id"=>324, "info"=>"Test Info2"}]
The Hash[] method is used to convert an array of form [['a',1],['b',2]] to a hash of form {'a'=>1,'b'=>2}.
Here are a couple more:
h = {"id" => [323, 324], "info" => ["Test Info", "Test Info2"]}
da_keys = h.keys
h.values.transpose.each_with_object([]) { |a,b| b << da_keys.zip(a).to_h }
#=> [{"id"=>323, "info"=>"Test Info"}, {"id"=>324, "info"=>"Test Info2"}]
rolling_keys = h.keys.cycle
h.values.transpose.map {|a| a.each_with_object({}) {|v,g| g.update(rolling_keys.next=>v)}}
#=> [{"id"=>323, "info"=>"Test Info"}, {"id"=>324, "info"=>"Test Info2"}]
You asked for this:
array = [
[{"id" => "323", info => "Test Info"}],
[{"id" => "324", info => "Test Info2"}]
]
But I assume what you really want is just this:
array = [
{"id" => "323", "info" => "Test Info"},
{"id" => "324", "info" => "Test Info2"}
]
Given this:
h = {"id" => [323, 324], "info" => ["Test Info", "Test Info2"]}
You can do this:
h.map { |k,l| l.map { |v| [ k,v ] } }.reduce(&:zip).map(&:to_h)
How to find out elements of array having same value_entries. As code is in ruby, looking better approach.
Input
"block_device": {
"sda": {
"size": "83886080",
"removable": "0",
"model": "VBOX HARDDISK",
"rev": "1.0",
"state": "running",
"timeout": "30",
"vendor": "ATA",
"rotational": "1"
},
"sdb": {
"size": "16384",
"removable": "0",
"model": "VBOX HARDDISK",
"rev": "1.0",
"state": "running",
"timeout": "30",
"vendor": "ATA",
"rotational": "1"
},
"sdc": {
"size": "16384",
"removable": "0",
"model": "VBOX HARDDISK",
"rev": "1.0",
"state": "running",
"timeout": "30",
"vendor": "ATA",
"rotational": "1"
}
}
Sample Code Block:
devicesForRaid = []
deviceHolder = []
node['block_device'].each do |deviceName,deviceProperty|
deviceHolder.push(deviceName,deviceProperty['size']) #['sda'=>'83886080','sdb'=>'16384','sdc'=>'16384']
end
deviceHolder.each do | deviceName,deviceSize|
# how to get deviceName who all having same size
if(deviceSize_match_found){
devicesForRaid.push(deviceName)
}
end
Expected Output:
devicesForRaid = ['sdb','sdc']
Trial way:
using stack,
push 1st element onto stack, and comparing with rest of array element.
if match found, push that element onto stack.
Sample code block completion or better code highly appreciated.
You can do this:
input_hash[:block_device].each_with_object({}) { |(k,g),h|
h.update(g[:size]=>[k]) { |_,o,n| o+n } }
#=> {"83886080"=>[:sda], "16384"=>[:sdb, :sdc]}
This uses the form of Hash#update (aka merge!) that employs the block:
{ |_,o,n| o+n }
to determine the values of keys that are present in both hashes being merged.
res = Hash.new { |h, k| h[k] = [] }
node['block_device'].each{|k, v| res[v[:size]]<<k}
gives:
=> {"83886080"=>[:sda], "16384"=>[:sdb, :sdc]}
I guess you want to look through res for values with length of 2 or more
res.to_a.select{|k, v| v.size > 1}
You can do it this way (assuming block_device is a key in your input data hash):
hash = input_data[:block_device]
new_hash = Hash.new{ |h, k| h[k] = [] }
hash.each do |k, v|
new_hash[v[:size]] << k
end
p new_hash
# => {"83886080"=>[:sda], "16384"=>[:sdb, :sdc]}
From this new_hash, you can extract your required data easily.
e.g. if you want to extract the elements that has a size more than 1, you can do this:
p new_hash.select { |k,v| v.length > 1 }.values.flatten
# => [:sdb, :sdc]
How about using group_by?
node[:block_device]
.group_by {|device, attributes| attributes[:size] }
.map {|size, devices| devices.size > 1 ? devices.map(&:first) : nil }
.compact
.flatten
=> [:sdb, :sdc]
I think this way is easy to understand what you are doing.