Ruby Greed Koan – 我怎样才能改善我的if / then汤?

我正在研究Ruby Koans,以便尝试学习Ruby,到目前为止,这非常好。 我已经得到了贪婪的koan,在撰写本文时是183.我有一个有效的解决方案,但我觉得我只拼凑了一堆if / then逻辑而且我不是拥抱Ruby模式。

在下面的代码中,有没有什么方法可以指出我更全面地接受Ruby模式? (我的代码包含在“MY CODE [BEGINS | ENDS] HERE”评论中。

# 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. (eg 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 = 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 

非常感谢您提供的任何帮助,因为我试图了解Ruby。

看起来不错。 我可能会写一些略有不同的东西,说:

 def do_triples number, total total + (number == 1 ? 1000 : number * 100) end 

如果你想做一些除Ruby之外的其他几种语言可以做的事情,我认为在DIE和DRY下,在备用的星期二,以下可能是合理的,但我不认为那些Ruby格言真正意图适用于常见的子表达式消除。 无论如何:

 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 

哇! 这里有很多非常酷的方法。 我喜欢每个人的创造力。 但是,我在这里提出了所有答案的教学问题。 (“教育学是对……教学过程的研究。” – 维基百科)

从前几个koans(返回about_asserts.rb)可以明显看出,启发之不需要任何Ruby的先验/外部知识。 似乎相当清楚的是,Path甚至不需要先前的编程经验。 因此,从教育的角度来看,这个公案必须只使用早期公案中教授的方法,语言结构和编程技术来回答。 这意味着:

  • no Enumerable#each_with_index
  • 没有Enumerable#count
  • 没有Enumerable#sort
  • no Hash.new(0)指定默认值
  • 没有Numeric#abs
  • 没有Numeric#divmod
  • 没有递归
  • 没有when
  • 等等

现在,我并不是说你不允许在你的实现中使用这些东西,但是koan不能要求使用它们。 必须有一个只使用先前公案引入的结构的解决方案。

此外,由于模板只是

 def score(dice) # You need to write this method end 

似乎暗示解决方案不应该定义其他方法或类。 也就是说,你应该只替换# You need to write this method行。

这是一个符合我的哲学要求的解决方案:

 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 

这里的方法/构造在以下koan文件中引入:

 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 

相关StackOverflow问题:

一名学生问Joshu,“我如何编写算法来计算骰子游戏的分数?”

约瑟用棍子击打了学生并说道:“使用计算器。”

 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 

我经历了一次,每次通过一次测试。 不确定这是一个非常“ruby”的解决方案,但我确实很清楚每个部分正在做什么以及没有多余的值声明

 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 

这就是我做的。 看起来非常类似于一些较旧的回复。 我很想找到一些巧妙的注入用法(mikeonbike的一个是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 

您可以将其压缩为更少的行,但算法的可读性会丢失,所以我最终得到了这样的结果:

 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 

如果你使用1.8.6,你将不得不使用backports或自己将count方法添加到Array:

 class Array def count(item) self.select { |x| x == item }.size end end 

这是我经过大约四次迭代后尝试利用我正在学习做koans的Ruby构造的答案:

 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 

又一个答案:)

 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 

这是我提出的最简单,最易读的解决方案。 这也是一些不在测试中的情况,例如一卷六个5或六个1。

 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 

我选择使用size方法而不是count方法,因为所有版本的Ruby都不支持count ,并且koans没有测试计数到此测试。

 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 

这是我最初在第一次尝试时遇到类似的if / then / else混乱的最终解决方案。

 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 

我会说你看起来已经非常像Ruby了。 对我来说唯一看起来不那么Rubyish的东西就是使用camelCase方法名而不是snake_case,但当然这是个人惯例,我自己也没有读过这些koans。

除此之外,通过使用case / when或任何其他解决方案,您的示例不会有太大改进。 瞄准任何少于3个其他操作的东西,除此之外的任何东西,你可能想要寻找更好的解决方案。

你可以将[0, 0, 0, 0, 0, 0] 0,0,0,0,0,0 [0] * 6缩短为[0] * 6但是除了提到的camelCase @injekt之外它对我来说很好。 我很乐意在代码审查中看到这一点。

另外我认为你的doTriples和doSingles并不真正需要它们的临时变量。

 def doTriples( number, total ) if number == 1 total + 1000 else total + ( number ) * 100 # be careful with precedence here end end 

你可能想要改变

  # for each die, make sure we've counted how many occurrencess there are dice.each do |die| count[ die - 1 ] += 1 end 

变成哈希,比如

 count = Hash.new(0) dice.each do |die| count[die] += 1 end 

甚至

 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 

这样,您不必在整个代码中散布index + 1

如果Ruby内置了count_by方法, count_by了。

我的2美分。 拥有单打/双打的新方法似乎是一种非常简单的迂回方式。

 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 

好,

这是我的解决方案:

 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 

一旦我找到了数组的“计数”方法,这个练习就很容易了。

这是我的答案。 我不知道它是否好,但至少看起来很清楚:)

 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 

