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
Что я качаю