Select array elements that are greater than 5% of a sum - ruby

sum = #products.inject(0){ |sum,item| sum += item['count'] }
#selected = #products.select { |item| (item['count']/sum) >= 0.05 }
I want to select every element from the #products array whose count property is greater than 5% of the sum. #products is an array of hashes.
However, when I use this second line, #selected returns an empty array. After finding no fault with |item| or the #products array itself, I'm inclined to believe it has something to do with trying to use an external variable, sum, inside the .select block. Could someone explain to me why #selected is returning nothing?

Write as below :
#selected = #products.select { |item| (item['count'].to_f/sum) >= 0.05 }
You need to make either item['count'] or sum as floating point number to get floating point number,after the division. Quick example to prove my words in PRY
(arup~>~)$ pry --simple-prompt
>> 12/13
=> 0
>> 12/13.to_f
=> 0.9230769230769231
>> 12.to_f/13
=> 0.9230769230769231
>>

If the counts are integers, item['count']/sum will always be zero due to integer division.
Try the following instead:
#selected = #products.select { |item| item['count'] >= 0.05 * sum }

Related

Ruby: If Values in a hash are equal if statement. Need a hint if possilbe?

Hi there: Ruby Beginner.
So I'm working on a little exercise.
I have this
donation = {
"Randy" => 50,
"Steve" => 50,
"Eddie" => 9,
"Bill" => 12
}
donation.max_by.first { |name, money| money.to_i }
name, money = donation.max_by { |name, money| money.to_i }
puts "#{name} donated the most, at $#{money}!"
But there's a little bug. "Randy" and "Steve" both donated the max, but it outputs "Randy" (because they're the first in key in the for hash, I assume?) and they get all the credit!
I'm thinking the way to go is, I need an IF ELSE statement; IF any? of the "money" values are equal, it moves on. Else, it puts the statement.
SO i guess I am wondering how to compare values?
Select Elements Matching Highest Value
There's more than one way to identify and extract multiple elements that share a maximum value. Based on the semantics you're trying to communicate in your code, one working example is as follows:
max_amt = donation.max_by(&:last)[-1]
donation.select { |name, amt| amt == max_amt }
#=> {"Randy"=>50, "Steve"=>50}
First, we capture the maximum value from the donations Hash. Next, the Hash#select method passes the name and amount of each donation into the block, which returns the Hash elements for which the comparison to the maximum value is truthy. Inside the block, each amount is compared to the maximum value found in the original Hash, allowing all matching key/value pairs to be returned when more than one of them contains a value equal to the max_amt.
As you discovered, max_by returns the first key that has the maximum value. It actually takes a parameter that is the number of elements to return, so doing something like .max_by(donation.size) will return the hash in descending order and you could then go through the elements until the value doesn't match the first (max) value.
But, there's a couple of simpler ways to go about it. If you just want to print the information as you go, one way would be to just iterate through the hash looking for values that match the max value:
mx = donation.values.max
puts "The following people donated the most ($#{mx}):"
donation.each { |k, v| puts k if v == mx }
Output:
The following people donated the most ($50):
Randy
Steve
You could also use .select to match for the max value, which preserves the hash form:
mx = donation.values.max
donation.select { |k, v| v == mx }
=> {"Randy"=>50, "Steve"=>50}
EDIT
Per the follow-up comment, if you use .select to capture the result as a new hash, you can then use conditional logic, loops, etc., to process the data from there. As a very simple example, suppose you want a different message if there's only one top donor vs. multiple:
mx = donation.values.max
max_donors = donation.select { |k, v| v == mx }
if max_donors.size > 1
puts "The following people donated the most ($#{mx}):"
max_donors.keys.each { |name| puts name }
elsif max_donors.size == 1
name, money = max_donors.first
puts "#{name} donated the most, at $#{money}!"
else
puts 'No one donated!'
end

How to solve fibonacci in ruby (HackerRank)

