compare different objects of array with same value in ruby - ruby

I have a usecase, where I have two different array of object which consist of same attributes with different/same values.
Ex:
x = [#<User _id: 32864efe, question: "comments", answer: "testing">]
y = [#<ActionController::Parameters {"question"=>"comments", "answer"=>"testing"} permitted: true>}>]
Now, When I am trying to get the difference between these two objects I am expecting difference to be nil when object consists of same value, however it is returning the below response.
x - y => [#<User _id: 32864efe, question: "comments", answer: "testing">]
In some cases, I may have more than one User object in x object. In that case, it should return the difference.
Can you please suggest how we can handle this. Any help would be appreciated.

Is this what you're looking for?
def compare_arrays(array1, array2)
result = []
# Iterate through each object in array1
array1.each do |obj1|
# Check if the object exists in array2
matching_obj = array2.find { |obj2| obj1 == obj2 }
# If no matching object was found, add the object to the result array
result << obj1 unless matching_obj
end
result
end
This function iterates through each object in array1 and checks if there is a matching object in array2. If no matching object is found, the object is added to the result array.
You can use this function like this:
array1 = [{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }]
array2 = [{ id: 1, name: 'John' }]
result = compare_arrays(array1, array2)
# result is [{ id: 2, name: 'Jane' }]

Related

How to access two elements within nested hashes within an array?

I have the following array with nested hashes:
pizza = [
{ flavor: "cheese", extras: { topping1: 1, topping2: 2, topping3: 3} },
{ flavor: "buffalo chicken", extras: { topping1: 1, topping2: 2, topping3: 3} } } ]
If want to verify that I can get an order of "buffalo chicken" pizza with two toppings. I use the .map method to iterate through the array of hashes to verify that the "flavor" I want and the "extras" I want ( 2 toppings) are available. Bingo! The code I use works, returns true, and indeed these two elements are available. BUT, if I want to check if the "buffalo chicken" flavor is available and 5 toppings are also available, then it should return false, but instead, I get an Error message that says:
Failure Error: expect(Party).not_to be_available(pizza, "buffalo chicken", :toppings5) to return false, got []
Here is my code:
def self.available?(pizza, flavor, extra)
pizza.map { |x| x if x[:flavor] == flavor && x[:extra] == extra }
end
I'm trying to figure out why I get [] returned rather than false. Perhaps there is something I'm not understanding with the way .map is being used to iterate through my array of hashes? Without changing the structure of my array of hashes, could someone please help me understand?
You have several problems here:
The keys in the hash must be unique, so the two first toppings keys are ignored. Here is an example of a wrong hash { key: 1, key: 2, key: 3 } it becomes { key: 3 }.
You must not use hash as the name of a variable in any case, it's a method.
To find an element in an array of hashes, you can use the find method, e.g.:
>> h = [{ f: "cheese", extras: [1,2,3] }, { f: "buffalo", extras: [1,3] }]
>> h.find { |h| h[:f] == "cheese" && h[:extras].size > 2 }
=> {:f=>"cheese", :extras=>[1, 2, 3]}
There are a lot of methods to iterate over an array or hash. Read more about Enumerable module. Also don't be lazy and check documentation.

Ruby function to get specific item in array and dynamically get nested data

If I have an array of hashes that looks like this
array = [{
name: 'Stan',
surname: 'Smith',
address: {
street: 'Some street',
postcode: '98877',
#...
}
}, {
#...
}]
can you write a function to get a specific item in an array, iterate over it and dynamically retrieve subsequently nested data?
This example doesn't work, but hopefully better explains my question:
def getDataFromFirstItem(someVal)
array(0).each{ |k, v| v["#{ someVal }"] }
end
puts getDataFromFirstItem('name')
# Expected output: 'Stan'
For context, I'm trying to create a Middleman helper so that I don't have to loop through a specific array that only has one item each time I use it in my template. The item (a hash) contains a load of global site variables. The data is coming from Contentful, within which everything is an array of entries.
Starting in ruby 2.3 and greater, you can use Array#dig and Hash#dig which both
Extracts the nested value specified by the sequence of idx objects by calling dig at each step, returning nil if any intermediate step is nil.
array = [{
name: 'Stan',
surname: 'Smith',
address: {
street: 'Some Street',
postcode: '98877'
}
}, {
}]
array.dig(0, :name) # => "Stan"
array.dig(0, :address, :postcode) # => "98877"
array.dig(0, :address, :city) # => nil
array.dig(1, :address, :postcode) # => nil
array.dig(2, :address, :postcode) # => nil
Please try this
array = [{
name: 'Stan',
surname: 'Smith',
address: {
street: 'Some street',
postcode: '98877',
#...
}
},
{
name: 'Nimish',
surname: 'Gupta',
address: {
street: 'Some street',
postcode: '98877',
#...
}
}
]
def getDataFromFirstItem(array, someVal)
array[0][someVal]
end
#Run this command
getDataFromFirstItem(array, :name) # => 'Stan'
#Please notice I send name as a symbol instead of string because the hash you declared consists of symbol keys
#Also if you want to make a dynamic program that works on all indexes of an array and not on a specific index then you can try this
def getDataFromItem(array, index, someVal)
if array[index]
array[index][someVal]
end
end
getDataFromItem(array, 0, :name) # => Stan
getDataFromItem(array, 1, :name) # => Nimish
getDataFromItem(array, 2, :name) # => nil
Hope this works, Please let me know if you still faces any issues

Is there any way to check if hashes in an array contains similar key value pairs in ruby?

For example, I have
array = [ {name: 'robert', nationality: 'asian', age: 10},
{name: 'robert', nationality: 'asian', age: 5},
{name: 'sira', nationality: 'african', age: 15} ]
I want to get the result as
array = [ {name: 'robert', nationality: 'asian', age: 15},
{name: 'sira', nationality: 'african', age: 15} ]
since there are 2 Robert's with the same nationality.
Any help would be much appreciated.
I have tried Array.uniq! {|e| e[:name] && e[:nationality] } but I want to add both numbers in the two hashes which is 10 + 5
P.S: Array can have n number of hashes.
I would start with something like this:
array = [
{ name: 'robert', nationality: 'asian', age: 10 },
{ name: 'robert', nationality: 'asian', age: 5 },
{ name: 'sira', nationality: 'african', age: 15 }
]
array.group_by { |e| e.values_at(:name, :nationality) }
.map { |_, vs| vs.first.merge(age: vs.sum { |v| v[:age] }) }
#=> [
# {
# :name => "robert",
# :nationality => "asian",
# :age => 15
# }, {
# :name => "sira",
# :nationality => "african",
# :age => 15
# }
# ]
Let's take a look at what you want to accomplish and go from there. You have a list of some objects, and you want to merge certain objects together if they have the same ethnicity and name. So we have a key by which we will merge. Let's put that in programming terms.
key = proc { |x| [x[:name], x[:nationality]] }
We've defined a procedure which takes a hash and returns its "key" value. If this procedure returns the same value (according to eql?) for two hashes, then those two hashes need to be merged together. Now, what do we mean by "merge"? You want to add the ages together, so let's write a merge function.
merge = proc { |x, y| x.dup.tap { |x1| x1[:age] += y[:age] } }
If we have two values x and y such that key[x] and key[y] are the same, we want to merge them by making a copy of x and adding y's age to it. That's exactly what this procedure does. Now that we have our building blocks, we can write the algorithm.
We want to produce an array at the end, after merging using the key procedure we've written. Fortunately, Ruby has a handy function called each_with_object which will do something very nice for us. The method each_with_object will execute its block for each element of the array, passing in a predetermined value as the other argument. This will come in handy here.
result = array.each_with_object({}) do |x, hsh|
# ...
end.values
Since we're using keys and values to do the merge, the most efficient way to do this is going to be with a hash. Hence, we pass in an empty hash as the extra object, which we'll modify to accumulate the merge results. At the end, we don't care about the keys anymore, so we write .values to get just the objects themselves. Now for the final pieces.
if hsh.include? key[x]
hsh[ key[x] ] = merge.call hsh[ key[x] ], x
else
hsh[ key[x] ] = x
end
Let's break this down. If the hash already includes key[x], which is the key for the object x that we're looking at, then we want to merge x with the value that is currently at key[x]. This is where we add the ages together. This approach only works if the merge function is what mathematicians call a semigroup, which is a fancy way of saying that the operation is associative. You don't need to worry too much about that; addition is a very good example of a semigroup, so it works here.
Anyway, if the key doesn't exist in the hash, we want to put the current value in the hash at the key position. The resulting hash from merging is returned, and then we can get the values out of it to get the result you wanted.
key = proc { |x| [x[:name], x[:nationality]] }
merge = proc { |x, y| x.dup.tap { |x1| x1[:age] += y[:age] } }
result = array.each_with_object({}) do |x, hsh|
if hsh.include? key[x]
hsh[ key[x] ] = merge.call hsh[ key[x] ], x
else
hsh[ key[x] ] = x
end
end.values
Now, my complexity theory is a bit rusty, but if Ruby implements its hash type efficiently (which I'm fairly certain it does), then this merge algorithm is O(n), which means it will take a linear amount of time to finish, given the problem size as input.
array.each_with_object(Hash.new(0)) { |g,h| h[[g[:name], g[:nationality]]] += g[:age] }.
map { |(name, nationality),age| { name:name, nationality:nationality, age:age } }
[{ :name=>"robert", :nationality=>"asian", :age=>15 },
{ :name=>"sira", :nationality=>"african", :age=>15 }]
The two steps are as follows.
a = array.each_with_object(Hash.new(0)) { |g,h| h[[g[:name], g[:nationality]]] += g[:age] }
#=> { ["robert", "asian"]=>15, ["sira", "african"]=>15 }
This uses the class method Hash::new to create a hash with a default value of zero (represented by the block variable h). Once this hash heen obtained it is a simple matter to construct the desired hash:
a.map { |(name, nationality),age| { name:name, nationality:nationality, age:age } }

Rails: Grouping Model records, then manipulating values

I am grouping Model instances, by attribute, then manipulating the hash values.
Product.create(id: 1, name: "alpha", value: "apple")
Product.create(id: 2, name: "beta", value: "bongo")
...
We want the form: [["alpha"],[["apple"],[1]]],[[beta],[["bongo"],[2]]]...]
array = []
array1 = []
Product.all.group_by(&:name).each do |a|
a[1].each do |b|
array1 << [b.value,b.id]
end
array << [a[0],array1]
array1 = []
end
Where a and b are iterator variables, array1 contains the ith a[1] values, and array contains the desired output structure.
This works, but is ugly. Can you accomplish this more cleanly?
array = Product.all.group_by(&:name).map { |name, products|
[name, products.map { |product| [product.value, product.id] }]
}
I believe this is what you want, but not 100% sure. Please try to use descriptive names, it takes a lot of effort to figure what array1, b and similar non-identifying identifiers are. It is also nice if you post an example of the output structure.

Dashing reading out of 2 different CSV's

Hello everyone I'm currently trying to get my Dashboard working properly, however I cannot figure out a way to get the values into something my List Widget can read.
begin
id = 1
names.each do |item|
label = names[id][0] #names = names.csv path
value = host_status[id]['status'] #host_status = host_status.csv path
items = { label: label, value: value }
id += 1
end
rescue
end
send_event('hosts', { items: items })
So what this script should do is :
write the host_status.csv with the values it gets from the status.cgi (working)
iterate through both the host_status.csv and names.csv getting values from both of them
output should be something like this (label comes from names.csv, value from host_status.csv) =>
{label: "localhost", value: "UP"}, {label: "USV", value: "UP"}
The list widget needs something like an Array in a Hash with the keys label and value as far as I can tell, however my script doesnt return anything is there something like a push method for hashes?
I'm assuming here that names is an array of arrays [['foo'], ['bar']]
and that host_status is an array of objects [{'status' => 'foo'}, {'status' => 'bar'}]
you should just be able to do
names = [['foo'], ['bar']]
host_status = [{'status' => 'foo'}, {'status' => 'bar'}]
labels = names.map(&:first) # ['foo', 'bar']
values = host_status.map {|s| s['status'] } # ['foo', 'bar']
# zip zips together two arrays [['foo', 'foo'], ['bar', 'bar']]
# inject iterates over the array and returns a new data structure
items = labels.zip(values).inject([]) do |memo, (k,v)|
memo.push({label: k, value:v})
memo
end
You should just be able to run that code sample in an irb session.
Enumerable#map:http://www.ruby-doc.org/core-1.9.3/Enumerable.html#method-i-map
Enumerable#zip: http://www.ruby-doc.org/core-1.9.3/Enumerable.html#method-i-zip
Enumerable#inject: http://www.ruby-doc.org/core-1.9.3/Enumerable.html#method-i-inject

Resources