How to check multiples itens of an array? - ruby

everyone, I'm trying to check an information from every item of an array but I don't know how to do this for all the 4 items, first that's the standard response:
data: [{"optinId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "name": "string", "description": string", "alertMessage": "string", "master": true, "optin": true}]
this example has only 1 item but the true I have has 4, what I have to do is "expect when MASTER=true then OPTIN=true", I started with a simple IF:
if (response["data"][0]["master"]=true)
expect(#response["data"][0]["optin"]).to eql true
end
that solution isn't enough for me because I want to check all 4 items of the array, can someone please help me with a solution?

You can find the item you want with .find method and assert the fields you want.
item = response[:data].find { |item| item[:master] }
expect(item&.dig(:optin)).to eql(true)
This code will assert there must be a item with master true and optin true

try iterating through items in the array, using a for loop or for each
example:
arr = [{"optinId": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "name": "string", "description": string", "alertMessage": "string", "master": true, "optin": true}]
arr.each do |item|
/** your code **/
end

item = #response["data"].find { |item| item["master"]==true }
expect(item["optin"]).to eql true
that solved my problem, thanks to Mehmet Adil Istikbal and everyone else that contribute

Related

Remove elements from a hash array if a value matches a line in a text file

I have a JSON file with a hash in that looks like this
{
"1": {
"organisation": "Example1",
"name": "Name1",
"items": [
{
"name": "Name1",
"html_url": "URL1",
"results": [
"items go here"
]
},
{
"name": "Name2",
"html_url": "URL2",
"results": [
"Items go here"
]
},
I want to delete elements whose html_url matches a list stored in a text file, I have been toying with a delete-if like this.
#org.each do |key,value|
File.open('views/investigated.txt').each do |line|
value['items'].delete_if { |h| h['html_url'] == line }
end
end
but it doesn't seem to alter the array, I'm completely lost, any help would be much appreciated
#Stefan is right, it's better practice to create a separate array with the lines you'd like to delete from the array:
urls_to_delete = []
File.open('views/investigated.txt').each do |line|
urls_to_delete.push(line)
end
Then use that array to remove the lines from the hash:
#org.each do |key,value|
value['items'].delete_if { |h| urls_to_delete.include?(h['html_url']) }
end
Tested it with your example and does exactly what you're trying to achieve.
Maybe you can delete records in one iteration by using each_key on ˙#org˙ and you dont need new array.
Like this:
#org.each_key do |key|
File.open('views/investigated.txt').each do |line|
line.chomp!
#org[key]['items'].delete_if { |h| h['html_url'] == line }
end
end

Merging hash values in an array of hashes based on key

I have an array of hashes similar to this:
[
{"student": "a","scores": [{"subject": "math","quantity": 10},{"subject": "english", "quantity": 5}]},
{"student": "b", "scores": [{"subject": "math","quantity": 1 }, {"subject": "english","quantity": 2 } ]},
{"student": "a", "scores": [ { "subject": "math", "quantity": 2},{"subject": "science", "quantity": 5 } ] }
]
Is there a simpler way of getting the output similar to this except looping through the array and finding a duplicate and then combining them?
[
{"student": "a","scores": [{"subject": "math","quantity": 12},{"subject": "english", "quantity": 5},{"subject": "science", "quantity": 5 } ]},
{"student": "b", "scores": [{"subject": "math","quantity": 1 }, {"subject": "english","quantity": 2 } ]}
]
Rules for merging duplicate objects:
Students are merged on matching "value" (e.g. student "a", student "b")
Students scores on identical subjects are added (e.g. student a's math scores 2 and 10 become 12 when merged)
Is there a simpler way of getting the output similar to this except looping through the array and finding a duplicate and then combining them?
Not that I know of. IF you explain where this data is comeing form the answer may be different but just based on the Array of Hash objects I think you will haev to iterate and combine.
While it is not elegant you could use a solution like this
arr = [
{"student"=> "a","scores"=> [{"subject"=> "math","quantity"=> 10},{"subject"=> "english", "quantity"=> 5}]},
{"student"=> "b", "scores"=> [{"subject"=> "math","quantity"=> 1 }, {"subject"=> "english","quantity"=> 2 } ]},
{"student"=> "a", "scores"=> [ { "subject"=> "math", "quantity"=> 2},{"subject"=> "science", "quantity"=> 5 } ] }
]
#Group the array by student
arr.group_by{|student| student["student"]}.map do |student_name,student_values|
{"student" => student_name,
#combine all the scores and group by subject
"scores" => student_values.map{|student| student["scores"]}.flatten.group_by{|score| score["subject"]}.map do |subject,subject_values|
{"subject" => subject,
#combine all the quantities into an array and reduce using `+`
"quantity" => subject_values.map{|h| h["quantity"]}.reduce(:+)
}
end
}
end
#=> [
{"student"=>"a", "scores"=>[
{"subject"=>"math", "quantity"=>12},
{"subject"=>"english", "quantity"=>5},
{"subject"=>"science", "quantity"=>5}]},
{"student"=>"b", "scores"=>[
{"subject"=>"math", "quantity"=>1},
{"subject"=>"english", "quantity"=>2}]}
]
I know that you specified your expected result but I wanted to point out that making the output simpler makes the code simpler.
arr.map(&:dup).group_by{|a| a.delete("student")}.each_with_object({}) do |(student, scores),record|
record[student] = scores.map(&:values).flatten.map(&:values).each_with_object(Hash.new(0)) do |(subject,score),obj|
obj[subject] += score
obj
end
record
end
#=>{"a"=>{"math"=>12, "english"=>5, "science"=>5}, "b"=>{"math"=>1, "english"=>2}}
With this structure getting the students is as easy as calling .keys and the scores would be equally as simple. I am thinking something like
above_result.each do |student,scores|
puts student
scores.each do |subject,score|
puts " #{subject.capitalize}: #{score}"
end
end
end
The console out put would be
a
Math: 12
English: 5
Science: 5
b
Math: 1
English: 2
There are two common ways of aggregating values in such instances. The first is to employ the method Enumerable#group_by, as #engineersmnky has done in his answer. The second is to build a hash using the form of the method Hash#update (a.k.a. merge!) that uses a block to resolve the values of keys which are present in both of the hashes being merged. My solution uses the latter approach, not because I prefer it to the group_by, but just to show you a different way it can be done. (Had engineersmnky used update, I would have gone with group_by.)
Your problem is complicated somewhat by the particular data structure you are using. I found that the solution could be simplfied and made easier to follow by first converting the data to a different structure, update the scores, then convert the result back to your data structure. You may want to consider changing the data structure (if that's an option for you). I've addressed that issue in the "Discussion" section.
Code
def combine_scores(arr)
reconstruct(update_scores(simplify(arr)))
end
def simplify(arr)
arr.map do |h|
hash = Hash[h[:scores].map { |g| g.values }]
hash.default = 0
{ h[:student]=> hash }
end
end
def update_scores(arr)
arr.each_with_object({}) do |g,h|
h.update(g) do |_, h_scores, g_scores|
g_scores.each { |subject,score| h_scores[subject] += score }
h_scores
end
end
end
def reconstruct(h)
h.map { |k,v| { student: k, scores: v.map { |subject, score|
{ subject: subject, score: score } } } }
end
Example
arr = [
{ student: "a", scores: [{ subject: "math", quantity: 10 },
{ subject: "english", quantity: 5 }] },
{ student: "b", scores: [{ subject: "math", quantity: 1 },
{ subject: "english", quantity: 2 } ] },
{ student: "a", scores: [{ subject: "math", quantity: 2 },
{ subject: "science", quantity: 5 } ] }]
combine_scores(arr)
#=> [{ :student=>"a",
# :scores=>[{ :subject=>"math", :score=>12 },
# { :subject=>"english", :score=> 5 },
# { :subject=>"science", :score=> 5 }] },
# { :student=>"b",
# :scores=>[{ :subject=>"math", :score=> 1 },
# { :subject=>"english", :score=> 2 }] }]
Explanation
First consider the two intermediate calculations:
a = simplify(arr)
#=> [{ "a"=>{ "math"=>10, "english"=>5 } },
# { "b"=>{ "math"=> 1, "english"=>2 } },
# { "a"=>{ "math"=> 2, "science"=>5 } }]
h = update_scores(a)
#=> {"a"=>{"math"=>12, "english"=>5, "science"=>5}
# "b"=>{"math"=> 1, "english"=>2}}
Then
reconstruct(h)
returns the result shown above.
+ simplify
arr.map do |h|
hash = Hash[h[:scores].map { |g| g.values }]
hash.default = 0
{ h[:student]=> hash }
end
This maps each hash into a simpler one. For example, the first element of arr:
h = { student: "a", scores: [{ subject: "math", quantity: 10 },
{ subject: "english", quantity: 5 }] }
is mapped to:
{ "a"=>Hash[[{ subject: "math", quantity: 10 },
{ subject: "english", quantity: 5 }].map { |g| g.values }] }
#=> { "a"=>Hash[[["math", 10], ["english", 5]]] }
#=> { "a"=>{"math"=>10, "english"=>5}}
Setting the default value of each hash to zero simplifies the update step, which follows.
+ update_scores
For the array of hashes a that is returned by simplify, we compute:
a.each_with_object({}) do |g,h|
h.update(g) do |_, h_scores, g_scores|
g_scores.each { |subject,score| h_scores[subject] += score }
h_scores
end
end
Each element of a (a hash) is merged into an initially-empty hash, h. As update (same as merge!) is used for the merge, h is modified. If both hashes share the same key (e.g., "math"), the values are summed; else subject=>score is added to h.
Notice that if h_scores does not have the key subject, then:
h_scores[subject] += score
#=> h_scores[subject] = h_scores[subject] + score
#=> h_scores[subject] = 0 + score (because the default value is zero)
#=> h_scores[subject] = score
That is, the key-value pair from g_scores is merely added to h_scores.
I've replaced the block variable representing the subject with a placeholder _, to reduce the chance of errors and to inform the reader that it is not used in the block.
+ reconstruct
The final step is to convert the hash returned by update_scores back to the original data structure, which is straightforward.
Discussion
If you change the data structure, and it meets your requirements, you may wish to consider changing it to that produced by combine_scores:
h = { "a"=>{ math: 10, english: 5 }, "b"=>{ math: 1, english: 2 } }
Then to update the scores with:
g = { "a"=>{ math: 2, science: 5 }, "b"=>{ english: 3 }, "c"=>{ science: 4 } }
you would merely to the following:
h.merge(g) { |_,oh,nh| oh.merge(nh) { |_,ohv,nhv| ohv+nhv } }
#=> { "a"=>{ :math=>12, :english=>5, :science=>5 },
# "b"=>{ :math=> 1, :english=>5 },
# "c"=>{ :science=>4 } }

Programmatic way to obtain Ruby keywords

I have an Array in Ruby, with all keywords.
For instance:
RUBY_KEYWORDS = %w(
alias and BEGIN begin break case class def defined
do else elsif END end ensure false for if in module
next nil not or redo rescue retry return self super
then true undef unless until when while yield
)
My question is simple:
Is there an in-built way to programmatically access all keywords?
Some of my projects need to run a query against user input,
and it's a bit annoying to have to define the same array in
all these projects.
Try this code :)
RubyToken::TokenDefinitions.select { |definition| definition[1] == RubyToken::TkId }
.map { |definition| definition[2] }
.compact
.sort
# returns :
# ["BEGIN", "END", "__FILE__", "__LINE__", "alias", "and", "begin", "break", "case", "class", "def", "defined?", "do", "else", "elsif", "end", "ensure", "false", "for", "if", "in", "module", "next", "nil", "not", "or", "redo", "rescue", "retry", "return", "self", "super", "then", "true", "undef", "unless", "until", "when", "while", "yield"]
I don't think you can, since it will be defined in the parser.
Your alternative would be to look at the source code: https://github.com/ruby/ruby/blob/ruby_2_1/defs/keywords

How to do hash arithmetic

I want to subtract the ackedOns time from the startOn time in the hash to get how many epoch seconds it took to acknowledge the alert.
Here is the code:
url = "https://xyz"
uri = URI(url)
http = Net::HTTP.new(uri.host, 443)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
req = Net::HTTP::Get.new(uri.request_uri)
response = http.request(req)
jsonResponse = JSON.parse(response.body)
#troubelshooting step to see how many total alerts there are
total = jsonResponse['data']['total']
pp total
# create the collected_alerts hash
collected_alerts = { 'hosts'=> [],'dataPoints' => [], 'startOns' => [], 'ackedOns' => [], 'timeToAcks' => [] }
# iterate through the json
jsonResponse['data']['alerts'].each do |alerts|
# store the interested json values into the appropriate hash
collected_alerts['hosts'] << alerts['host']
collected_alerts['dataPoints'] << alerts['dataPoint']
collected_alerts['startOns'] << alerts['startOn']
collected_alerts['ackedOns'] << alerts['ackedOn']
# Calculate mins it took to acknowledge alert and store it in timeToAcks
# Formula is Ack time - Start time which would give us seconds / 60 to give mins.
alerts['timeToAcks'] = alerts['ackedOn'].zip(alerts['startOn']).map{|a,s| a-s/60}
end
pp collected_alerts
CSV.open("data.csv", "wb") {|csv| collected_alerts.to_a.each {|elem| csv << elem} }
Here is the json response that i am trying to parse
{
"status": 200,
"data": {
"total": 3,
"alerts": [
{
"dataPoint": "average",
"ackedBy": "x",
"dataSourceInstance": "Ping50Packets",
"dataSource": "Ping50Packets",
"host": "x",
"endOn": 0,
"ackedOn": 1392218853,
"dataSourceInstanceId": 400554,
"hostId": 1829,
"type": "alert",
"dataSourceId": 560,
"ackedOnLocal": "2014-02-12 07:27:33 PST",
"id": 6862895,
"startOn": 1392197595,
"thresholds": "> 200",
"endOnLocal": "",
"level": "warn",
"ackComment": "Ack.",
"value": "206.00",
"hostDataSourceId": 137481,
"acked": true,
"hostGroups": [{
"alertEnable": true,
"createdOn": 1367604091,
"id": 106,
"parentId": 105,
"description": "",
"appliesTo": "",
"name": "x",
"fullPath": "x"
}],
"startOnLocal": "2014-02-12 01:33:15 PST"
},
something like;
alerts['tta'] = alerts['ackedOns'].zip(alerts['startOns']).map{|a,s| a-s/60}
Based on your data, you simply need to do:
collected_alerts['timeToAcks'] << (alerts['ackedOn'] - alerts['startOn'])/60
Your origin code had a spelling mistake in one of the field names.
You want to push these values into collected_alerts, not the current alert, right?
Alternatively, after your have looped through all the alerts:
ca = collected_alerts
ca['timeToAcks'] = ca['ackedOns'].zip(ca['startOns']).map{ |ack,st| (ack-st)/60 }
Further, instead of looping through the alerts one at a time and pulling out the pieces, I would do this:
alerts = jsonResponse['data']['alerts']
collected_alerts = {}
%w[host dataPoint startOn ackedOn].each do |field|
collected_alerts[field] = alerts.map{ |alert| alert[field] }
end
Or even better:
alert_fields = %w[host dataPoint startOn ackedOn]
collected_alerts = Hash[ alert_fields.map{ |f| [f,alerts.map{|a| a[f] }] } ]
Explained, roughly in execution order.
alert_fields = %w[ … ] — create a variable referencing an array of string values (space-delimited in the source code), i.e. ["ackedOn", "host", … ]
alerts.map{ |a| … } — Create a new array by taking each value in the alerts array, creating a variable named a that references that value, and use the result of the block as the value in the new array.
a[f] — for each alert in the alerts array, look up the key with the value of f. For example, when f is "ackedOn", this looks up the value of a["ackedOn"].
alert_fields.map{ |f| … } — for each element in the array create a variable named f (for 'field') and run the contents of this block. Create a new array where the contents are, for each entry, whatever the final value of the block results in.
[f, … ] — after creating an array of all the values for a particular field in all the alerts, the result of this block is a two element array that is the name of the field followed by these values. For example, [ "ackedOn", [1,3,16,4,44,7] ].
Hash[ … ] — given an array of two-valued arrays, create a hash mapping the first value in each pair to the second value, i.e. { "ackedOn" => [1,3,16,4,44,7] }
This same Hash[…] method alternatively accepts an even number of arguments, pairing each even-numbered argument with the following one.
With examples:
alert_fields = %w[ackedOn host dataPoint startOn]
#=> ["ackedOn", "host", "dataPoint", "startOn" ]
alerts.map{|a| a[f] }
#=> [5,3,4,5,1,6,8,2,…]
[f,alerts.map{|a| a[f] }]
#=> ["ackedOn",[5,3,4,5,1,6,8,2,…]]
alert_fields.map{ |f| [f,alerts.map{|a| a[f] }] }
#=> [
#=> ["host",['foo','bar',…]],
#=> …
#=> ["ackedOn",[5,3,4,5,1,6,8,2,…]]
#=> ]
Hash[ alert_fields.map{ |f| [f,alerts.map{|a| a[f] }] } ]
#=> {
#=> "host" => ['foo','bar',…],
#=> …
#=> "ackedOn" => [5,3,4,5,1,6,8,2,…]
#=> }

How do I get a hash from an array based on a value in the hash?

How do I grab a hash from an array based on a value in the hash? In this case I want to select the hash that has the lowest score, being potato. I use Ruby 1.9.
[
{ name: "tomato", score: 9 },
{ name: "potato", score: 3 },
{ name: "carrot", score: 6 }
]
You can use Enumerable's min_by method:
ary.min_by {|h| h[:score] }
#=> { name: "potato", score: "3" }
I think your intention is to compare by the number rather than as strings.
array.min_by{|h| h[:score].to_i}
Edit Since the OP changed the question, the answer becomes
array.min_by{|h| h[:score]}
which now makes no difference from Zach Kemp's answer.
Ruby's Enumerable#min_by is definitely the way to go; however, just for kicks, here is a solution based on Enumerable#reduce:
array.reduce({}) do |memo, x|
min_score = memo[:score]
(!min_score || (min_score > x[:score])) ? x : memo
end

Resources