Algorithm for ‘spreading` sum in Hash - ruby

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

Related

Ruby: Why is my Code false?

Trying to multiply each number by array position, and it's coming out false:
def the_sum(number)
i = 0
number = 0
ans = 0
while i < 0
ans = string[idx] * string.index
i += idx
end
return ans
end
test =
the_sum([2, 3]) == 3 # (2*0) + (3*1)
the_sum([2, 3, 5]) == 13 # (2*0) + (3*1) + (5*2)
and it comes out false?
There are a few problems here
def the_sum(number)
i = 0
number = 0 # You just cleared your input variable!
ans = 0
while i < 0 # You previously i = 0 so this will never be true
ans = string[idx] * string.index
i += idx
end
return ans # Ans is and was always 0
end
This can be fixed by calling each_with_index on the Array that you're passing.
def the_array_sum(array)
ans = 0
array.each_with_index do |val, index|
ans += val * index
end
return ans
end
the_array_sum([2, 3]) == 3
# => true
the_array_sum([2, 3, 5]) == 13
# => true

Ruby: Iterate over entire array from nth position

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

how to get max and sum of 2nd column of array in ruby

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

Ruby Koans Scoring Project

I'm working through the Ruby Koans, and I'm having a bit of trouble figuring out what is going wrong with a method I've written. I'm in about_scoring_project.rb, and I've written the score method for the dice game:
def score(dice)
return 0 if dice == []
sum = 0
rolls = dice.inject(Hash.new(0)) { |result, element| result[element] += 1; result; }
rolls.each { |key, value|
# special condition for rolls of 1
if key == 1
sum += 1000 | value -= 3 if value >= 3
sum += 100*value
next
end
sum += 100*key | value -= 3 if value >= 3
sum += 50*value if key == 5 && value > 0
}
return sum
end
For those unfamiliar with the exercise:
Greed is a dice game where you roll up to five dice to accumulate
points. The following "score" function will be used to calculate the
score of a single roll of the dice.
A greed roll is scored as follows:
A set of three ones is 1000 points
A set of three numbers (other than ones) is worth 100 times the number. (e.g. three fives is 500 points).
A one (that is not part of a set of three) is worth 100 points.
A five (that is not part of a set of three) is worth 50 points.
Everything else is worth 0 points.
Examples:
score([1,1,1,5,1]) => 1150 points score([2,3,4,6,2]) => 0 points
score([3,4,5,3,3]) => 350 points score([1,5,1,2,4]) => 250 points
More scoring examples are given in the tests below:
Your goal is to write the score method.
I run into trouble when I try to run the last test in the file: assert_equal 550, score([5,5,5,5])
For some reason I am returning 551 instead of 550. Thanks for your help!
Here is my approach:
def score(dice)
# Count how many what
clusters = dice.reduce(Hash.new(0)) {|hash, num| hash[num] += 1; hash }
# Since 1's are special, handle them first
ones = clusters.delete(1) || 0
score = ones % 3 * 100 + ones / 3 * 1000
# Then singular 5's
score += clusters[5] % 3 * 50
# Then the triples other than triple-one
clusters.reduce(score) {|s, (num, count)| s + count / 3 * num * 100 }
end
My approach uses two lookup tables - one containing the scores for triples, the other for singles. I work out the score for each number using the tables, and accumulate the total using inject:
def score(dice)
triple_scores = [1000, 200, 300, 400, 500, 600]
single_scores = [100, 0, 0, 0, 50, 0]
(1..6).inject(0) do |score, number|
count = dice.count(number)
score += triple_scores[number - 1] * (count / 3)
score += single_scores[number - 1] * (count % 3)
end
end
I went with
def score(dice)
dice.uniq.map do |die|
count = dice.count die
if count > 2
count -= 3
die == 1 ? 1000 : 100 * die
else 0
end + case die
when 1 then count * 100
when 5 then count * 50
else 0
end
end.inject(:+) || 0
end
This is because you're really adding the result of a | operator (Bitwise OR) to the total score:
sum += 100*key | value -= 3 if value >= 3 # This is 501 in your case
Proof:
irb(main):004:0> value = 4
=> 4
irb(main):005:0> 100 * 5 | value -= 3 # This should be read as (500) | 1 which is 501
=> 501
So rewrite it like this:
if value >= 3
sum += 100 * key
value -= 3
end
My approach was:
def score(dice)
calculator = ->(no, group_multipler, individual_multipler) { (no / 3 * group_multipler) + (no % 3 * individual_multipler) }
dice.group_by {|i| i % 7 }.inject(0) do |total, (value, scores)|
group_multipler, individual_multipler = case value
when 1
[1000, 100]
when 5
[500, 50]
else
[value * 100, 0]
end
total += calculator.call(scores.size, group_multipler, individual_multipler)
end
end
My approach:
def score(dice)
score = 0
score += dice.count(1) >= 3? (1000+ (dice.count(1) -3)*100): dice.count(1) * 100
score += dice.count(5) >= 3 ? (500 + (dice.count(5) -3)*50): dice.count(5) * 50
[2,3,4,6].each {|x| dice.count(x) >=3? score+= x*100:0}
return score
end
Here's my answer:
def score(dice)
frequency = dice.inject(Hash.new(0)) do |h, el|
h[el] += 1
h
end
score_triples = { 1 => 1000 }
score_singles = { 1 => 100, 5 => 50 }
score = 0
frequency.each do |k, v|
score += v / 3 * score_triples.fetch(k, 100 * k)
score += v % 3 * score_singles.fetch(k, 0)
end
score
end
My approach used integer division and modulus division:
def score(dice)
points = 1000 * (dice.count(1) / 3)
points += 100 * (dice.count(1) % 3)
points += 50 * (dice.count(5) % 3)
(2..6).each do |i|
points += (100 * i) * (dice.count(i) / 3)
end
points
end
This was the first piece of code I ever wrote by myself (With a ton of help of stackoverflow, of course.) After watching all other answers I realize it is way overkill specially because it works for a 9 numbers dice (does that exist?)
def score(dice)
if dice.empty?
return 0
end
var_score = 0
conteo = (0..9).to_a.each.map { |x| dice.count(x)}
#Evaluating 1
if ( conteo[1] / 3 ) >= 0
multiplier1 = conteo[1]/3
var_score += multiplier1 * 1000
end
if ( conteo[1] % 3 ) != 0
var_score += (conteo[1] % 3)*100
end
#Evaluating 5
if ( conteo[5] % 3 ) != 0
var_score += (conteo[5] % 3)* 50
end
#Evaluating numbers x 3
if (conteo[2..9].count { |x| x >= 3 }) > 0
triplets = conteo[2..9].map {|x| x / 3}
array_multiplicator = triplets.each_with_index.select {|num,index| (num > 0)}.map {|x| x[0]}
product_triplets = triplets.each_with_index.select {|num,index| (num > 0)}.map {|x| x[1]}.map {|x| (x+2)*100}
var_score += array_multiplicator.zip(product_triplets).map{|x| x.inject(&:*)}.sum
end
var_score
end
It took 29 lines, but this is my first Ruby
def score(dice)
return 0 if dice == []
sums = Array.new # To hold number of occurrences 1 - 6
for i in 0..6 # Initialize to 0... note [0] is not used
sums[i] = 0
end
total = 0 # To hold total
dice.each do |dots| # Number of dots showing on dice
sums[dots] += 1 # Increment the array members 1 - 6
end
if sums[1] > 2 then # If 3 1's
total += 1000
sums[1] -= 3 # Remove the 3 you took, in case there's more
end
if sums[2] > 2 then total += 200 # If 3 2's
end
if sums[3] > 2 then total += 300 #If 3 3's
end
if sums[4] > 2 then total += 400 #If 3 4's
end
if sums[5] > 2 then total += 500 #If 3 5's
sums[5] -= 3 #Remove the 5's you took
end
if sums[6] > 2 then total += 600 #If 3 6's
end
total += (sums[1] * 100) # If any ones are left
total += (sums[5] * 50) # Same for fives
return total
end
This is my solutions.
def score(dice)
score = 0
# grab all the numbers and their amounts
number_amounts = dice.reduce(Hash.new(0)) { |hash, numb| hash[numb] += 1; hash }
# iterate through each pair
number_amounts.each do |key, value|
# case with number 1
score += (value % 3) * 100 + value / 3 * 1000 if (key == 1)
# case with number 5
score += (value % 3) * 50 + value / 3 * key * 100 if (key == 5)
# all numbers except 1 and 5
score += (value / 3) * key * 100 if (key != 1 && key != 5)
end
score
end
def score(dice)
# Set up rules Hash
rules = { 1 => {:triples => 1000, :singles => 100}, 5 => {:triples => 100, :singles => 50} }
[2,3,4,6].each {|i| rules[i] = {:triples => 100, :singles => 0} }
# Count all ocourencies
counts = dice.each_with_object(Hash.new(0)) {|e, h| h[e] += 1}
#calculate total
total = 0
counts.each_pair{ | key, value |
total += value >= 3? (rules[key][:triples]*key + (value -3)*rules[key][:singles]): value * rules[key][:singles]
}
return total
end
I used the new ruby enumerable method tally
def score(dice)
return 0 if dice.empty?
ans = dice.tally.map do |k,v|
case k
when 1
three = (k * 1000) * (v/3)
val = (v%3) * 100
val + three
when 5
three = (k * 100) * (v/3)
val = (v%3) * 50
val + three
else
(k * 100) * (v/3)
end
end
ans.reduce(0, :+)
end
My attempt, feedback and refactoring suggestions most welcome:
def score(dice)
score = 0
score_array = [[0, 100, 200, 1000, 1100, 1200], [0, 0, 0, 200, 200, 200], [0, 0, 0, 300, 300, 300], [0, 0, 0, 400, 400, 400], [0, 50, 100, 500, 550, 600], [0, 0, 0, 600, 600, 600]]
tally_hash = {1=>0, 2=>0, 3=>0, 4=>0, 5=>0, 6=>0}
dice.sort.tally.each do |key, value|
tally_hash[key] += value
end
tally_hash.each do |key, value|
score += score_array[key -1][value]
end
return score
end
I used hash for score
def score(dice)
score_map = {
1 => 100,
5 => 50
}
cluster = dice.inject(Hash.new(0)) {|hash, num| hash[num] += 1; hash}
cluster.inject(0) do |sum, (num, count)|
set_count = count / 3
sum += num == 1 ? 1000 * set_count : num * 100 * set_count
sum + (score_map[num] || 0) * (count % 3)
end
end
def score(dice)
score = 0
dice.uniq.each do |number|
count = dice.count number
weight = if number == 1 then 10 else number end
if count >= 3
score += weight * 100
count -= 3
end
if count > 0 and number == 1 or number == 5
score += count * weight * 10
end
end
score
end
def score(dice)
ones = fives = rest = 0
one_count = dice.count(1)
if one_count > 2
ones = 1000
one_count -= 3
end
ones += one_count * 100
five_count = dice.count(5)
if five_count > 2
fives = 500
five_count -= 3
end
fives += five_count * 50
[2,3,4,6].each do |num|
if dice.count(num) > 2
rest += num * 100
end
end
return ones + fives + rest
end

Distribute range in array

I need to create one array of numbers inside one range, like:
[1..5] in 10 times = [1,1,2,2,3,3,4,4,5,5]
[1..5] in 5 times = [1,2,3,4,5]
[1..5] in 3 times = [1,3,5]
def distribute(start_value, end_value, times, is_integer)
array = Array.new(times-1)
min_value = [end_value,start_value].min
max_value = [end_value,start_value].max
if max_value-min_value<times
factor = (max_value-min_value).abs/(array.size).to_f
else
factor = (max_value-min_value).abs/(array.size-1).to_f
end
for i in 0..array.size
v = [ [max_value, factor*(i+1)].min, min_value].max
is_integer ? array[i] = v.round : array[i] = v
end
start_value < end_value ? array : array.reverse
end
distribute(1, 5, 10, true)
=> [1, 1, 1, 2, 2, 3, 3, 4, 4, 4] #WRONG should be [1,1,2,2,3,3,4,4,5,5]
distribute(5, 1, 5, true)
=> [5, 4, 3, 2, 1] #OK
distribute(1, 5, 3, true)
=> [4, 5, 5] #WRONG should be [1, 3, 5]
How 'bout this:
def distribute(min,max,items)
min,max = [min,max].sort
(0...items).map {|i| (min + i * (max - min) / (items-1.0)).round}
end
Or if you really need the int/float flag:
def distribute(min,max,items,ints)
min,max = [min,max].sort
a = (0...items).map {|i| min + i * (max - min) / (items-1.0)}
ints ? a.map {|i| i.round} : a
end
And if you really need this to go in reverse if the parameters are given to you backwards:
def distribute(min,max,items,ints)
usemin,usemax = [min,max].sort
diff = usemax - usemin
a = (0...items).map {|i| usemin + i * diff / (items-1.0)}
a.map! {|i| i.round} if ints
min != usemin ? a.reverse : a
end
just a little correction... when the array_size is 0
def distribute(start_value, end_value, array_size, want_ints)
diff = 1.0 * (end_value - start_value)
n = [array_size-1, 1].max
(0..(array_size-1)).map { |i|
v = start_value + i * diff / n
want_ints ? v.round : v
}
end

Resources