I am trying to solve a Fibonacci solution in HackerRanck.
I am mostly using the inject method.
I first declared an empty array.
I then define a method that takes in a parameter and turns it into a list that stores two initial values, 0 and 1.
The method then takes the last two values and sum them up together and returns the newly-formed list. (I hope I got this logic right)
I was then aiming at calling the method and loop through it this time around summing all the even values in it and returning the final result.
#!/bin/ruby
t = gets.strip.to_i
for a0 in (0..t-1)
n = gets.strip.to_i
end
result = 0
def fibbonacci num
(1..num).inject([0, 1]) { |fib| << fib.last(2).inject(:+)}
end
my_fib_num = fibbonacci n
for i in my_fib_num
if i % 2 == 0
result.inject(0){|sum,x| sum + x }
end
end```
Anything I could be getting wrong here?
I see you are doing unnecessary things over here and you are using inject in completely wrong way. The inject should always be called on an array or a range object. On every loop the result of the loop will be assigned to first parameter (i.e result in the example below) of the block and it will be passed back again to next loop. Finally returns the value in result.
The updated code:
#!/bin/ruby
t = gets.strip.to_i
def fibbonacci(num)
(1..num).inject([0, 1]) { |result| result << result.last(2).inject(:+)}
end
my_fib_num = fibbonacci(t)
result = my_fib_num.inject(:+)
Note: The solution is to solve by using inject.
Ref: https://apidock.com/ruby/Enumerable/inject
May be you can use select and inject together to some the even
t = gets.strip.to_i
def fibbonacci(num)
(1..num).inject([0, 1]) { |fib| fib << fib.last(2).inject(:+)}
end
my_fib_num = fibbonacci(t)
my_fib_num.keep_if{|d| d.even?}.inject(:+)

Using Ruby filter in logstash to sum values of a field everytime that event appears

I'm trying to sum the value of a specific field every time it shows, the field is in this format: [cdr][Service-Information][PS-Information][Service-Data-Container][Accounting-Output-Octets] and its value is a numeric field (it shows the number of bits consumed).
What I'm trying to do is the following:
a = event.get("[cdr][Service-Information][PS-Information][Service-Data-Container][Accounting-Output-Octets]")
if a
sum = 0
a.each_index { |x|
sum += a["amount"]
}
event.set("amount-sum", sum)
end
I'm getting the following error:
Ruby exception occurred: undefined method `each_index' for Integer
I am a newbie in Ruby, so I've got no idea if this code serves for this type of field too.
If a is integer You can use something like this:
a = 123456789
a = a.to_s.split("").map(&:to_i)
sum = 0
a.each do |x|
sum += x
end
p sum #=> 45
And please DO NOT USE brace if block is not in one line. Here I did it with do end just to show how it must be. If You want to use one line block - You must write like that:
a.each {|x| sum += x}
if block > 1 lines than use do/end | else { }

Calculating totals from two different hashes

