Related
I am currently working through the Ruby Koans and I am stuck on the scoring project. First of all, I am having a hard time evaluating the instructions and laying it out in terms of what I am suppose to do. Secondly, I am not sure if I am on the right track with the method score I wrote below. My questions is - Is there a way to understand these instructions better? Also, with the score method I wrote, I am still not passing the first test. I think I must understand what I need to do first but I can't figure it out. Any help and simple explanations or direction is appreciated.
Thank you.
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.
def score(dice)
(1..6).each do |num|
amount = dice.count(num)
if amount >= 3
100 * num
elsif num == 1
100 * amount
elsif num == 5
50 * amount
else
0
end
end
end
# test code for method
class AboutScoringProject < Neo::Koan
def test_score_of_an_empty_list_is_zero
assert_equal 0, score([])
end
def test_score_of_a_single_roll_of_5_is_50
assert_equal 50, score([5])
end
def test_score_of_a_single_roll_of_1_is_100
assert_equal 100, score([1])
end
def test_score_of_multiple_1s_and_5s_is_the_sum_of_individual_scores
assert_equal 300, score([1,5,5,1])
end
def test_score_of_single_2s_3s_4s_and_6s_are_zero
assert_equal 0, score([2,3,4,6])
end
def test_score_of_a_triple_1_is_1000
assert_equal 1000, score([1,1,1])
end
def test_score_of_other_triples_is_100x
assert_equal 200, score([2,2,2])
assert_equal 300, score([3,3,3])
assert_equal 400, score([4,4,4])
assert_equal 500, score([5,5,5])
assert_equal 600, score([6,6,6])
end
def test_score_of_mixed_is_sum
assert_equal 250, score([2,5,2,2,3])
assert_equal 550, score([5,5,5,5])
assert_equal 1100, score([1,1,1,1])
assert_equal 1200, score([1,1,1,1,1])
assert_equal 1150, score([1,1,1,5,1])
end
end
This is what I have done:
def score(dice)
score = 0
return score if dice == nil || dice == []
quantity = dice.inject(Hash.new(0)) {|result,element| result[element] +=1; result}
score += quantity[1] >= 3 ? 1000 + ((quantity[1] - 3) * 100) : quantity[1] * 100
score += quantity[5] >= 3 ? 500 + ((quantity[5] - 3) * 50) : quantity[5] * 50
[2,3,4,6].each {|x| score += x * 100 if quantity[x] >= 3 }
score
end
You can use hash for score instead of writing switch-case
def score(dice)
score_map = {
1 => 100,
5 => 50
}
cluster = dice.inject(Hash.new(0)) {|hash, number| hash[number] += 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) # use 0 if score of num dosn't exist
end
end
Given these arrays, how do I remove three occurrences of a value while keeping the fourth or fifth in the array?
[1,5,1,1,1] # => [1,5]
[3,3,3,2,3] # => [3,2]
[3,4,5,3,3] # => [4,5]
[1,1,1,1,1] # => [1,1]
[1,2,2,4,5] # => [1,2,2,4,5]
Here's what I've tried:
array = [1,5,1,1,1]
top3 = array.select { |x| array.count(x) >= 3 }[0..2]
last2 = array - top3
This strategy (and similar) only seem to work when there are three duplicates but not four or five. Are there elegant solutions to this problem?
UPDATE: Thank you for your amazing answers. As a beginning rubyist I learned a lot just from analyzing each response. My question came from a Ruby Koan challenge for a dice program. Here's my complete solution implemented with Abdo's suggestion. I'm sure there are more efficient ways to implement the program :)
def score(dice)
a,b,c,d,e = dice
array = [a,b,c,d,e]
total = 0
triples = array.select {|x| array.count(x) >= 3}[0..2]
singles = array.group_by{|i| i}.values.map{ |a|
a.length > 2 ? a[0, a.length - 3] : a
}.inject([], :+)
# Calculate values for triples
# 1 * 3 = 1000pts
# 2 * 3 = 200pts
# 3 * 3 = 300pts
# 4 * 3 = 400pts
# 5 * 3 = 500pts
# 6 * 3 = 600pts
case triples[0]
when 1 then total += triples[0]*1000
when (2..6) then total += triples[0]*100
end
# Calculate values for singles:
# 1s = 100pts each
# 5s = 50pts each
singles.include? (1) ? singles.select {|x| x == 1 }.each {|x| total += x*100 } : total
singles.include? (5) ? singles.select {|x| x == 5 }.each {|x| total += x*10 } : total
return total
end
puts score([5,1,1, 5, 6]) # 300 points
puts score([]) # 0 points
puts score([1,1,1,5,1]) # 1150 points
puts score([2,3,4,6,2]) # 0 points
puts score([3,4,5,3,3]) # 350 points
puts score([1,5,1,2,4]) # 250 points
array = [1,5,1,1,1]
occurrence = {}
array.select do|a|
if(array.count(a) > 3)
occurrence[a] ||= []
occurrence[a] << a
occurrence[a].count > 3
else
true
end
end
PS: This solution preserves the order of the elements in the original array
Here's a faster solution when the size of the array is large:
(I avoid using count because it would loop through the array in an inner loop)
arr.inject({}) {
|h, i| h[i] ||= 0; h[i] += 1; h
}.collect_concat {|k,v| [k] * (v > 2 ? v - 3 : v) }
Here's the fruity comparison to the other working solutions:
arr = 1000.times.collect { rand(100) }.shuffle
require 'fruity'
compare do
vimsha {
occurrence = {};
arr.select do|a|
if(arr.count(a) > 3)
occurrence[a] ||= []
occurrence[a] << a
occurrence[a].count > 3
else
true
end
end
}
caryswoveland {
arr.uniq.reduce([]) {|a,e| a + [e]*((cnt=arr.count(e)) > 2 ? cnt-3 : cnt)}
}
aruprakshit {
num_to_del = arr.find { |e| arr.count(e) >= 3 }
if !num_to_del.nil?
3.times do
ind = arr.index { |e| e == num_to_del }
arr.delete_at(ind)
end
end
arr
}
# edited as suggested by #CarySwoveland
abdo {
arr.each_with_object(Hash.new {|h,k| h[k]=[]}) {|i,h| h[i] += 1
}.collect_concat { |k,v| [k] * (v > 2 ? v - 3 : v) }
}
broisatse {
arr.group_by{|i| i}.values.map{ |a|
a.length > 2 ? a[0, a.length - 3] : a
}.inject([], :+)
}
end
Here's the comparison result:
Running each test 64 times. Test will take about 48 seconds.
broisatse is faster than abdo by 30.000000000000004% ± 10.0%
abdo is faster than aruprakshit by 4x ± 1.0 (results differ: ...)
aruprakshit is similar to caryswoveland (results differ: ...)
caryswoveland is similar to vimsha (results differ: ...)
Note: I took #aruprakshit's code outside the method so we don't waste time in the method call itself.
When the array's size is increased further:
arr = 1000.times.collect { rand(1000) }.shuffle
we get:
abdo is faster than broisatse by 3x ± 1.0
broisatse is faster than aruprakshit by 6x ± 10.0
aruprakshit is faster than caryswoveland by 2x ± 1.0
caryswoveland is similar to vimsha
Another way, assuming order need not be preserved (which is consistent with a comment by the asker):
array = [1,2,4,1,2,1,2,1,1,4]
array.uniq.reduce([]) {|a,e| a + [e]*((cnt=array.count(e)) > 2 ? cnt-3 : cnt)}
#=> [1, 1, 4, 4]
Try something like:
a.group_by{|i| i}.values.map{|a| a[0, a.length % 3]}.inject([], :+)
This will remove all triplets from the array. If you want to remove only the first triplet, then do:
a.group_by{|i| i}.values.map{|a| a.length > 2 ? a[0, a.length - 3] : a }.inject([], :+)
Note: This might mess up the order of the array:
[1,2,1,2,3] #=> [1,1,2,2,3]
Let me know if you need to keep the order and, if so, which elements need to be removed if there are more than three, e.g. what should say: [1,1,2,1,1,] - [1,2] or [2,1]?
x.group_by{|i| i }.values.select{|a| a.size >= 3 }.each{|a| c=[3,a.size].min; x.delete_if{|e| a[0]==e && (c-=1)>=0 } }
It will remove the first [3,a.size].min occurrences of a[0] from the input x where a is, for example, [1,1,1,1] for x = [1,2,1,1,1]
I'd do as below :
def del_first_three(a)
num_to_del = a.find { |e| a.count(e) >= 3 }
return a if num_to_del.nil?
3.times do
ind = a.index { |e| e == num_to_del }
a.delete_at(ind)
end
a
end
del_first_three([3,4,5,3,3]) # => [4, 5]
del_first_three([1,5,1,1,1]) # => [5, 1]
del_first_three([1,2,2,4,5]) # => [1, 2, 2, 4, 5]
I am trying to implement a cumulative weighted average function that takes as argument the
list
[[1000, 3.1], [500, 1.2], [800, 7.1], [1300, 8.88]]
and returns (rounded to 2 decimal places here)
[3.1, 2.47, 4.08, 5.81]
For, example: 2.47 = (1000 * 3.1 + 500 * 1.2) / 1500.
I have currently solved this using the following piece of code:
def cumulative_weighted_average(list)
cs = 0
qu = 0
res = list.inject([0]) do |s, el|
cs += el[0] * el[1]
qu += el[0]
s + [cs.to_f / qu]
end
res.shift
res
end
Is there a shorter (more compact) way of doing this?
Edit:
Thanks for the answers below! The list will on average contain about 1000 entries, so not sure about the speed requirement. Since I need to be able to essentially track two values within the block, is there some extension of inject that allows you to write
list.inject([0,0]){ |s1, s2, el| ...}
where s1 and s2 are initialized to 0?
I think this is what you want:
def cumulative_weighted_average list
cs, qu = 0.0, 0.0
list
.map{|x, w| [cs += x * w, qu += x]}
.map{|cs, qu| cs / qu}
end
cumulative_weighted_average([[1000, 3.1], [500, 1.2], [800, 7.1], [1300, 8.88]])
# => [3.1, 2.466666666666667, 4.078260869565217, 5.812222222222222]
For the additional question, things like this are possible:
list.inject([0,0]){|(s1, s2), el| ...}
Is there a shorted (more compact) way of doing this?
I can try for you..
arr = [[1000, 3.1], [500, 1.2], [800, 7.1], [1300, 8.88]]
arr2 = (1..arr.size).map do |i|
b = arr.take(i)
b.reduce(0){|sum,a| sum + a.reduce(:*)}/b.reduce(0){|sum,k| sum + k[0]}
end
arr2
# => [3.1, 2.466666666666667, 4.078260869565217, 5.812222222222222]
You can avoid the "outer" temporary variables, and make things look a little cleaner, and idiomatic Ruby, if you allow for a two-stage calculation (which is not necessarily slower, same amount of maths is involved):
def cumulative_weighted_average list
cumulative_totals = list.inject( [] ) do |cumulative,item|
tot_count, tot_sum = cumulative.last || [0, 0.0]
next_count, next_value = item
cumulative << [ tot_count + next_count, tot_sum + next_count * next_value ]
end
cumulative_totals.map { |count,sum| sum/count }
end
p cumulative_weighted_average(
[[1000, 3.1], [500, 1.2], [800, 7.1], [1300, 8.88]] )
=> [3.1, 2.46666666666667, 4.07826086956522, 5.81222222222222]
I have several records with a given attribute, and I want to find the standard deviation.
How do I do that?
module Enumerable
def sum
self.inject(0){|accum, i| accum + i }
end
def mean
self.sum/self.length.to_f
end
def sample_variance
m = self.mean
sum = self.inject(0){|accum, i| accum +(i-m)**2 }
sum/(self.length - 1).to_f
end
def standard_deviation
Math.sqrt(self.sample_variance)
end
end
Testing it:
a = [ 20, 23, 23, 24, 25, 22, 12, 21, 29 ]
a.standard_deviation
# => 4.594682917363407
01/17/2012:
fixing "sample_variance" thanks to Dave Sag
It appears that Angela may have been wanting an existing library. After playing with statsample, array-statisics, and a few others, I'd recommend the descriptive_statistics gem if you're trying to avoid reinventing the wheel.
gem install descriptive_statistics
$ irb
1.9.2 :001 > require 'descriptive_statistics'
=> true
1.9.2 :002 > samples = [1, 2, 2.2, 2.3, 4, 5]
=> [1, 2, 2.2, 2.3, 4, 5]
1.9.2p290 :003 > samples.sum
=> 16.5
1.9.2 :004 > samples.mean
=> 2.75
1.9.2 :005 > samples.variance
=> 1.7924999999999998
1.9.2 :006 > samples.standard_deviation
=> 1.3388427838995882
I can't speak to its statistical correctness, or your comfort with monkey-patching Enumerable; but it's easy to use and easy to contribute to.
The answer given above is elegant but has a slight error in it. Not being a stats head myself I sat up and read in detail a number of websites and found this one gave the most comprehensible explanation of how to derive a standard deviation. http://sonia.hubpages.com/hub/stddev
The error in the answer above is in the sample_variance method.
Here is my corrected version, along with a simple unit test that shows it works.
in ./lib/enumerable/standard_deviation.rb
#!usr/bin/ruby
module Enumerable
def sum
return self.inject(0){|accum, i| accum + i }
end
def mean
return self.sum / self.length.to_f
end
def sample_variance
m = self.mean
sum = self.inject(0){|accum, i| accum + (i - m) ** 2 }
return sum / (self.length - 1).to_f
end
def standard_deviation
return Math.sqrt(self.sample_variance)
end
end
in ./test using numbers derived from a simple spreadsheet.
#!usr/bin/ruby
require 'enumerable/standard_deviation'
class StandardDeviationTest < Test::Unit::TestCase
THE_NUMBERS = [1, 2, 2.2, 2.3, 4, 5]
def test_sum
expected = 16.5
result = THE_NUMBERS.sum
assert result == expected, "expected #{expected} but got #{result}"
end
def test_mean
expected = 2.75
result = THE_NUMBERS.mean
assert result == expected, "expected #{expected} but got #{result}"
end
def test_sample_variance
expected = 2.151
result = THE_NUMBERS.sample_variance
assert result == expected, "expected #{expected} but got #{result}"
end
def test_standard_deviation
expected = 1.4666287874
result = THE_NUMBERS.standard_deviation
assert result.round(10) == expected, "expected #{expected} but got #{result}"
end
end
I'm not a big fan of adding methods to Enumerable since there could be unwanted side effects. It also gives methods really specific to an array of numbers to any class inheriting from Enumerable, which doesn't make sense in most cases.
While this is fine for tests, scripts or small apps, it's risky for larger applications, so here's an alternative based on #tolitius' answer which was already perfect. This is more for reference than anything else:
module MyApp::Maths
def self.sum(a)
a.inject(0){ |accum, i| accum + i }
end
def self.mean(a)
sum(a) / a.length.to_f
end
def self.sample_variance(a)
m = mean(a)
sum = a.inject(0){ |accum, i| accum + (i - m) ** 2 }
sum / (a.length - 1).to_f
end
def self.standard_deviation(a)
Math.sqrt(sample_variance(a))
end
end
And then you use it as such:
2.0.0p353 > MyApp::Maths.standard_deviation([1,2,3,4,5])
=> 1.5811388300841898
2.0.0p353 :007 > a = [ 20, 23, 23, 24, 25, 22, 12, 21, 29 ]
=> [20, 23, 23, 24, 25, 22, 12, 21, 29]
2.0.0p353 :008 > MyApp::Maths.standard_deviation(a)
=> 4.594682917363407
2.0.0p353 :043 > MyApp::Maths.standard_deviation([1,2,2.2,2.3,4,5])
=> 1.466628787389638
The behavior is the same, but it avoids the overheads and risks of adding methods to Enumerable.
The presented computation are not very efficient because they require several (at least two, but often three because you usually want to present average in addition to std-dev) passes through the array.
I know Ruby is not the place to look for efficiency, but here is my implementation that computes average and standard deviation with a single pass over the list values:
module Enumerable
def avg_stddev
return nil unless count > 0
return [ first, 0 ] if count == 1
sx = sx2 = 0
each do |x|
sx2 += x**2
sx += x
end
[
sx.to_f / count,
Math.sqrt( # http://wijmo.com/docs/spreadjs/STDEV.html
(sx2 - sx**2.0/count)
/
(count - 1)
)
]
end
end
As a simple function, given a list of numbers:
def standard_deviation(list)
mean = list.inject(:+) / list.length.to_f
var_sum = list.map{|n| (n-mean)**2}.inject(:+).to_f
sample_variance = var_sum / (list.length - 1)
Math.sqrt(sample_variance)
end
If the records at hand are of type Integer or Rational, you may want to compute the variance using Rational instead of Float to avoid errors introduced by rounding.
For example:
def variance(list)
mean = list.reduce(:+)/list.length.to_r
sum_of_squared_differences = list.map { |i| (i - mean)**2 }.reduce(:+)
sum_of_squared_differences/list.length
end
(It would be prudent to add special-case handling for empty lists and other edge cases.)
Then the square root can be defined as:
def std_dev(list)
Math.sqrt(variance(list))
end
In case people are using postgres ... it provides aggregate functions for stddev_pop and stddev_samp - postgresql aggregate functions
stddev (equiv of stddev_samp) available since at least postgres 7.1, since 8.2 both samp and pop are provided.
Or how about:
class Stats
def initialize( a )
#avg = a.count > 0 ? a.sum / a.count.to_f : 0.0
#stdev = a.count > 0 ? ( a.reduce(0){ |sum, v| sum + (#avg - v) ** 2 } / a.count ) ** 0.5 : 0.0
end
end
You can place this as helper method and assess it everywhere.
def calc_standard_deviation(arr)
mean = arr.sum(0.0) / arr.size
sum = arr.sum(0.0) { |element| (element - mean) ** 2 }
variance = sum / (arr.size - 1)
standard_deviation = Math.sqrt(variance)
end
I'm working my way through the Ruby Koans in order to try and learn Ruby, and so far, so good. I've gotten to the greed koan, which at the time of this writing is 183. I've got a working solution, but I feel like I've cobbled together just a bunch of if/then logic and that I'm not embracing Ruby patterns.
In the following code, are there ways you would point me to more fully embracing Ruby patterns? (My code is wrapped in "MY CODE [BEGINS|ENDS] HERE" comments.
# Greed is a dice game where you roll up to five dice to accumulate
# points. The following "score" function will be used 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.
# MY CODE BEGINS HERE
def score(dice)
# set up basic vars to handle total points and count of each number
total = 0
count = [0, 0, 0, 0, 0, 0]
# for each die, make sure we've counted how many occurrencess there are
dice.each do |die|
count[ die - 1 ] += 1
end
# iterate over each, and handle points for singles and triples
count.each_with_index do |count, index|
if count == 3
total = doTriples( index + 1, total )
elsif count < 3
total = doSingles( index + 1, count, total )
elsif count > 3
total = doTriples( index + 1, total )
total = doSingles( index + 1, count % 3, total )
end
end
# return the new point total
total
end
def doTriples( number, total )
if number == 1
total += 1000
else
total += ( number ) * 100
end
total
end
def doSingles( number, count, total )
if number == 1
total += ( 100 * count )
elsif number == 5
total += ( 50 * count )
end
total
end
# MY CODE ENDS HERE
class AboutScoringProject < EdgeCase::Koan
def test_score_of_an_empty_list_is_zero
assert_equal 0, score([])
end
def test_score_of_a_single_roll_of_5_is_50
assert_equal 50, score([5])
end
def test_score_of_a_single_roll_of_1_is_100
assert_equal 100, score([1])
end
def test_score_of_multiple_1s_and_5s_is_the_sum_of_individual_scores
assert_equal 300, score([1,5,5,1])
end
def test_score_of_single_2s_3s_4s_and_6s_are_zero
assert_equal 0, score([2,3,4,6])
end
def test_score_of_a_triple_1_is_1000
assert_equal 1000, score([1,1,1])
end
def test_score_of_other_triples_is_100x
assert_equal 200, score([2,2,2])
assert_equal 300, score([3,3,3])
assert_equal 400, score([4,4,4])
assert_equal 500, score([5,5,5])
assert_equal 600, score([6,6,6])
end
def test_score_of_mixed_is_sum
assert_equal 250, score([2,5,2,2,3])
assert_equal 550, score([5,5,5,5])
end
end
Thanks so much to any help you can give as I try to get my head around Ruby.
Wow! There are a lot of really cool approaches being done here. I like everybody's creativity. However, I have a pedagogical problem with all of the answers presented here. ("Pedagogy is the study of … the process of teaching." -- Wikipedia)
It is obvious from the first several koans (back in about_asserts.rb) that the Path to Enlightenment does not require any prior/outside knowledge of Ruby. It also seems fairly clear that the Path doesn't even require prior programming experience. So from an educational standpoint, this koan must be answerable using only the methods, language constructs, and programming techniques taught in earlier koans. That means:
no Enumerable#each_with_index
no Enumerable#count
no Enumerable#sort
no Hash.new(0) specifying a default value
no Numeric#abs
no Numeric#divmod
no recursion
no case when
etc
Now, I'm not saying that you are not allowed to use these things in your implementation, but the koan mustn't require using them. There must be a solution that only uses constructs introduced by prior koans.
Also, since the template was just
def score(dice)
# You need to write this method
end
it seemed implied that the solution should not define other methods or classes. That is, you should only replace the # You need to write this method line.
Here is a solution that fits my philosophical requirements:
def score (dice)
sum = 0
(1..6).each do |i|
idice = dice.select { |d| d == i }
count = idice.size
if count >= 3
sum += (i==1 ? 1000 : i*100)
end
sum += (count % 3) * 100 if i == 1
sum += (count % 3) * 50 if i == 5
end
sum
end
The methods/constructs here are introduced in the following koan files:
Enumerable#each about_iteration.rb
Enumerable#select about_iteration.rb
Array#size about_arrays.rb
a ? b : c about_control_statements.rb
% about_control_statements.rb
Related StackOverflow Questions:
Ruby Koans 182. Refactor help
https://codereview.stackexchange.com/questions/423/is-this-good-ruby-ruby-koans-greed-task
A student asked Joshu, "How can I write an algorithm to calculate the scores for a dice game?"
Joshu struck the student with his stick and said: "Use a calculator."
def score(dice)
score = [0, 100, 200, 1000, 1100, 1200][dice.count(1)]
score += [0, 50, 100, 500, 550, 600][dice.count(5)]
[2,3,4,6].each do |num|
if dice.count(num) >= 3 then score += num * 100 end
end
score
end
I went through and passed each of the tests one at a time. Not sure this is a very "ruby" solution, but I do like that it's obvious what each section is doing and that there are no excess declarations of values
def score(dice)
## score is set to 0 to start off so if no dice, no score
score = 0
## setting the 1000 1,1,1 rule
score += 1000 if (dice.count(1) / 3) == 1
## taking care of the single 5s and 1s here
score += (dice.count(5) % 3) * 50
score += (dice.count(1) % 3) * 100
## set the other triples here
[2, 3, 4, 5, 6].each do |num|
score += num * 100 if (dice.count(num) / 3 ) == 1
end
score
end
Looks OK. I might have written some things slightly differently, say:
def do_triples number, total
total + (number == 1 ? 1000 : number * 100)
end
If you want to do something that few languages other than Ruby can do, I suppose the following might be justifiable under DIE and DRY, on alternate Tuesdays, but I don't think those Ruby maxims were really intended to apply to common subexpression elimination. Anyway:
def do_triples number, total
total +
if number == 1
1000
else
number * 100
end
end
def do_triples number, total
if number == 1
1000
else
number * 100
end + total
end
Here's what I did. Looks pretty similar to a few of the older replies. I would love to find some ingenious usage of inject for this one (mikeonbike's one is niiiice).
def score(dice)
total = 0
# handle triples scores for all but '1'
(2..6).each do |num|
total += dice.count(num) / 3 * num * 100
end
# non-triple score for '5'
total += dice.count(5) % 3 * 50
# all scores for '1'
total += dice.count(1) / 3 * 1000 + dice.count(1) % 3 * 100
total
end
You can condense this down to fewer lines but the readability of the algorithm gets lost so I ended up with this:
def score(dice)
result = 0;
(1..6).each do |die|
multiplier = die == 1 ? 1000 : 100
number_of_triples = dice.count(die) / 3
result += die * multiplier * number_of_triples
end
result += 100 * (dice.count(1) % 3)
result += 50 * (dice.count(5) % 3)
end
And if you're using 1.8.6, you'll have to use backports or add the count method to Array yourself:
class Array
def count(item)
self.select { |x| x == item }.size
end
end
Here is the answer I went with after about four iterations and trying to take advantage of the Ruby constructs I'm learning doing the koans:
def score(dice)
total = 0
(1..6).each { |roll| total += apply_bonus(dice, roll)}
return total
end
def apply_bonus(dice, roll, bonus_count = 3)
bonus = 0
bonus = ((roll == 1 ? 1000 : 100) * roll) if (dice.count(roll) >= bonus_count)
bonus += 50 * (dice.count(5) % bonus_count) if (roll == 5)
bonus += 100 * (dice.count(1) % bonus_count) if (roll == 1)
return bonus
end
Yet another answer :)
def score(dice)
score = 0
for num in 1..6
occurrences = dice.count {|dice_num| dice_num == num}
score += 1000 if num == 1 and occurrences >= 3
score += 100 * (occurrences % 3) if num == 1
score += 100 * num if num != 1 and occurrences >= 3
score += 50 * (occurrences % 3) if num == 5
end
score
end
This is the simplest and most readable solution that I came up with. This also accounts for a few situations not in the tests, such as a roll of six 5's or six 1's.
def score(dice)
score = 0
(1..6).each { |d|
count = dice.find_all { |a| a == d }
score = ( d == 1 ? 1000 : 100 ) * d if count.size >= 3
score += (count.size - 3) * 50 if (count.size >= 4) && d == 5
score += (count.size - 3) * 100 if (count.size >= 4) && d == 1
score += count.size * 50 if (count.size < 3) && d == 5
score += count.size * 100 if (count.size < 3) && d == 1
}
score
end
I opted to use the size method instead of the count method as count isn't supported by all versions of Ruby and the koans had not tested count up to this test.
def score(dice)
total = 0
sets = dice.group_by{|num| num }
sets.each_pair do |num, values|
number_of_sets, number_of_singles = values.length.divmod(3)
number_of_sets.times { total += score_set(num) }
number_of_singles.times { total += score_single(num) }
end
total
end
def score_set(num)
return 1000 if num == 1
num * 100
end
def score_single(num)
return 100 if num == 1
return 50 if num == 5
0
end
This was my eventual solution after initially having a similar if/then/else mess on my first attempt.
def score(dice)
score = 0
dice.uniq.each do |roll|
score += dice.count(roll) / 3 * (roll == 1 ? 1000 : 100*roll)
score += dice.count(roll) % 3 * (roll == 1 ? 100 : (roll == 5 ? 50 : 0))
end
score
end
I'd say you have it looking very Ruby-like already. The only thing that doesn't look very Rubyish to me would be the use of camelCase method names instead of snake_case, but of course that's a personal convention and I haven't read the koans myself.
Other than that, your example wouldn't be improved much by using case/when or any other solution for that matter. Aim for anything less than 3 elseif operations, anything more than that and you'd probably want to hunt for a better solution.
You could shorten [0, 0, 0, 0, 0, 0] to [0] * 6 but aside from the camelCase #injekt mentioned it looks fine to me. I'd be quite happy to see this in a code review.
Also I suppose your doTriples and doSingles don't really need their temporary variables.
def doTriples( number, total )
if number == 1
total + 1000
else
total + ( number ) * 100 # be careful with precedence here
end
end
You may want to change
# for each die, make sure we've counted how many occurrencess there are
dice.each do |die|
count[ die - 1 ] += 1
end
into a hash, such as
count = Hash.new(0)
dice.each do |die|
count[die] += 1
end
or even
count = {} # Or Hash.new(0)
grouped_by_dots = dice.group_by {|die| die}
1.upto(6) do |dots| # Or grouped_by_dots.each do |dots, dice_with_those_dots|
dice_with_those_dots = grouped_by_dots.fetch(dots) {[]}
count_of_that_dots = dice_with_those_dots.length
count[dots] = count_of_that_dots
end
That way you don't have to have index + 1 littered throughout your code.
It'd be nice if Ruby had a count_by method built in.
My 2 cents. Having new methods for singles/doubles seems like a roundabout way of doing something very simple.
def score(dice)
#fill initial throws
thrown = Hash.new(0)
dice.each do |die|
thrown[die]+=1
end
#calculate score
score = 0
faces.each do |face,amount|
if amount >= 3
amount -= 3
score += (face == 1 ? 1000 : face * 100)
end
score += (100 * amount) if (face == 1)
score += (50 * amount) if (face == 5)
end
score
end
Well,
Here's my solution:
def score(dice)
total = 0
#Iterate through 1-6, and add triples to total if found
(1..6).each { |roll| total += (roll == 1 ? 1000 : 100 * roll) if dice.count(roll) > 2 }
#Handle Excess 1's and 5's
total += (dice.count(1) % 3) * 100
total += (dice.count(5) % 3) * 50
end
Once I found the "count" method for an array, this exercise was pretty easy.
Here is my answer. I do not know if it is good or not, but at least, it looks clear :)
RULEHASH = {
1 => [1000, 100],
2 => [100,0],
3 => [100,0],
4 => [100,0],
5 => [100,50],
6 => [100,0]
}
def score(dice)
score = 0
RULEHASH.each_pair do |i, rule|
mod = dice.count(i).divmod(3)
score += mod[0] * rule[0] * i + mod[1] * rule[1]
end
score
end
My solution is not ruby-like style. Just for fun and shortest code. We can set rules through hash p.
def score(dice)
p = Hash.new([100,0]).merge({1 => [1000,100], 5 => [100,50]})
dice.uniq.inject(0) { |sum, n| sum + dice.count(n) / 3 * n * p[n][0] + dice.count(n) % 3 * p[n][1] }
end
My answer uses a "lookup table" approach...
def score(dice)
tally = (1..6).inject(Array.new(7,0)){|a,i| a[i] = dice.count(i); a}
rubric = {1 => [0,100,200,1000,1100,1200], 5 => [0,50,100,500,550,600]}
score = rubric[1][tally[1]] + rubric[5][tally[5]]
[2,3,4,6].each do |i| score += 100 * i if dice.count(i) >= 3 end
score
end
Mine was similar to a couple of others posted here.
score = 0
[1,2,3,4,5,6].each {|d|
rolls = dice.count(d)
score = (d==1 ? 1000 : 100)*d if rolls >= 3
score += 100*(rolls % 3) if d == 1
score += 50*(rolls % 3) if d == 5
}
score
Me and my girlfriend were going through these rubykoans this weekend and I had quite a bit of fun golfing on this and trying many different solutions. Here is a reasonably short data-driven solution:
SCORES = [[1000, 100], [200, 0], [300, 0], [400, 0], [500, 50], [600, 0]]
def score(dice)
counts = dice.group_by(&:to_i).map { |i, j| [i-1, j.length] }
counts.inject(0) do |score, (i, count)|
sets, singles = count.divmod 3
score + sets * SCORES[i][0] + singles * SCORES[i][1]
end
end
Here is my obligatory one-liner (and perhaps FP version):
SCORES = [[1000, 100], [200, 0], [300, 0], [400, 0], [500, 50], [600, 0]]
def score(dice)
dice.group_by(&:to_i).inject(0) {|s,(i,j)| s + j.size / 3 * SCORES[i-1][0] + j.size % 3 * SCORES[i-1][1]}
end
I also went some weird routes as well:
SCORES = [[1000, 100], [200, 0], [300, 0], [400, 0], [500, 50], [600, 0]]
def score(dice)
dice.group_by(&:to_i).inject(0) do |s, (i,j)|
s + j.size.divmod(3).zip(SCORES[i-1]).map {|a,b| a*b }.reduce(:+)
end
end
All programmers should be screwing around with little problems like this...It is like performing morning stretches :)
def score(dice)
result = 0
result += 1000 * (dice.find_all{|e| e == 1}).length.divmod(3)[0]
result += 100 * (dice.find_all{|e| e == 1}).length.divmod(3)[1]
result += 50 * (dice.find_all{|e| e == 5}).length.divmod(3)[1]
(2..6).each {|value| result += value*100 * (dice.find_all{|e| e == value}).length.divmod(3)[0]}
return result
end
And what about this solution?
Thanks for the feedback!
def score(dice)
count = Hash.new(0)
dice.each do |die|
count[die] += 1
end
total = 0
count.each_pair { |die, set| total += set < 3 ? single_value(die,set) : triple_value(die,set)}
total
end
def single_value(die,set)
value = 0
value += (set * 100) if die == 1
value += (set * 50) if die == 5
value
end
def triple_value(die,set)
value = 0
diff = set - 3
value += single_value(die,diff)
value += die == 1 ? 1000 : die * 100
value
end
I used a slightly different method to others here, and (naturally) it's one that I see as preferable. It's very DRY and uses ruby methods fairly extensively to avoid manual loops and branches as much as possible. Should be relatively obvious, but essentially what is happening is we loop through each unique dice roll, and use iterative erosion of the number of occurences of that roll to add the appropriate points to an aggregate total score.
def score(dice)
score = 0 # An initial score of 0.
throw_scores = { 1 => 10, 2 => 2, 3 => 3, 4 => 4, 5 => 5, 6 => 6 }
# A hash to store the scores for each dice throw
dice.uniq.each { |throw| # for each unique dice value present in the "hand"
throw_count = (dice.select { |item| item == throw }).count
# use select to store the number of times this throw occurs
while throw_count > 0
# iteratively erode the throw count, accumulating
# points as appropriate along the way.
if throw_count >= 3
score += throw_scores[throw] * 100
throw_count -= 3
elsif throw == 1 || throw == 5
score += throw_scores[throw] * 10
throw_count -= 1
else
throw_count -= 1
end
end
}
return score
end
And another one, just for the fun of it:
def score(dice)
result = 0
dice.uniq.each { |k|
result += ((dice.count(k) / 3) * 1000 + (dice.count(k) % 3) * 100) if k == 1
result += ((dice.count(k) / 3) * 100 * k + (dice.count(k) % 3) * ( k == 5 ? 50 : 0 )) if k != 1
}
result
end
Here's my opinion. All other solutions here try to be clever. There's a place for learning clever tricks, but it's even more important to learn to write clear and maintainable code. The main problem I see with all of these solutions is that it's very difficult to discern the scoring rules from the code. Can you read your solution and make sure that it's correct in your head? Then imagine someone asks you to add a new scoring rule, or remove one. Can you quickly point to the place where the rule must be added or removed?
Here's my solution. I'm sure it can be improved, but look at the shape of the "score" function. This is the sort of code that I would not mind to maintain.
class Array
def occurrences_of(match)
self.select{ |number| match == number }.size
end
def delete_one(match)
for i in (0..size)
if match == self[i]
self.delete_at(i)
return
end
end
end
end
def single_die_rule(match, score, dice)
dice.occurrences_of(match) * score
end
def triple_rule(match, score, dice)
return 0 if dice.occurrences_of(match) < 3
3.times { dice.delete_one match }
score
end
def score(dice)
triple_rule(1, 1000, dice) +
triple_rule(2, 200, dice) +
triple_rule(3, 300, dice) +
triple_rule(4, 400, dice) +
triple_rule(5, 500, dice) +
triple_rule(6, 600, dice) +
single_die_rule(1, 100, dice) +
single_die_rule(5, 50, dice)
end
I'm gonna have to go with:
def score(dice)
# some checks
raise ArgumentError, "input not array" unless dice.is_a?(Array)
raise ArgumentError, "invalid array size" unless dice.size <= 5
raise ArgumentError, "invalid dice result" if dice.any? { |x| x<1 || x>6 }
# setup (output var, throws as hash)
out = 0
freqs = dice.inject(Hash.new(0)) { |m,x| m[x] += 1; m }
# 3-sets
1.upto(6) { |i| out += freqs[i]/3 * (i == 1 ? 10 : i) * 100 }
# one not part of 3-set
out += (freqs[1] % 3) * 100
# five not part of 3-set
out += (freqs[5] % 3) * 50
out
end
Because most of the solutions presented so far lack basic checks. And some of them are fairly unreadable (in my book) and not very idiomatic.
Granted, the 3-set condition could be made more readable by splitting into two clauses:
# 3-sets of ones
out += freqs[1]/3 * 1_000
# 3-sets of others
2.upto(6) { |i| out += freqs[i]/3 * i * 100 }
but that's IMO mostly about personal preference.
Coming from Perl, my instinct is to use a hash:
def score(dice)
# You need to write this method
score = 0
count = Hash.new(0)
for die in dice
count[die] += 1
is_triple = (count[die] % 3 == 0)
if die == 1 then
score += is_triple ? 800 : 100
elsif die == 5 then
score += is_triple ? 400 : 50
elsif is_triple
score += 100 * die
end
end
return score
end
This has the advantage that it makes a single pass over dice. I could probably have used an Array in place of the Hash.
I grouped the dice by face, then looped over these groups, first scoring the threes, then individual dice. This is how I would score the game were I playing IRL
def score(dice)
points = 0
dice.group_by {|face| face}.each do |face,group|
while group.size >= 3
if face == 1
# A set of three ones is 1000 points
points += 1000
else
# A set of three numbers (other than ones) is worth 100 times the number.
points += 100 * face
end
group.pop(3)
end
group.each do |x|
# A one (that is not part of a set of three) is worth 100 points.
points += 100 if x==1
# A five (that is not part of a set of three) is worth 50 points.
points += 50 if x==5
end
end
return points
end
That's how I roll
Late to the party, but wanted to take a shot at answering only using knowledge introduced thus far in the Koans. Specifically, I don't use Enumerable#count like most others have.
This seems very straightforward to me, but if anyone happens along, I'd be happy to hear about an optimizations you may have.
And what can I say? I like taking advantage of array indexing.
def score(dice)
return 0 if dice.empty? # Immediately recognize an empty roll
# Create an array to hold the scores for each die face
totals = []
7.times { totals << 0 }
# Handle each roll and calculate new score
dice.each do |roll|
if roll == 5
# If we have seen two 5s thus far, make the score 500 for 5s, otherwise add 50
totals[roll] == 100 ? totals[roll] = 500 : totals[roll] += 50
elsif roll == 1
# If we have seen two 1s thus far, make the score 1000 for 5s, otherwise add 100
totals[roll] == 200 ? totals[roll] = 1000 : totals[roll] += 100
else
# If we see any other number three times, score is the number times 100
totals[roll] == 2 ? totals[roll] = roll * 100 : totals[roll] += 1
end
end
# Count up the scores for each die face; if score is less than 50, then it's just zero
return totals.inject(0) { |sum, points| points >= 50 ? sum += points : sum }
end