How to check if hash keys match value from array - arrays

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"}

Related

How to reject an element from an array which also contains hashes, in Ruby?

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

Converting an array to hash in ruby

Supposed I have an array that looks like
testarr = [["Actor", "Morgan", "33", ["A","B"]],
["Movie", "Titanic", "44", ["A","A"]],
["Actor", "Jack Black", "333", ["A","A"]]]
I want to convert this into a hash which will be converted into a json eventually.
I want it to look like
{
"Actor" => {
{ "name" : "Morgan",
"Age" : 33",
"Films: { "A", "B" }} ,
{ "name" : "Jack Black",
"Age" : 44",
"Films: { "A", "A" }}
}
"Movie" => {
{ "Title" : "Titanic"
"Gross" : "44"
"Actors" : { "A", "A" }
}
}
Not sure about the exact format, but whatever makes sense.
I tried
def hashing(arr)
hash = Hash.new
arr.each do |item|
if item[0] == "Movie"
item.delete("Movie")
hash["Movie"] = item
item["Title"] = item[1]
item["Movie"]["Box Office"] = item[2]
item["Movie"]["Actors"] = item[3]
else
item.delete("Actor")
hash["Actor"] = item
item["Actor"]["Name"] == item[1]
item["Actor"]["Age"] == item[2]
item["Actor"]["Filmography"] == item[3]
end
end
return hash
end
testarr = [["Actor", "Morgan", "33", ["dsfds","dsfdsf"]],
["Movie", "Titanic", "44", ["dsfds","dfdsf"]],
["Actor", "Jack Black", "333", ["ssdsfds","dsfdsf"]]]
puts hashing(testarr)
But it gives me an error for putting the array item into "Movie" and "Actor" then trying to create keys like "Name" and "Age".
How can I make this as I desire?
testarr = [["Actor", "Morgan", "33", ["A","B"]],
["Movie", "Titanic", "44", ["A","A"]],
["Actor", "Jack Black", "333", ["A","A"]]]
a = Hash.new{ |h,k| h[k] = [] }
testarr.each do |arr|
b = {name: arr[1], age: arr[2], films: arr[3]}
a[arr[0]] << b
end
this will produce
{"Actor"=>[{"name"=>"Morgan", "age"=>"33", "films"=>["A", "B"]}, {"name"=>"Jack Black", "age"=>"333", "films"=>["A", "A"]}], "Movie"=>[{"name"=>"Titanic", "age"=>"44", "films"=>["A", "A"]}]}
Please try the below code,
v = [["Actor", "Morgan", "33", ["A", "B"]], ["Movie", "Titanic", "44", ["A", "A"]], ["Actor", "Jack Black", "333", ["A", "A"]]]
v.inject({}) do |ot, arr|
item = {name: arr[1], age: arr[2], films: arr[3]}
if ot[arr[0]].present?
ot[arr[0]] << item
else
ot[arr[0]] = []
ot[arr[0]] << item
end
ot
end
And the o/p is like below,
# => {"Actor"=>[{:name=>"Morgan", :age=>"33", :films=>["A", "B"]}, {:name=>"Jack Black", :age=>"333", :films=>["A", "A"]}], "Movie"=>[{:name=>"Titanic", :age=>"44", :films=>["A", "A"]}]}
Please note here the Actor is not hash of hashes, it's array of hashes, this is the standard way of keeping collection and convert it to json if you need by using to_json method.
You need to iterate through the array and parse each item, appending it to the resultant hash.
testarr = [["Actor", "Morgan", "33", ["A", "B"]],
["Movie", "Titanic", "44", ["A", "A"]],
["Actor", "Jack Black", "333", ["A", "A"]]]
results = {}
testarr.each do |item|
key, a, b, c = item
r = if key == 'Actor'
{ name: a, age: b, movies: c }
elsif key == 'Movie'
{ title: a, gross: b, actors: c }
end
results[key] = [] unless results[key]
results[key] << r
end
puts results
This will produce:
{"Actor"=>[{:name=>"Morgan", :age=>"33", :movies=>["A", "B"]}, {:name=>"Jack Black", :age=>"333", :movies=>["A", "A"]}], "Movie"=>[{:title=>"Titanic", :gross=>"44", :actors=>["A", "A"]}]}
The value in your :actor contains a hash without a key. The best thing you can do is put that into an array.
This will work. There might be a cleaner way, but I'm not sure how at the moment:
h = Hash.new { |hash, key| hash[key] = [] }
testarr = [["Actor", "Morgan", "33", ["A", "B"]], ["Movie", "Titanic", "44", ["A", "A"]], ["Actor", "Jack Black", "333", ["A", "A"]]]
testarr.each do |t|
if t[0] == 'Movie'
h[t[0]] << {title: t[1], gross: t[2], actors: t[3]}
else
h[t[0]] << {name: t[1], age: t[2], films: t[3]}
end
end
puts h
Output:
{"Actor"=>[{:name=>"Morgan", :age=>"33", :films=>["A", "B"]}, {:name=>"Jack Black", :age=>"333", :films=>["A", "A"]}], "Movie"=>[{:title=>"Titanic", :gross=>"44", :actors=>["A", "A"]}]}
I tried to keep the example you wrote.
First of all, it must be shaped for Array(such as [a, b] ) not Hash( {a, b} ) list of items
# You may want result like this ...
{
"Actor": [ # not '{' but '['
{
"name": "Morgan",
"Age": "33",
"Films": ["A", "B"] # not '{' but '[' also
},
{
"name": "Jack Black",
"Age": "44",
"Films": ["A", "A"]
}
],
"Movie": [
{
"Title": "Titanic",
"Gross": "44",
"Actors": ["A", "A"]
}
]
}
and then your function should be like this ...
def hashing(arr)
hash = Hash.new
hash["Movie"], hash["Actor"] = [], []
arr.each do |item|
if item[0] == "Movie"
movie = {}
movie["Title"] = item[1]
movie["Box Office"] = item[2]
movie["Actors"] = item[3]
item.delete("Movie") # optional
hash["Movie"] << movie
else
actor = {}
actor["Name"] = item[1]
actor["Age"] = item[2]
actor["Filmography"] = item[3]
item.delete("Actor") # optional
hash["Actor"] << actor
end
end
return hash
end
Then it's time to test!
as your codes,
testarr = [
["Actor", "Morgan", "33", ["dsfds","dsfdsf"]],
["Movie", "Titanic", "44", ["dsfds","dfdsf"]],
["Actor", "Jack Black", "333", ["ssdsfds","dsfdsf"]]
]
puts hashing(testarr)
It will return this:
{
"Movie"=>
[
{"Title"=>"Titanic", "Box Office"=>"44", "Actors"=>["dsfds", "dfdsf"]}
],
"Actor"=>
[
{"Name"=>"Morgan", "Age"=>"33", "Filmography"=>["dsfds", "dsfdsf"]},
{"Name"=>"Jack Black", "Age"=>"333", "Filmography"=>["ssdsfds", "dsfdsf"]}
]
}
Code
def convert(arr, keys)
arr.group_by(&:first).transform_values do |a|
a.map { |key, *values| keys[key].zip(values).to_h }
end
end
Example (using testarr defined in the question)
keys = { "Actor"=>[:name, :Age, :Films], "Movie"=>[:Title, :Gross, :Actors] }
convert(testarr, keys)
#=> { "Actor"=>[
# {:name=>"Morgan", :Age=>"33", :Films=>["A", "B"]},
# {:name=>"Jack Black", :Age=>"333", :Films=>["A", "A"]}
# ],
# "Movie"=>[
# {:Title=>"Titanic", :Gross=>"44", :Actors=>["A", "A"]}
# ]
# }
Explanation
See Enumerable#group_by, Hash#transform_values, Array#zip and Array#to_h.
The steps are as follows.
h = testarr.group_by(&:first)
#=> { "Actor"=>[
# ["Actor", "Morgan", "33", ["A", "B"]],
# ["Actor", "Jack Black", "333", ["A", "A"]]
# ],
# "Movie"=>[
# ["Movie", "Titanic", "44", ["A", "A"]]
# ]
# }
Though not quite equivalent, you can think of testarr.group_by(&:first) as being "shorthand" for testarr.group_by { |a| a.first }. Continuing,
e0 = h.transform_values
#=> #<Enumerator:
# {"Actor"=>[["Actor", "Morgan", "33", ["A", "B"]],
# ["Actor", "Jack Black", "333", ["A", "A"]]],
# "Movie"=>[["Movie", "Titanic", "44", ["A", "A"]]]}
# :transform_values>
The first element is generated by the enumerator e0, passed to the block and the block variable is set equal to that value.
a = e0.next
#=> [["Actor", "Morgan", "33", ["A", "B"]],
# ["Actor", "Jack Black", "333", ["A", "A"]]]
A second enumerator is now created.
e1 = a.map
#=> #<Enumerator: [["Actor", "Morgan", "33", ["A", "B"]],
# ["Actor", "Jack Black", "333", ["A", "A"]]]:map>
The first value is generated by e1, passed to the inner block and the block variables are assigned values (using disambiguation).
key, *values = e1.next
#=> ["Actor", "Morgan", "33", ["A", "B"]]
key
#=> "Actor"
values
#=> ["Morgan", "33", ["A", "B"]]
The inner block calculation is now performed.
b = keys[key].zip(values)
#=> keys["Actor"].zip(["Morgan", "33", ["A", "B"]])
#=> [:name, :Age, :Films].zip(["Morgan", "33", ["A", "B"]])
#=> [[:name, "Morgan"], [:Age, "33"], [:Films, ["A", "B"]]]
b.to_h
#=> {:name=>"Morgan", :Age=>"33", :Films=>["A", "B"]}
Now the second and last element is generated by e1 and the same calculations are performed.
key, *values = e1.next
#=> ["Actor", "Jack Black", "333", ["A", "A"]]
b = keys[key].zip(values)
#=> [[:name, "Jack Black"], [:Age, "333"], [:Films, ["A", "A"]]]
b.to_h
#=> {:name=>"Jack Black", :Age=>"333", :Films=>["A", "A"]}
When another value is sought from e1 we obtain the following.
e1.next
#=> StopIteration: iteration reached an end
This exception is caught, causing e1 to return to the outer block. At this point e0 generates it next (and last value).
a = e0.next
#=> [["Movie", "Titanic", "44", ["A", "A"]]]
The remaining calculations are similar.

Find all elements in an array between two elements

Is there any method to get the elements in an array between two values?
Example:
arr = ["abc", "AGC", "xxx", "tcv", "car", "kiss"]
## After some operation..
arr # => ["AGC", "xxx", "tcv", "car"]
How do I get the elements that are between "AGC" and "car" in a new array?
You could use Ruby's flip-flop operator:
def extract(arr, first, last)
return nil unless ([first, last]-arr).empty? && arr.index(first) <= arr.index(last)
arr.select { |s| s==first..s==last ? true : false }
end
arr = ["abc", "AGC", "xxx", "tcv", "car", "kiss"]
extract(arr, 'AGC', 'car')
#=> ["AGC", "xxx", "tcv", "car"]
extract(arr, 'car', 'car')
#=> ["car"]
extract(arr, 'AGC', 'dog')
#=> nil
extract(arr, 'car', 'AGC')
#=> nil
If you're confident they're both present then you should be able to do this:
a = ["abc", "AGC", "xxx", "tcv", "car", "kiss"]
a[(a.index('abc')+1)..a.index('car')]
# => ["AGC", "xxx", "tcv", "car"]
You can use Array#drop_while/Array#take_while. Note that this leads to one-off, hence the need for push at the end:
things = ['abc', 'AGC', 'xxx', 'tcv', 'car', 'kiss']
things.
drop_while { |thingy| thingy != 'AGC' }.
take_while { |thingy| thingy != 'car' }.
push('car')
# => ["AGC", "xxx", "tcv", "car"]
Alternatively:
things.
slice_before { |thingy| thingy == 'AGC' }.to_a.last.
slice_after { |thingy| thingy == 'car' }.first
# => ["AGC", "xxx", "tcv", "car"]
Here's a variation of #ndn's answer, with 2 reverses:
def extract(arr, first, last)
arr.reverse.drop_while{ |x| x != last }.reverse.drop_while{ |x| x != first }
end
arr = %w[abc AGC xxx tcv car kiss]
p extract(arr, 'AGC', 'car')
#=> ["AGC", "xxx", "tcv", "car"]
p extract(arr, 'car', 'car')
#=> ["car"]
p extract(arr, 'AGC', 'dog')
#=> []
p extract(arr, 'car', 'AGC')
#=> []
The advantage of using 2 drop_whiles is that no extra push or drop is needed, and if first and last aren't present or well ordered, the method returns an empty array.
Here's a related question : "Ruby split lines from files while iterating each line"
Yet another silly way.
Assuming that the elements are all strings and a certain character, say "#", is not included,
arr.join("#")[/(?<=#)AGC#.+#car(?=#)/].split("#")
# => ["AGC", "xxx", "tcv", "car"]

Select items from arrays of an array with certain indexes

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 } }

return keys from array having same values in ruby

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.

Resources