I have two hashes:
For example, one contains a list of dishes and their prices
dishes = {"Chicken"=>12.5, "Pizza"=>10, "Pasta"=>8.99}
The other is a basket hash i.e. I've selected one pasta and two pizzas:
basket = {"Pasta"=>1, "Pizza"=>2}
Now I am trying to calculate the total cost of the basket but can't seem to get my references right.
Have tried
basket.inject { |item, q| dishes[item] * q }
But keep getting the following error
NoMethodError: undefined method `*' for nil:NilClass
basket.inject { |item, q| dishes[item] * q }
Let's look at the documentation for Enumerable#inject to see what is going on. inject "folds" the collection into a single object, by taking a "starting object" and then repeatedly applying the binary operation to the starting object and the first element, then to the result of that and the second element, then to the result of that and the third element, and so forth.
So, the block receives two arguments: the current value of the accumulator and the current element, and the block returns the new value of the accumulator for the next invocation of the block. If you don't supply a starting value for the accumulator, then the first element of the collection is used.
So, during the first iteration here, since you didn't supply a starting value for the accumulator, the value is going to be the first element; and iteration is going to start from the second element. This means that during the first iteration, item is going to be ['Pasta', 1] and q is going to be ['Pizza', 2]. Let's just run through the example in our heads:
dishes[item] * q # item is ['Pasta', 1]
dishes[['Pasta', 1]] * q # q is ['Pizza', 2]
dishes[['Pasta', 1]] * ['Pizza', 2] # there is no key ['Pasta', 1] in dishes
nil * ['Pizza', 2] # nil doesn't have * method
Ergo, you get a NoMethodError.
Now, I believe, what you actually wanted to do was something like this:
basket.inject(0.0) {|sum, (item, q)| sum + dishes[item] * q }
# ↑↑↑ ↑↑↑ ↑↑↑↑↑
You don't want to accumulate orders, you want to accumulate numbers, so you need to supply a number as the starting value; if you don't, the starting value will be the first element, which is an order, not a number
You were mixing up the meaning of the block parameters
You weren't actually summing anything
Now, while inject is capable of summing (in fact, inject is capable of anything, it is a general iteration operation, i.e. anything you could do with a loop, you can also do with inject), it is usually better to use more specialized operations if they exist. In this case, a more specialized operation for summing does exist, and it is called Enumerable#sum:
basket.sum {|item, q| dishes[item] * q }
But there is a deeper underlying problem with your code: Ruby is an object-oriented language. It is not an array-of-hash-of-strings-and-floats-oriented language. You should build objects that represent your domain abstractions:
class Dish < Struct.new(:name, :price)
def to_s; "#{name}: $#{price}" end
def *(num) num * price end
def coerce(other) [other, price] end
end
require 'bigdecimal'
require 'bigdecimal/util'
dishes = {
chicken: Dish.new('Chicken', '12.5'.to_d),
pizza: Dish.new('Pizza', '10'.to_d),
pasta: Dish.new('Pasta', '8.99'.to_d)
}
class Order < Struct.new(:dish, :quantity)
def to_s; "#{quantity} * #{dish}" end
def total; quantity * dish end
end
class Basket
def initialize(*orders)
self.orders = orders
end
def <<(order)
orders << order
end
def to_s; orders.join("\n") end
def total; orders.sum(&:total) end
private
attr_accessor :orders
end
basket = Basket.new(
Order.new(dishes[:pasta], 1),
Order.new(dishes[:pizza], 2)
)
basket.total
#=> 0.2899e2
Now, of course, for such a simple example, this is overkill. But I hope that you can see that despite this being more code, it is also much much simpler. There is no complex navigation of complex nested structures, because a) there are no complex nested structures and b) all the objects know how to take care of themselves, there is never a need to "take apart" an object to examine its parts and run complex calculations on them, because the objects themselves know their own parts and how to run calculations on them.
Note: personally, I do not think that allowing arithmetic operations on Dishes is a good idea. It is more of a "neat hack" that I wanted to show off in this code snippet.
With Ruby 2.4, you could use Hash(Enumerable)#sum with a block :
basket = {"Pasta"=>1, "Pizza"=>2}
prices = {"Chicken"=>12.5, "Pizza"=>10, "Pasta"=>8.99}
basket.sum{ |dish, quantity| quantity * prices[dish] }
# 28.99
Data structure
dishes
dishes (what I called prices to avoid writing dishes[dish]) is the correct data structure :
Hash lookup is fast
If you want to update the price of a dish, you only have to do it in one place
It's basically a mini database.
basket
basket is also fine as a Hash, but only if you don't oder any dish more than once. If you want to order 2 pizzas, 1 pasta and then 3 pizzas again :
{"Pizza"=>2, "Pasta" => 1, "Pizza" =>3}
=> {"Pizza"=>3, "Pasta"=>1}
you'll lose the first order.
In that case, you might want to use an array of pairs (a 2-element array with dish and quantity) :
basket = [["Pizza", 2], ["Pasta", 1], ["Pizza", 3]]
With this structure, you could use the exact same syntax to get the total as with a Hash :
basket.sum{ |dish, quantity| quantity * prices[dish] }
Try this one
basket.inject(0) do |acc, item|
dish, q = item
acc + (dishes[dish] * q)
end
=> 28.990000000000002
one line
basket.inject(0) { |acc, item| acc + (dishes[item.first] * item.last) }
Your variables for the block are wrong. You have the accumulator and an item (that it's an hash)
2.2.0 :011 > basket.inject(0){ |sum, (item, q)| sum + dishes[item].to_f * q }
=> 28.990000000000002

syntax for .max/.min and sum of hash values

Trying to get a value out of 2d array inside of a hash and determine its max or min value
This is what I have so far
pretend_hash = { 333 => [Dog,19.99], 222=> [Cat,25.55] }
if a == 5 # Loop for view highest priced product"
puts "View highest priced product"
puts pretend_hash.values.max
end
So this returns the highest alphabetical value in the first part of the array by default
Which would be Dog. How do I go about getting access to the 2nd part of the array where 25.55 gets declared the .max value? Something like pretend_hash.values.max[|k,v,z| print z]
or a reverse on something?
The other problem I'm having is iterating through 2nd hash elements and determining the sum. Again callling that 2nd element in the array is something I'm not able to find the syntax for. I mean its not hard to say 19.99+25.55 = some variable and slam it into a puts. I'm assuming its something like:
pretend_hash.sum{|k,v,z| ?? }
#I assume it iterates through the z element
#and adds all current z values and returns the sum?
Min/max can be solved like this:
pretend_hash.values.sort{|x,y| x[1] <=> y[1]}[0] # Gives min, -1 will be max
Or:
pretend_hash.values.map{|x| x[1]}.max
And sum can be had like this:
pretend_hash.values.inject(0){|sum,x| sum+x[1]}
pretend_hash = { 333 => ["Dog",19.99], 222=> ["Cat",25.55] }
key,value = pretend_hash.max{|a,b| b[1] <=> a[1]}
puts key.to_s
puts value.join(',')
puts pretend_hash.inject(0){|sum, hash| sum + hash[1][1]}.to_s
#returns:
#222
#Cat,25.55
#45.54

Resources