我的解决方案不是类似ruby的风格。 只是为了有趣和最短的代码。 我们可以通过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 

我的回答使用了“查找表”方法……

 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 

我和这里发布的其他几个人类似。

 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 

我和我的女朋友本周末正在经历这些ruby,我在这方面打了很多乐趣并尝试了许多不同的解决方案。 这是一个相当短的数据驱动解决方案:

 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 

这是我的强制性单线程(也许是FP版本):

 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 

我也走了一些奇怪的路线:

 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 

所有的程序员都应该把这样的小问题搞砸……这就像表演早晨一样:)

 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 

这里有一些不错的答案,还有一个时间吗?

我采用了使用查找来最小化条件语句的方法 – 所以只有一个if。 [而且我认为我只使用了已经在koans中引入的内容。]

 def score(dice) count = [0]*7 score = [0, 100, 0, 0, 0, 50, 0] bonus = [0, 700, 200, 300, 400, 350, 600] total = 0 dice.each do |roll| total += score[roll] count[roll] += 1 total += bonus[roll] if count[roll]==3 end total end 

(我知道我可以使查找数组有六个元素,但我认为更好的可读性值得几个字节。)

那个解决方案呢? 感谢您的反馈!

 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 

我在这里使用了与其他人稍微不同的方法,并且(自然地)我认为这是更好的方法。 它非常干,并且使用ruby方法相当广泛,以尽可能避免手动循环和分支。 应该是相对明显的,但基本上正在发生的事情是我们循环每个独特的骰子滚动,并使用该滚动的出现次数的迭代侵蚀,以将合适的点添加到总的总得分。

 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 

另一个,只是为了它的乐趣:

 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 

这是我的意见。 这里的所有其他解决方案都试图变得聪明。 有一个学习巧妙技巧的地方,但学习编写清晰可维护的代码更为重要。 我在所有这些解决方案中看到的主要问题是很难从代码中辨别评分规则。 你能阅读你的解决方案并确保它在你脑子里是正确吗? 然后想象某人要求您添加新的评分规则,或删除一个。 您能快速指向必须添加或删除规则的位置吗?

这是我的解决方案。 我确信它可以改进,但看看“得分”function的形状。 这是我不介意维护的那种代码。

 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 

我必须去:

 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 

因为到目前为止提供的大多数解决方案缺乏基本检查。 其中一些是相当难以理解的(在我的书中)而不是非常惯用。

当然,通过分成两个子句,可以使3组条件更具可读性:

  # 3-sets of ones out += freqs[1]/3 * 1_000 # 3-sets of others 2.upto(6) { |i| out += freqs[i]/3 * i * 100 } 

但这是IMO主要是关于个人偏好。

来自Perl,我的直觉是使用哈希:

 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 

这样做的好处是可以在dice进行一次通过。 我可能用一个数组来代替哈希。

我按照脸部对骰子进行分组,然后对这些组进行循环,首先得分三分,然后是个人骰子。 这就是我在玩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 

这就是我滚动的方式