Ruby 公案 182 - 贪婪骰子

Ruby Koan 182 - Greed Dice

我很快得出结论,我不能成为一名程序员。尽管在 Ruby Koans 中记下了足够的笔记并练习了所有内容,但在完成 Codecademy 上的 Ruby 课程三遍以及目前平均每天学习 6 小时的 Chris Pine 的书的过程中…… .当前的 Koan 是一次沮丧的练习,并认识到并非每个人都可以成为程序员。

练习如下

# 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)
end

所以乍一看,我假设测试将通过 dice 提供随机数功能?还是需要我自己写?现在我必须限制掷骰子的范围?然后我所要做的就是获取数字并将它们放入一个数组中,但最多只能有 5 个位置(所以像 while array has 5 or less entries? 然后我需要定义点系统的工作方式。所以像 score[0] = 100, score[2]= 200?但是现在我已经对分数进行了硬编码,如果第一个位置不是 1 而是 5 怎么办?那么数字将是 50。那么 if/else 语句怎么样?类似于

if score[0] == 1
  point = 100
elsif score[0] == 5
  point = 50
else point = 0

然后我对位置 [1]-[4] 重复这个?

尽管非常想要,但我不想简单地 google 答案,我宁愿得到一些指导,因为找不到更好的词。

希望这不是一个太笼统的问题,但你是如何处理这样的问题的?在解决这个问题之前,也许我应该先从头到尾通读一下 Pine 的书?

你可以写你的方法:

def score(d1,d2,d3,d4,d5)
  ...
end

您需要做的第一件事是确定五个值中的三个是否相同。如果三个相同,您将需要知道它们是否都是 1 或其他值,并且您将需要知道其他两个值是什么。如果至多有两个值相同,您也需要知道这一点。这才是问题的关键。所以让我们写一个方法:

def partition(d1,d2,d3,d4,d5)
  ...
end

这是从 score 调用的,如下所示:

three_value, other_values = partition(d1,d2,d3,d4,d5)

其中:

  • three_value等于三个相等值组的公共值(1-6)或者(比如)nil如果没有三个相等值组;和
  • other_values[d1,d2,d3,d4,d5] 中排除三个相等值(如果有一组三个相等值)或 [d1,d2,d3,d4,d5](如果有没有一组三个相等的值)。

例如,

three_value, other_values = partition(1,3,4,3,6)
  #=> [nil, [1, 3, 4, 3, 6]] 
three_value, other_values = partition(1,3,4,3,3)
  #=> [3, [1, 4]] 
three_value, other_values = partition(1,3,3,3,3)
  #=> [3, [1, 3]] 
three_value, other_values = partition(3,3,3,3,3)
  #=> [3, [3, 3]] 

一旦我们有了方法 partition,方法 score 如果非常简单的话:

def score(d1,d2,d3,d4,d5)
  three_value, other_values = partition(d1,d2,d3,d4,d5)

  total = case three_value
          when 1   then 1000
          when nil then    0
          else          three_value * 1000
          end

  other_values.each do |i|
    total += case i
             when ...
               ...
             when ...
               ...
             end
  end
end

在进入方法partition之前,我们可以将score简化如下:

def score(*d)
  three_value, other_values = partition(*d)

  total = case three_value
          when 1   then 1000
          when nil then    0
          else          three_value * 1000
          end

  other_values.each do |i|
    total += case i
             when ...
               ...
             when ...
               ...
             end
  end
end

使用 "splat" 运算符 * 很方便,因为我们不关心掷骰子的顺序。在调用方法scorepartition时,如果d = [1,3,4,3,3]score(*d)等同于:

score(1,3,4,3,3)    

(看看为什么 * 被称为 "splat"?)

在上面的方法 score 中,d(不是 *d)是一个包含五个值的数组。

现在让我们看一下方法partition。我们需要计算每个结果 (1-6) 发生的次数。这对哈希来说是个好工作:

def partition(*d)
  counts = {}
  d.each do |i|
    if counts.key?(i)
      counts[i] += 1
    else
      counts[i] = 1
    end
  end
  ...
end

假设d = [1,3,4,3,3]。然后

counts #=> {1=>1, 3=>3, 4=>1}

我们现在需要找到具有最大值的键。为此,我们可以使用 Enumerable#max_by:

k, v = counts.max_by { |k,v| v }
  #=> [3, 3]
k #=> 3
v #=> 3

然后我们可以这样计算other_values

other_values = case v
               when 3
                 d - [k]
               when 4
                 (d - [k]) << k
               when 5
                 (d - [k]) << k << k
               else
                 d
               end

注意Array#-是数组差分法

最后,

 three_value = (v >= 3) ? k : nil

partition将return:

 [three_value, other_values]

你能把所有这些放在一起吗?

[在您获得有效代码之前,您可能不想阅读以下内容。]

一旦您获得了 Ruby 的经验,您将能够按如下方式编写 partition

def partition(*d)
  k,v = d.each_with_object(Hash.new(0)) { |i,h| h[i]+=1 }.max_by(&:last)
  (v < 3)) ? [nil, d] : [k, d - [k] + [k]*(v-3)]
end                      

旁白:我想看一个核心方法Array#difference,我定义并阐述here。这将允许表达 partition 正文的最后一行:

(v < 3)) ? [nil, d] : [k, d.difference([k,k,k])]