I've been trying to level up my python skills (more experienced in other languages) by working through the python3 version of Python Koans. After completing the greed dice scoring project exercise, I decided to make it a bit more interesting by allowing for rolls of greater than 5. More on that after the original description:Original for reference: about_scoring_project.py.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 pointsA 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 pointsscore([2,3,4,6,2]) => 0 pointsscore([3,4,5,3,3]) => 350 pointsscore([1,5,1,2,4]) => 250 pointsModifications:Allow more than five dice rollsmultiple sets are now possibleThe following test illustrates these changes:assertEqual(1000 + 100 + 600*2, score([1, 1, 1, 1, 6, 6, 6, 6, 6, 6]))Score details: 1000(one set of 1's) + 100(single 1) + 600(two sets of 6's)Review interestsGiven that this is part of Python Koans, I'm not looking for any critique of the problem or it's related setup / testing structure. The problem comes with an empty def score() and unit tests (I've also added tests to cover my changes to accepting more than 5 rolls).Having said that I'm mainly interested in feedback related to:pythonic(ality?) -- anything out of place to regular pythonistas?namingstyle / readabilityperformance considerationsquality of solution -- is this solution overly complicated?overall code qualityabout_scoring_project.py -- gist for convenience#!/usr/bin/env python# -*- coding: utf-8 -*-from runner.koan import Koanimport mathimport functoolsimport collectionsSET = 3def score(dice_rolls):    if not dice_rolls:        return 0    rolls_to_counts = collections.Counter(dice_rolls)    rolls_to_scoring_counts = _get_rolls_to_scoring_counts(rolls_to_counts)    total_points = 0    total_points += _calc_set_points(rolls_to_scoring_counts)    total_points += _calc_nonset_points(rolls_to_scoring_counts)    return total_pointsdef _get_rolls_to_scoring_counts(rolls_to_counts):    '''Return a dict of rolls to scoring counts.    ScoringCounts is a namedtuple: ScoringCounts(sets, nonsets)    containing the set count and nonset count as values for each roll key.    '''    rolls_to_scoring_counts = {}    ScoringCounts = collections.namedtuple('ScoringCounts', 'sets nonsets')    for roll in rolls_to_counts:        cur_roll_count = rolls_to_counts[roll]        if _roll_has_a_set(cur_roll_count):            set_count = math.floor(cur_roll_count / SET)            nonset_count = cur_roll_count % SET        else:            set_count = 0            nonset_count = cur_roll_count        rolls_to_scoring_counts[roll] = ScoringCounts(set_count, nonset_count)    return rolls_to_scoring_countsdef _roll_has_a_set(roll_count):    return roll_count >= SETdef _calc_set_points(rolls_to_scoring_counts):    def _accumulate_set_points(accum, roll):        SET_ROLL_TO_POINTS = {            1: 1000,            2: 2 * 100,            3: 3 * 100,            4: 4 * 100,            5: 5 * 100,            6: 6 * 100        }        return accum + SET_ROLL_TO_POINTS[roll] * rolls_to_scoring_counts[roll].sets    return functools.reduce(_accumulate_set_points, rolls_to_scoring_counts, 0)def _calc_nonset_points(rolls_to_scoring_counts):    def _accumlate_nonset_points(accum, roll):        ROLL_TO_POINTS = {            1: 100,            2: 0,            3: 0,            4: 0,            5: 50,            6: 0        }        return accum + ROLL_TO_POINTS[roll] * rolls_to_scoring_counts[roll].nonsets    return functools.reduce(_accumlate_nonset_points, rolls_to_scoring_counts, 0)# If interested in getting tests to run, check the project https://github.com/gregmalcolm/python_koans/tree/master/python3# For ref:# class Koan(unittest.TestCase):#     passclass AboutScoringProject(Koan):    def test_score_of_an_empty_list_is_zero(self):        self.assertEqual(0, score([]))    def test_score_of_a_single_roll_of_5_is_50(self):        self.assertEqual(50, score([5]))    def test_score_of_a_single_roll_of_1_is_100(self):        self.assertEqual(100, score([1]))    def test_score_of_multiple_1s_and_5s_is_the_sum_of_individual_scores(self):        self.assertEqual(300, score([1, 5, 5, 1]))    def test_score_of_single_2s_3s_4s_and_6s_are_zero(self):        self.assertEqual(0, score([2, 3, 4, 6]))    def test_score_of_a_triple_1_is_1000(self):        self.assertEqual(1000, score([1, 1, 1]))    def test_score_of_other_triples_is_100x(self):        self.assertEqual(200, score([2, 2, 2]))        self.assertEqual(300, score([3, 3, 3]))        self.assertEqual(400, score([4, 4, 4]))        self.assertEqual(500, score([5, 5, 5]))        self.assertEqual(600, score([6, 6, 6]))    def test_score_of_mixed_is_sum(self):        self.assertEqual(250, score([2, 5, 2, 2, 3]))        self.assertEqual(550, score([5, 5, 5, 5]))        self.assertEqual(1150, score([1, 1, 1, 5, 1]))    def test_ones_not_left_out(self):        self.assertEqual(300, score([1, 2, 2, 2]))        self.assertEqual(350, score([1, 5, 2, 2, 2]))    def test_score_of_rolls_larger_than_5(self):        self.assertEqual(0, score([2, 2, 3, 4, 6, 6]))    def test_score_of_larger_than_5_with_set(self):        self.assertEqual(600, score([2, 2, 3, 4, 6, 6, 6]))        self.assertEqual(600, score([2, 2, 3, 6, 6, 6, 6]))    def test_score_of_larger_than_5_with_multiple_set(self):        self.assertEqual(600*2, score([2, 2, 3, 4, 6, 6, 6, 6, 6, 6]))        self.assertEqual(1000 + 600*2, score([1, 1, 1, 4, 6, 6, 6, 6, 6, 6]))        self.assertEqual(1000 + 100 + 600*2, score([1, 1, 1, 1, 6, 6, 6, 6, 6, 6]))
Greed Dice Scoring Game expanded - Python Koans
python;performance;programming challenge;python 3.x;dice
Overall, the code looks clean and readable, so here are some basics nits and tips for the code snippet in your question.Don't create new variables for everything. Sometimes you don't need them.total_points = 0total_points += _calc_set_points(rolls_to_scoring_counts)total_points += _calc_nonset_points(rolls_to_scoring_counts)return total_pointsWhy not just do this?return _calc_set_points(rolls_to_scoring_counts) + _calc_nonset_points(rolls_to_scoring_counts)While we're addressing this function, lets discuss code convention. You're using lower snake case, and your naming conventions are consistent, which is good (I suggest taking a look at the PEP8 style guide for a concrete set of rules around Pythonic code). However, some variable names are a bit too long. Descriptive variable names are good, but providing redundant information is not.def score(dice_rolls):    if not dice_rolls:        return 0    rolls_to_counts = collections.Counter(dice_rolls)    rolls_to_scoring_counts = _get_rolls_to_scoring_counts(rolls_to_counts)    total_points = 0    total_points += _calc_set_points(rolls_to_scoring_counts)    total_points += _calc_nonset_points(rolls_to_scoring_counts)    return total_pointsWhy not rewrite it this way?def score(rolls):    if not rolls:        return 0    counts = collections.Counter(rolls)    scoring_counts = _get_rolls_to_scoring_counts(counts)    return _calc_set_points(scoring_counts) + _calc_nonset_points(scoring_counts)This is much more concise and conveys the same amount of information.Underscore prefixed function names.def _get_rolls_to_scoring_counts(rolls_to_counts):Prefixing with an underscore is generally a developer convention in Python indicating that the method is private. Given the context of your function, I don't think you intend for your code to be a module whose functions are imported and used elsewhere. Having the underscore prefix seems unnecessary to me.Too much abstraction?def _roll_has_a_set(roll_count):    return roll_count >= SETDefining this seems overkill to me, since you can do this operation in your code.ConstantsROLL_TO_POINTS = {    1: 100,    2: 0,    3: 0,    4: 0,    5: 50,    6: 0}Extract this to the top of the file as a global constant, like your SET variable. 6. Unneeded import.    return functools.reduce(_accumlate_nonset_points, rolls_to_scoring_counts, 0)Python has a built-in reduce() function.These are just some basic nits and optimizations. Other than that, I don't see any major glaring errors. Happy coding!Python Experience: +100LEVEL UP!