I have an array of hashes:
arr_of_hsh =[
{minimum_quantity: "250", cost: "1.5600"},
{minimum_quantity: "500", cost: "1.3500"},
{minimum_quantity: "1000", cost: "1.1800"},
{minimum_quantity: "2500", cost: "1.0200"}
]
Given a certain amount, I'd like to get the hash whose value for :minimum_quantity is the closest to the amount among those less than or equal to the amount. For example, if the amount was 300, this hash would be returned:
{minimum_quantity: "250", cost: "1.5600"}
and if the amount was 1000:
{minimum_quantity: "1000", cost: "1.1800"}
Keeping your data structure the same I'd say this way
arr_of_hsh.select { |item| item[:minimum_quantity].to_i <= quantity }.
max_by { |item| item[:minimum_quantity].to_i }
Here is a single-pass solution.
def f(arr, qty)
h = arr.min_by do |h|
n = h[:minimum_quantity].to_i
n <= qty ? (qty-n) : Float::INFINITY
end
h[:minimum_quantity].to_i <= qty ? h : nil
end
(100..1100).step(100).each { |qty| puts "#{qty}: #{f(arr_of_hsh, qty)}" }
100: nil
200: nil
300: {:minimum_quantity=>"250", :cost=>"1.5600"}
400: {:minimum_quantity=>"250", :cost=>"1.5600"}
500: {:minimum_quantity=>"500", :cost=>"1.3500"}
600: {:minimum_quantity=>"500", :cost=>"1.3500"}
700: {:minimum_quantity=>"500", :cost=>"1.3500"}
800: {:minimum_quantity=>"500", :cost=>"1.3500"}
900: {:minimum_quantity=>"500", :cost=>"1.3500"}
1000: {:minimum_quantity=>"1000", :cost=>"1.1800"}
1100: {:minimum_quantity=>"1000", :cost=>"1.1800"}
If the method is to be called several times for a given arr_of_hsh, sort arr_of_hsh by decreasing value of arr_by_hsh[:minimum_quantity.to_i and then simply use Enumerable#find.
def f(arr, qty)
h = arr.find { |h| h[:minimum_quantity].to_i <= qty }
end
arr = arr_of_hsh.sort_by { |h| -h[:minimum_quantity].to_i }
#=> [{:minimum_quantity=>"2500", :cost=>"1.0200"},
# {:minimum_quantity=>"1000", :cost=>"1.1800"},
# {:minimum_quantity=>"500", :cost=>"1.3500"},
# {:minimum_quantity=>"250", :cost=>"1.5600"}]
(100..1100).step(100).each { |qty| puts "#{qty}: #{f(arr, qty)}" }
#=> (same as above)
Related
So I am trying to sum a total from an array, but once that total is above 8000 it should reduce the amount added by 50%. I seem to be getting the sum of the total from before and after the if condition. Can anyone explain why and how to fix it?
arr = [{ add_on: "AWD Drivetrain", price: 2500 }, { add_on: "Sport Package", price: 3500 }, { add_on: "Winter Tire Package", price: 2000 }, { add_on: "GPS Navigation", price: 2000 },]
def calculate_price_recursive(arr)
prices = arr.sort_by { |x| -x[:price] }.map { |x| x[:price] }
return recur_sum(prices, 0)
end
def recur_sum(prices, total)
puts "#{prices}"
return total if prices.count == 0
if total < 8000
prices[0] + recur_sum(prices[1..-1], total + prices[0])
else
(prices[0] / 2) + recur_sum(prices[1..-1], total + prices[0])
end
end
Ruby does not have TCO enabled by default. To enable it explicitly one should do this:
RubyVM::InstructionSequence.compile_option = {
tailcall_optimization: true,
trace_instruction: false
}
That said, the result might look like
RubyVM::InstructionSequence.compile_option = {
tailcall_optimization: true,
trace_instruction: false
}
arr = [
{ add_on: "AWD Drivetrain", price: 2500 },
{ add_on: "Sport Package", price: 3500 },
{ add_on: "Winter Tire Package", price: 2000 },
{ add_on: "GPS Navigation", price: 2000 }
]
def calculate_price_recursive(arr)
prices = arr.map { |x| x[:price] }.sort.reverse
recur_sum(prices)
end
def recur_sum(prices, total = 0)
return total if prices.count == 0
recur_sum(
prices[1..-1],
total + prices[0] / (total < 8000 ? 1 : 2)
)
end
I was adding the stuff twice.
def recur_sum(prices, total)
puts "#{prices}"
return total if prices.count == 0
if total < 8000
recur_sum(prices[1..-1], total + prices[0])
else
recur_sum(prices[1..-1], total + prices[0] / 2)
end
end
I found a way to do the whole thing recusrively in one function:
def recur_sum_holistic(arr, base, check, constant)
if check == 1
constant = arr.map { |x| x[:add_on] }.join(', ')
end
return "The cost for this car is $#{((base + (((base - 24999) * 0.02) + 1200)) * 1.13).round(2)} with the following configuration: #{constant}" if arr.count == 0
recur_sum_holistic(arr[1..-1], base + (arr[0][:price] / (base < (24999 + 8000) ? 1 : 2)), 2, constant)
end
\\for Element in Arr:
\\\\if ElementSum < 8300
\\\\\\ElementSum = ElementSum + Element
\\\\else
\\\\\\ElementSum = ElementSum + (Element / 2)
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)
I'd like to iterate over an entire array, starting from any position. I'm not sure if there's a way to achieve this easily in Ruby, and I couldn't find any examples in the Array or Enumerator docs.
array = [0, 1, 2, 3, 4]
array.each.starting_at(3) { |e| e }
#=> [3, 4, 0, 1, 2]
And also:
array.each.starting_at_reverse(3) { |e| e }
#=> [3, 2, 1, 0, 4]
You can use the rotate method for this. This method rotates the position of each element by n. So your examples can be done like this
array.rotate(3).each {|e| e }
and
array.reverse.rotate(1).each {|e| e}
Note: for the second method the parameter to rotate can be derived by finding the negative index of n. So for this the element at index 3 is at index -2 in a length 5 array.
You can do this with upto and downto Fixnum's methods:
array = [0, 1, 2, 3, 4]
last_index = array.size - 1
3.upto last_index do |i|
puts array[i]
end
# => 3, 4
last_index.downto 3 do |i|
puts array[i]
end
# => 4, 3
PS. as speed benchmark, iteration with rotation faster
array.rotate(3).each {|e| puts e}
benchmark:
require 'benchmark'
array = Array.new(10000000) { rand(1...9) }
last_index = array.size - 1
Benchmark.bm do |x|
x.report 'upto' do
10000.upto last_index do |index| a = array[index] + 1; end
end
x.report 'downto' do
last_index.downto 10000 do |index| a = array[index] + 1; end
end
x.report 'rotate' do
array.rotate(10000).each {|e| a = e + 1 }
end
end
# RESULTS:
# user system total real
# upto 0.680000 0.000000 0.680000 ( 0.681932)
# downto 0.680000 0.000000 0.680000 ( 0.679752)
# rotate 0.590000 0.040000 0.630000 ( 0.622901)
but, as memory benchmark, iteration by array indexes less memory hungry, especially on big array sizes:
require 'memory_profiler'
array = Array.new(10000000) { rand(1...9) }
last_index = array.size - 1
{
upto: -> { 10000.upto last_index do |index| a = array[index] + 1; end },
downto: -> { last_index.downto 10000 do |index| a = array[index] + 1; end },
rotate: -> { array.rotate(10000).each {|e| a = e + 1 } },
reverse_rotate: -> { array.reverse.rotate(10000).each {|e| a = e + 1 } }
}.each { |desc, code| puts "#{desc.to_s} => #{MemoryProfiler.report(&code).total_allocated_memsize.to_s}" }
# RESULTS (in bytes):
# upto => 0 # no additional memory allocation
# downto => 0 # no additional memory allocation
# rotate => 80000040 # implicitly copied array 1 time
# reverse_rotate => 160000080 # implicitly copied array 2 times
for an array like
s = [[1,2],[4,6],[2,7]]
How i can select max and sum of 2nd column in each row in one statement
max= 7
sum= 15
I know, that
sum = 0
max = 0
s.each{ |a,b| sum+=b;if max<b then max = b end }
would work.
The transpose method is nice for accessing "columns":
s = [[1,2],[4,6],[2,7]]
col = s.transpose[1]
p col.max #=> 7
p col.inject(:+) #=> 15
second_elements = s.map { |el| el[1] }
sum = second_elements.inject{|sum,x| sum + x }
max = second_elements.max
To be more clear:
inject{|sum,x| sum + x } returns nil if array is empty, so if you want to get 0 for empty array then use inject(0, :+)
s.max {|a| a[1]}[1] # Max of elements at index 1
s.max {|a| a.last }.last # Max of last elements
# => 7
To find the sum, if you use Ruby 2.4 or greater / if you are on Rails
s.sum {|a| a[1]} # Sum of elements at index 1
s.sum(&:last) # Sum of last elements
# => 15
else
s.inject(0) {|sum, a| sum+= a[1] }
# => 15
s.map{|e| e[1]}.max gives you max
s.map{|e| e[1]}.reduce(:+) gives you sum.
s = [[1,2],[4,6],[2,7]]
second_max = s.max_by(&:last).last
# => 7
sum = s.reduce(0){|sum,a| sum + a.last}
# => 15
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.