I have four sizes, each are in a hash by their square feet:
# The sizes. Sizes are in sq ft (min, max)
size_hash = {
'Large' => [70,139],
'Medium' => [15,69],
'Small' => [1,14]
}
If I'm given the number 40, how can I return Medium as the size from the array?
Do I need to do something like this?:
# The sizes. Sizes are in sq ft (min, max)
size_hash = {
[70..139] => 'Large',
#... etc
}
You could use a proc:
size_store = proc{ |n|
case n
when 70..139
'Large'
when 15..69
'Medium'
when 1..14
'Small'
end
}
# USAGE: size_store[40]
size_hash.find{|_, (min, max)| (min..max) === 40}[0]
# => "Medium"
But I think it is a better idea to store ranges instead of min and max in the first place.
size_hash = {
'Large' => 70..139,
'Medium' => 15..69,
'Small' => 1..14,
}
size_hash.find{|_, r| r === 40}[0]
# => "Medium"
Yet another solution taking care of edge cases ...
#size_hash = {
'Large' => 70..139,
'Medium' => 15..69,
'Small' => 1..14,
}
some_value = #size_hash["Small"]
#min = some_value.first
#max = some_value.last
#size_hash.each_pair do |k, v|
#min = [#min, v.first].min
#max = [#max, v.last].max
end
puts "size_hash goes from #{#min} to #{#max}"
# Given a number, return the name of the range which it belongs to.
def whichSize(p_number)
#size_hash.each_pair do |k, v|
return k if v.include?(p_number)
end
return "below minimum" if p_number < #min
"above maximum" if p_number > #max
end
# test
[-10, 0, 1, 10, 14, 15, 20, 69, 70, 80, 139, 140, 1000].each do |e|
puts "size of #{sprintf("%4s", e)} is #{whichSize(e)}"
end
$ ruby -w t.rb
size_hash goes from 1 to 139
size of -10 is below minimum
size of 0 is below minimum
size of 1 is Small
size of 10 is Small
size of 14 is Small
size of 15 is Medium
size of 20 is Medium
size of 69 is Medium
size of 70 is Large
size of 80 is Large
size of 139 is Large
size of 140 is above maximum
size of 1000 is above maximum
If you stored ranges as keys, you could do this:
size_hash = Hash.new {|hash, key| hash.find {|range,_| range.cover?(key) }.last }.merge({
(1..14) => 'Small',
(15..69) => 'Medium',
(70..139) => 'Large'
})
This sets a default proc in the hash, so when you look up a value, like size_hash[9], the first range covering that value is returned. Bear in mind that this doesn't handle out-of-range errors.
Related
I have this greedy algorithm :
banknotes = { 5 => 6, 10 => 7, 20 => 8, 50 => 9 }
amount = 335
a = CardCalculation.new(banknotes: banknotes)
a.greedy_alg( amount: amount )
class CardCalculation
def greedy_alg( amount )
result = {}
banknotes.each do | face, value |
needed_value = amount - hash_summator( result )
quantity = ( needed_value / face )
quantity = value if quantity > value
result[face] = quantity
end
result
end
def hash_summator(hash)
hash.inject(0){|memo, (k, v)| memo += k * v}
end
and in a result I see
result = { 50 => 6, 20 => 1, 10 => 1, 5 => 1 }
how you can see, I took 335 (amount:), went through the hash and selected the maximum from each key and its value.
but now I need ‘spreading’ algorithm, I need this result, for example:
result = { 50 => 4, 20 => 4, 10 => 4, 5 => 3}
I need the middle number of each key .. I have thoughts about the loop but maybe someone has ideas?
This solution could be a starting point, I didn't test for avoiding infinite loop:
banknotes = { 5 => 6, 10 => 7, 20 => 8, 50 => 9 }
amount = 335
min = banknotes.keys.min
init_avg = amount / banknotes.keys.sum # calculate the initial average
result = banknotes.transform_values { init_avg } # initialize with average
delta = amount - result.sum { |k,v| k * v }
if delta > 0 # start distribution
loop do
banknotes.keys.reverse_each do |val|
if delta - val >= 0 and result[val] + 1 < banknotes[val]
delta -= val
result[val] += 1
end
end
break if delta < min
end
end
result
#=> {5=>3, 10=>4, 20=>4, 50=>4}
finished version
banknotes = {5 => 10, 10 => 10, 20 => 10, 50 => 10, 100 => 5}
amount = 435
def spreaded_alg(amount)
validate_money!(amount: amount)
min = banknotes.keys.min
avg = amount / banknotes.keys.sum.round(0)
result = banknotes.transform_values {avg}
delta = amount - result.sum {|k, v| k * v}
if delta > 0
loop do
banknotes.keys.each do |value|
if delta - value >= 0 && result[value] > banknotes[value]
delta += value
result[value] -= 1
end
if delta - value >= 0 && result[value] + 1 <= banknotes[value]
delta -= value
result[value] += 1
end
end
break if delta < min
end
end
result
end
I'm trying to solve the following:
"You are given a dictionary/hash/object containing some languages and your test results in the given languages. Return the list of languages where your test score is at least 60, in descending order of the results.
Examples:
{"Java" => 10, "Ruby" => 80, "Python" => 65} --> ["Ruby", "Python"]
{"Hindi" => 60, "Dutch" => 93, "Greek" => 71} --> ["Dutch", "Greek", "Hindi"]
{"C++" => 50, "ASM" => 10, "Haskell" => 20} --> []
I am having trouble sorting into descending order by results. Here is what I have so far
def my_languages(results)
array = []
results.each { |a,b|
results.values.sort.reverse
if b.to_i >= 60
array << a
end
}
return array
end
Not the most elegant solution but I am a complete Ruby newbie (and Stack Overflow newbie too - sorry!) Any advice would be much appreciated!
You are kinda mixing the sort and filtering out phases. My solution
Filter results with value >= 60
Sort for values (descending, -v)
Extract the first element for every array (the language name)
def my_languages(results)
results.select { |k, v| v >= 60 }.sort_by { |(k,v)| -v }.map(&:first)
end
h = { "Java" => 10, "Ruby" => 80, "Python" => 65 }
h.select { |_,v| v >= 60 }.
keys.
sort_by { |k| -h[k] }
#=> ["Ruby", "Python"]
The steps are as follows.
g = h.select { |_,v| v >= 60 }
#=> {"Ruby"=>80, "Python"=>65}
a = g.keys
#=> ["Ruby", "Python"]
a.sort_by { |k| -h[k] }
#=> ["Ruby", "Python"]
If you don't care for -h[k] two alternative follow.
h.select { |_,v| v >= 60 }.
keys.
sort_by { |k| h[k] }.
reverse
and
a = h.select { |_,v| v >= 60 }.
keys
a.max_by(a.size) { |k| h[k] }
I doubt that one would notice any significant difference in performance among the three.
Enumerable#max_by, min_by, max and min have been permitted to have an optional argument since Ruby v2.2.
To make it faster I would check minimum value when mapping:
hash = {"Hindi" => 60, "Dutch" => 93, "Greek" => 71}
hash.sort.map { |arr| arr[0] if arr[1] >= 60 }.compact
# or imo cleaner
hash.sort.select { |a| a[1] >= 60 }.map(&:first)
Lets say I have the code below. Is there any code that will turn the values into percents of the sum of all the values. For example, since the sum of all the points is 200, Harrison will be 0.4 (40%)
kids_points {
Harrison: 80,
Jack: 70,
Justin: 30,
Max: 20,
}
Here is a way :
kids_points = {
Harrison: 80,
Jack: 70,
Justin: 30,
Max: 20,
}
def calculate_average(hash = arg.dup)
sum_of_values = calculate_sum(hash.values)
hash.each { |k,v| hash[k] = (100 * v) / sum_of_values }
end
def calculate_sum(enum)
enum.reduce(:+).to_f
end
calculate_average(kids_points)
# => {:Harrison=>40.0, :Jack=>35.0, :Justin=>15.0, :Max=>10.0}
Read this Enumerable#reduce and Hash#values method.
If you use active_support you can do this:
require 'active_support/all'
sum = hash.values.sum
hash.each { |k,v| hash[k] = (100.0 * v) / sum }
Note that 100.0 is necessary when percentages are not integer (most cases) or they won't sum up 100. Example:
hash = {
Harrison: 60,
Jack: 60,
Justin: 50,
Max: 50
}
# With 100.0
# => {:Harrison=>27.272727272727273, :Jack=>27.272727272727273, :Justin=>22.727272727272727, :Max=>22.727272727272727}
# With 100
# => {:Harrison=>27, :Jack=>27, :Justin=>22, :Max=>22}
I have an instance variable #limit which must be greater than 0 and no greater than 20. I currently have code like this:
#limit = (params[:limit] || 10).to_i
#limit = 20 if #limit > 20
#limit = 0 if #limit < 0
This looks ugly. Is there a better way to restrict an integer to a range of values?
Comparable#clamp is available in Ruby 2.4.
3.clamp(10, 20)
=> 10
27.clamp(10, 20)
=> 20
15.clamp(10, 20)
=> 15
How about using Enumerable#min, Enumerable#max?
For example, to limit the value in range 0..10:
x = 100
[[10, x].min, 0].max
# => 10
x = -2
[[10, x].min, 0].max
# => 0
x = 5
[[10, x].min, 0].max
# => 5
Alternative, Using Enumerable#sort:
x = 100
[x, 0, 10].sort[1]
# => 10
x = -2
[x, 0, 10].sort[1]
# => 0
x = 5
[x, 0, 10].sort[1]
# => 5
Here is a quick benchmark to show which method we should use. Because someone will inevitably say "Use sort_by because it's faster than sort", I added it. sort_by is only faster than sort when dealing with complex objects. Basic objects, like integers and strings should be handled by sort.
require 'fruity'
class Numeric
def clamp(min, max)
self < min ? min : self > max ? max : self
end
end
compare do
min_max { [[10, 100].min, 0].max }
sort { [100, 0, 10].sort[1] }
sort_by { [100, 0, 10].sort_by{ |v| v }[1] }
clamp_test { 10.clamp(0, 100) }
original {
limit = 10
limit = 100 if limit > 100
limit = 0 if limit < 0
limit
}
end
With the results being:
Running each test 65536 times. Test will take about 8 seconds.
original is faster than clamp_test by 2x ± 1.0
clamp_test is faster than sort by 6x ± 1.0
sort is faster than min_max by 2x ± 0.1
min_max is faster than sort_by by 2x ± 0.1
Sometimes ugly is better.
If you feel like monkey patching a method, you might do something like this:
class Numeric
def clamp(min, max)
self < min ? min : self > max ? max : self
end
end
# usage
#limit = (params[:limit] || 10).clamp(0, 20)
For convenience, here's a monkey-patch for the "ugly and better" solution that wins the Tin Man's benchmark elsewhere on this page. (It should be a tiny bit faster by returning immediately if the boundary's exceeded.)
class Numeric
def clamp(min, max)
return min if self < min
return max if self > max
self
end
end
(1..19).cover? #limit
See docs for details.
AFAIK when reducing an array we can only output once variable at the end like so:
(0..10).reduce(0) do |sum, value|
sum + value
end
What if I have an array of hash objects, can I reduce the array and output multiple variables something like:
({:grade => 100, :sex => 'female'}, {:grade => 90, :sex => 'male'}).reduce(0, 0, 0) do |sum_of_grades, sum_of_male, sum_of_female, value|
sum_of_grades = sum_of_grades + value[:grade]
sum_of_male += 1 if value[:sex] == 'male'
sum_of_female +=1 if value[:sex] == 'female
end
Aggregate multiple results in a hash or any other suitable object:
a.reduce({:sum_of_grades => 0, :sum_of_male => 0, :sum_of_female => 0}) do |result, value|
result[:sum_of_grades] += value[:grade]
result[:sum_of_male] += 1 if value[:sex] == 'male'
result[:sum_of_female] += 1 if value[:sex] == 'female'
result
end