Ruby Greed Koan - Как я могу улучшить мой суп-поп-суп?

Я прокладываю себе путь через Ruby Koans, чтобы попытаться изучить Ruby, и пока что так хорошо. Я добрался до жадного коана, который на момент написания этой статьи - 183. У меня есть рабочее решение, но я чувствую, что я объединил только кучу логики if/then и что я не охватывая шаблоны Ruby.

В следующем коде есть способы, которыми вы могли бы указать мне более полно охватывать шаблоны Ruby? (Мой код завернут в комментарии "MY CODE [НАЧАТЬ | ENDS] ЗДЕСЬ".

# 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

Большое спасибо за любую помощь, которую вы можете дать, когда я пытаюсь поднять голову вокруг Ruby.

Ответы

Ответ 1

Выглядит хорошо. Возможно, я написал несколько вещей по-другому, скажем:

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

Если вы хотите сделать что-то, что могут сделать на нескольких языках, отличных от Ruby, я полагаю, что следующие альтернативы могут быть оправданы при использовании DIE и DRY в альтернативных вторниках, но я не думаю, что те Ruby maxims действительно были предназначены для применения к общим устранение подвыражений. В любом случае:

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

Ответ 2

Ничего себе! Здесь очень много замечательных подходов. Мне нравятся все творчество. Однако у меня есть педагогическая проблема со всеми ответами, представленными здесь. ( "Педагогика - это изучение... процесса обучения". - Википедия)

Очевидно, что из первых нескольких коанов (назад в about_asserts.rb), что путь к Просветлению не требует какого-либо предварительного/внешнего знания Ruby. Также представляется довольно очевидным, что Path даже не требует предварительного программирования. Поэтому с точки зрения образования этот коан должен быть ответственным, используя только методы, языковые конструкции и методы программирования, преподаваемые в более ранних коанах. Это означает:

  • no Enumerable#each_with_index
  • no Enumerable#count
  • no Enumerable#sort
  • no Hash.new(0) с указанием значения по умолчанию
  • no Numeric#abs
  • no Numeric#divmod
  • нет рекурсии
  • no case when
  • и т.д.

Теперь я не говорю, что вам не разрешено использовать эти вещи в вашей реализации, но коан не должен использовать их. Там должен быть решением, которое использует только конструкции, введенные предыдущими коанами.

Кроме того, поскольку шаблон был просто

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:

Ответ 3

Студент спросил Джошу: "Как я могу написать алгоритм для вычисления баллов за игру в кости?"

Джошу ударил студента своей палкой и сказал: "Используйте калькулятор".

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

Ответ 4

Я прошел и прошел каждый из тестов по одному. Не уверен, что это очень "рубиновое" решение, но мне нравится, что он ясно, что делает каждый раздел, и что нет лишних деклараций значений

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

Ответ 5

Вот что я сделал. Выглядит довольно похоже на несколько старых ответов. Я хотел бы найти какое-то гениальное использование инъекции для этого (mikeonbike one 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

Ответ 6

Вы можете сконденсировать это до меньшего количества строк, но читаемость алгоритма теряется, поэтому я закончил с этим:

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 или добавить метод подсчета в Array самостоятельно:

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

Ответ 7

Вот ответ, который я отправил после четырех итераций и попытался воспользоваться конструкциями 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

Ответ 8

Еще один ответ:)

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

Ответ 9

Это самое простое и читаемое решение, с которым я столкнулся. Это также объясняет несколько ситуаций не в тестах, таких как бросок шести 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, поскольку count не поддерживается всеми версиями Ruby, а коаны не тестировали счетчик до этого теста.

Ответ 10

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

Ответ 11

Это было мое возможное решение после того, как изначально было схоже, если /then/else mess при первой попытке.

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

Ответ 12

Я бы сказал, что у вас он выглядит очень похожим на Ruby. Единственное, что не выглядит очень рубистским для меня, было бы использование имен методов camelCase вместо snake_case, но, конечно, это личное соглашение, и я сам не читал коан.

Кроме этого, ваш пример не будет сильно улучшен, если использовать case/when или любое другое решение. Направьте на что-нибудь меньшее, чем 3 операции elseif, что-то большее, чем это, и вы, вероятно, захотите найти лучшее решение.

Ответ 13

Вы можете сократить [0, 0, 0, 0, 0, 0] до [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

Ответ 14

Вы можете изменить

  # 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.

Ответ 15

Мои 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

Ответ 16

Ну,

Здесь мое решение:

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 and 5's
    total += (dice.count(1) % 3) * 100 
    total += (dice.count(5) % 3) * 50
end

Как только я нашел метод count для массива, это упражнение было довольно простым.

Ответ 17

Вот мой ответ. Я не знаю, хорошо это или нет, но, по крайней мере, это выглядит ясно:)

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

Ответ 18

Мое решение не похоже на рубиновый стиль. Просто для удовольствия и кратчайший код. Мы можем установить правила через хэш 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

Ответ 19

Мой ответ использует подход "таблицы поиска"...

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

Ответ 20

Шахта была похожа на пару других, опубликованных здесь.

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

Ответ 21

В этот уик-энд я и моя подруга проходили через этих рубикоанов, и мне было очень весело играть в гольф на этом и пробовать много разных решений. Вот достаточно короткое решение, управляемое данными:

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

Все программисты должны закручиваться с небольшими проблемами вроде этого... Это похоже на выполнение утренних растяжек:)

Ответ 22

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

Ответ 23

Некоторые хорошие ответы здесь, время для еще одного?

Я использовал подход, использующий lookups для минимизации условных операторов - так что только один if. [И я думаю, что я использовал только то, что было введено уже в коанах.]

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

(Я знаю, что я мог бы сделать массив поиска шести элементов, но я думаю, что более высокая читаемость стоит нескольких байтов.)

Ответ 24

А как насчет этого решения? Спасибо за отзыв!

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

Ответ 25

Я использовал немного другой метод для других здесь, и (естественно) это тот, который я считаю предпочтительным. Он очень СУХОЙ и использует рубиновые методы достаточно широко, чтобы избежать как можно большего числа ручных петель и ветвей. Должно быть относительно очевидно, но в основном то, что происходит, мы прокручиваем каждый уникальный бросок кости и используем итеративную эрозию количества вхождений этого рулона, чтобы добавить соответствующие точки к суммарному суммарному счету.

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

Ответ 26

И еще один, просто для удовольствия:

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

Ответ 27

Вот мое мнение. Все другие решения здесь стараются быть умными. Там есть место для изучения умных трюков, но еще важнее научиться писать четкий и удобный код. Основная проблема, которую я вижу во всех этих решениях, заключается в том, что очень сложно распознать правила оценки из кода. Можете ли вы прочитать свое решение и убедиться, что оно исправлено в вашей голове? Тогда представьте, что кто-то попросит вас добавить новое правило подсчета очков или удалить его. Можете ли вы быстро указать место, где правило должно быть добавлено или удалено?

Вот мое решение. Я уверен, что его можно улучшить, но посмотрите на форму функции "оценка". Это тот код, который я бы не прочь поддерживать.

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

Ответ 28

Мне нужно будет:

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 в основном о личных предпочтениях.

Ответ 29

Исходя из 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. Возможно, я использовал массив вместо Хэша.

Ответ 30

Я сгруппировал кости по лицу, затем зациклился над этими группами, сначала забил три, а затем отдельные кости. Вот как я забил бы игру, играя в 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

Что я качаю