Find and replace specific hash and it's values within array - ruby

What is the most efficient method to find specific hash within array and replace its values in-place, so array get changed as well?
I've got this code so far, but in a real-world application with loads of data, this becomes the slowest part of application, which probably leaks memory, as unbounded memory grows constantly when I perform this operation on each websocket message.
array =
[
{ id: 1,
parameters: {
omg: "lol"
},
options: {
lol: "omg"
}
},
{ id: 2,
parameters: {
omg: "double lol"
},
options: {
lol: "double omg"
}
}
]
selection = array.select { |a| a[:id] == 1 }[0]
selection[:parameters][:omg] = "triple omg"
p array
# => [{:id=>1, :parameters=>{:omg=>"triple omg"}, :options=>{:lol=>"omg"}}, {:id=>2, :parameters=>{:omg=>"double lol"}, :options=>{:lol=>"double omg"}}]

This will do what you're after looping through the records only once:
array.each { |hash| hash[:parameters][:omg] = "triple omg" if hash[:id] == 1 }
You could always expand the block to handle other conditions:
array.each do |hash|
hash[:parameters][:omg] = "triple omg" if hash[:id] == 1
hash[:parameters][:omg] = "quadruple omg" if hash[:id] == 2
# etc
end
And it'll remain iterating over the elements just the once.
It might also be you'd be better suited adjusting your data into a single hash. Generally speaking, searching a hash will be faster than using an array, particularly if you've got unique identifier as here. Something like:
{
1 => {
parameters: {
omg: "lol"
},
options: {
lol: "omg"
}
},
2 => {
parameters: {
omg: "double lol"
},
options: {
lol: "double omg"
}
}
}
This way, you could just call the following to achieve what you're after:
hash[1][:parameters][:omg] = "triple omg"
Hope that helps - let me know how you get on with it or if you have any questions.

Related

Convert object with array values into array of object

I do have this kind of params
params = { "people" =>
{
"fname" => ['john', 'megan'],
"lname" => ['doe', 'fox']
}
}
Wherein I loop through using this code
result = []
params["people"].each do |key, values|
values.each_with_index do |value, i|
result[i] = {}
result[i][key.to_sym] = value
end
end
The problem on my code is that it always gets the last key and value.
[
{ lname: 'doe' },
{ lname: 'fox' }
]
i want to convert it into
[
{fname: 'john', lname: 'doe'},
{fname: 'megan', lname: 'fox'}
]
so that i can loop through of them and save to database.
Your question has been answered but I'd like to mention an alternative calculation that does not employ indices:
keys, values = params["people"].to_a.transpose
#=> [["fname", "lname"], [["john", "megan"], ["doe", "fox"]]]
keys = keys.map(&:to_sym)
#=> [:fname, :lname]
values.transpose.map { |val| keys.zip(val).to_h }
#=> [{:fname=>"john", :lname=>"doe"},
# {:fname=>"megan", :lname=>"fox"}]
result[i] = {}
The problem is that you're doing this each loop iteration, which resets the value and deletes any existing keys you already put there. Instead, only set the value to {} if it doesn't already exist.
result[i] ||= {}
In your inner loop, you're resetting the i-th element to an empty hash:
result[i] = {}
So you only end up with the data from the last key-value-pair, i.e. lname.
Instead you can use this to only set it to an empty hash if it doesn't already exist:
result[i] ||= {}
So the first loop through, it gets set to {}, but after that, it just gets set to itself.
Alternatively, you can also use
result[i] = {} if !result[i]
which may or may not be more performant. I don't know.

Show the structure of a large nested hash

I have a large hash which I'm trying to inspect, but because there are so many values it's hard to visually see what's going on.
For example say this is the hash:
{
days: {
monday: [1,2,3,4], # There are thousands of values here
tuesday: [1,2,3,4]
},
movies: {
action: ['Avengers', 'My Little Pony'],
adventure: ['Dorra The Explorer'],
comedy: ['Star Wars']
},
data_quality: 0.9,
verified: true
}
Now there is something going wrong and I need to examine what's going on here. It could be that I'm missing a movie category, a day of the week, or something in another field.
Because the Arrays are thousands of items long I can't just look at them to see what's missing.
Ideally I would like something like this:
{
days: {
monday: Array,
tuesday: Array
},
movies: {
action: Array,
adventure: Array,
comedy: Array
},
data_quality: Float,
verified: TrueClass
}
This would make the data a lot easier to analyse.
This is the method I'm currently using:
def hash_keys(hash)
unless hash.is_a?(Hash)
return hash.class
end
keys_hash = {}
hash.each do |key, value|
keys_hash[key] = hash_keys(value)
end
keys_hash
end
It's a recursive method which will run itself if the value is a hash, and return the values class otherwise.
The result for the sample input matches the expected output, however there is room for improvement. Like if all values in the Array are the same then show the value type (e.g. 'Array of ints') or if the array contains similar hashes, then what do those hashes look like?
I think #transform_values is perfect variant to help here:
def deep_values_transform(hash)
hash.transform_values do |value|
if value.is_a?(Hash)
deep_values_transform(value)
else
value.class
end
end
end
> hash = {
days: {
monday: [1,2,3,4],
tuesday: [1,2,3,4]
},
movies: {
action: ['Avengers', 'My Little Pony'],
adventure: ['Dorra The Explorer'],
comedy: ['Star Wars']
},
data_quality: 0.9,
verified: true
}
> deep_values_transform hash
=> {:days=>{:monday=>Array, :tuesday=>Array}, :movies=>{:action=>Array, :adventure=>Array, :comedy=>Array}, :data_quality=>Float, :verified=>TrueClass}

Ruby 2.5 efficient way to delete ruby key if it contains a hash with only one key/val pair

Assuming a data structure that looks like the following:
foo = {
'first': {
'bar': 'foo'
},
'second': {
'bar': 'foobar',
'foo': 'barfoo'
},
'third': {
'test': 'example'
}
}
I want to remove all keys from the Hash foo that contain an entry that has only one key/val pair. In this particular case, after the operation is done, foo should only have left:
foo = {
'second': {
'bar': 'foobar',
'foo': 'barfoo'
}
}
as foo['first'] and foo['third'] only contain one key/val pair.
Option 1 - delete_if
foo.delete_if { |_, inner| inner.one? }
delete_if is destructive so it mutates the original hash
This will let through empty hashes
Option 2 - reject
This doesn't mutate any more:
foo = foo.reject { |_, inner| inner.one? }
This will let through empty hashes
Option 3 - select
No mutation plus different operator:
foo = foo.select { |_, inner| inner.size > 1 }
Option 4 - many? - Rails only
foo = foo.select { |_, inner| inner.many? }
If you're using Rails it defines #many? for you which is any array with more than 1 item
Other Notes
Used _ for unused variables as that's a way of showing "this is irrelevant"
Named the variable inner - convinced there's a better name but value could be confusing
Just a pair of option more, letting apart the way to check the condition.
Using Hash#keep_if
foo.keep_if{ |_, v| v.size > 1 }
And a more complicated, Enumerable#each_with_object:
foo.each_with_object({}){ |(k,v), h| h[k] = v if v.size > 1 }

Lazy enumerator for nested array of hashes

Suppose I have an Array like this
data = [
{
key: val,
important_key_1: { # call this the big hash
key: val,
important_key_2: [
{ # call this the small hash
key: val,
},
{
key: val,
},
]
},
},
{
key: val,
important_key_1: {
key: val,
important_key_2: [
{
key: val,
},
{
key: val,
},
]
},
},
]
I want to create a lazy enumerator that would return the next small hash on each #next, and move on to the next big hash and do the same when the first big hash reaches the end
The easy way to return all the internal hashes that I want would be something like this
data[:important_key_1].map do |internal_data|
internal_data[:important_key_2]
end.flatten
Is there someway to do this or do I need to implement my own logic ?
This returns a lazy enumerator which iterates over all the small hashes :
def lazy_nested_hashes(data)
enum = Enumerator.new do |yielder|
data.each do |internal_data|
internal_data[:important_key_1][:important_key_2].each do |small_hash|
yielder << small_hash
end
end
end
enum.lazy
end
With your input data and a val definition :
#i = 0
def val
#i += 1
end
It outputs :
puts lazy_nested_hashes(data).to_a.inspect
#=> [{:key=>3}, {:key=>4}, {:key=>7}, {:key=>8}]
puts lazy_nested_hashes(data).map { |x| x[:key] }.find { |k| k > 3 }
#=> 4
For the second example, the second big hash isn't considered at all (thanks to enum.lazy)

How effectively join ruby hashes recieved from json lists

I'm trying to make page with table which content is data from two arrays.
I have two lists(arrays) with hashes:
arr1 = [
{ "device"=>100, "phone"=>"12345" },
...,
{ "device"=>102, "phone"=>"12346" }
]
arr2 = [
{ "device"=>100, "type"=>"mobile", "name"=>"nokia" },
...,
{ "device"=>102, "type"=>"VIOP", "name"=>"smth" }
]
How can I join hashes from arr1 and arr2 by "device" to get a result array:
result = [
{ "device"=>100, "phone"=>"12345", "type"=>"mobile", "name"=>"nokia" },
...,
{ "device"=>102, "phone"=>"12346", "type"=>"VIOP", "name"=>"smth" }
]
Page which consist table with result array, loads very slowly and I need to find the fastest way to generate result_array.
Help me please.
This would work:
(arr1 + arr2).group_by { |i| i["device"] }.map { |d,(i1,i2)| i1.merge(i2)}
#=> [{"device"=>100, "phone"=>"12345", "type"=>"mobile", "name"=>"nokia"}, {"device"=>102, "phone"=>"12346", "type"=>"VIOP", "name"=>"smth"}]
Multiple ways to tackle it. Here is a quite readable way to do it:
# prepare an index hash for easier access of data by device
first_by_device = arr1.group_by {|a| a['device'] }
# build a new array joining both data hashes for each item
result = arr2.map do |item|
device = item['device']
item.merge first_by_device(device)
end
(arr1 + arr2).group_by { |i| i["device"] }.values.map{|x|x.reduce(&:merge)}
Maybe it's not the prettiest one, but it works:
result = arr1.collect{|a| h = arr2.select{|b| b["device"] ==
a["device"]}.first; h ? a.merge(h) : a }
Do you need something faster for large amount of data?
h = Hash.new
arr1.each do |a|
h[ a["device" ] ] ||= Hash.new
h[ a["device" ] ].merge!(a)
end
arr2.each do |a|
h[ a["device" ] ] ||= Hash.new
h[ a["device" ] ].merge!(a)
end
result = h.values

Resources