Re: [Tutor] Long post: Request comments on starting code and test code on chess rating project.
It is rather late here, so I won't get to the links until much later today, but ... On Sun, Aug 13, 2017 at 1:42 AM, Steven D'Aprano wrote: > I haven't had a chance to read the entire post in detail, but one thing > which stands out: > > On Sun, Aug 13, 2017 at 12:22:52AM -0500, boB Stepp wrote: > >> I have started my coding with a RatingCalculator class. The intent of >> this class is to gather all methods together needed to validate and >> calculate chess ratings. My intent is to only ever have a single >> instance of this class existing during a given running of the program. >> (BTW, is there a technique to _guarantee_ that this is always true?) > > Yes, this is called the "Singleton" design pattern, which is very > possibly the most popular and common design pattern in OOP. > > It is also very possibly the *least appropriate* use of OOP techniques > imaginable, so much so that many people consider it to be an > anti-pattern to be avoided: [...] > In Python, the equivalent to the singleton is the module. If you have a > class full of methods which is only ever going to be instantiated once, > *almost always* the solution in Python is to use a module full of > functions. Hmm. I should have gone with my gut instinct. I did not see any value in a one-object class, and felt I should just have a module with these functions. But the goal of this project is to use OOP, so my intended design was to *force* these methods into a class and use the class as a convenient container. OTOH, my other classes I plan to code -- Player, Game and possibly Database -- make more sense as classes as the first two will have multiple instances with different states per object. And eventually there will be a GUI in tkinter which is naturally organized by classes. Thanks, Steve. This is especially useful feedback as it enhances my understanding of when and how to implement classes vs. collections of functions. boB ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: https://mail.python.org/mailman/listinfo/tutor
Re: [Tutor] Long post: Request comments on starting code and test code on chess rating project.
On 13/08/17 06:22, boB Stepp wrote: > The intent of this project is more than just calculate chess ratings. > I also envision being able to store a record of all game results and > player information for my school chess players that I give lessons to. That's fair enough but OOP or no OOP the basic advice is the same: divide and conquer. Make the admin and logic parts of the application separate and work on the core of each. Bring them together via the UI (which initially may be a set of CLI tools...). > I have started my coding with a RatingCalculator class. The intent of > this class is to gather all methods together needed to validate and > calculate chess ratings. That may be appropriate in that it is often the case that you have a single class representing the applications a whole. But a calculator sounds like a low level helper class to me, possibly encapsulating the detailed algorithms used top create the ratings. I'd expect there to be potentially several subclasses representing different algorithms. I'd expect the calculator instances to be passed as an object into the rate() method of a game object... > My intent is to only ever have a single > instance of this class existing during a given running of the program. I notice Steven's post, to which I'll add amen. Singletons in Python are rarely needed, just make your game a module. Maybe rethink the role of the calculator class. > added any methods yet. Currently I am adding class constants that > will be used in this class' methods. Since internal data/attributes should support the operations its usual to start with the operations before defining the supporting data. Seriously consider using the CRC methodology to identify and describe your projects classes. It can be in a single document rather than physical cards, the important thing is to briefly describe what each class is called, its responsibilities(often becoming methods) and collaborators (other classes which become attributes or method parameters). > These constants are for internal > class use only and should not be altered from outside the class. It sounds like they are to be shared values across methods of various objects/methods, that suggests putting them in a shared class or module. > is the current state of the main.py program (Which will probably be > broken into multiple modules as the project develops.): While Python doesn't require using a module per class, it is better to keep related classes in a single module so I strongly suggest breaking it into multiple modules. At the least 3: rating engine, admin and UI. > """Module to process chess ratings.""" > > class RatingCalculator: > """This class contains methods to validate and calculate chess ratings.""" I'd suggest that validation and calculation are two very different things. Probably requiring separate classes. But we need more detail on the objects involved. What does a rating rate - a Game? a Move? a strategy? And what does validation validate? A rating perhaps? You are in danger of focusing on the verbs (rating calculate, create, edit, etc rather than the objects that do these things. You need to turn your initial thoughts away from what the application does (traditional procedural coding style) and onto thinking about what kind of objects make up the application. It's common to start with a fairly long list then, as you apply CRC analysis(*), realize that many of the objects are only attributes. But it's better to start with too many objects than to try to squeeze functionality into just a few uber-objects. (*)If you discover that a candidate class has no responsibility except holding one or maybe two pieces of information, or that it only collaborates with one other object you may decide it only needs be an attribute not a full class. Also, especially in Python, many collections of classes will turn out to be standard container types such as lists, dicts, tuples etc. Or standard classes such as dates/times. Making these early win decisions is one of the biggest reasons for doing CRC analysis IMHO. -- Alan G Author of the Learn to Program web site http://www.alan-g.me.uk/ http://www.amazon.com/author/alan_gauld Follow my photo-blog on Flickr at: http://www.flickr.com/photos/alangauldphotos ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: https://mail.python.org/mailman/listinfo/tutor
Re: [Tutor] Long post: Request comments on starting code and test code on chess rating project.
boB Stepp wrote: > I mentioned in one of the recent threads that I started that it is > probably time for me to attempt a substantial project using the OO > paradigm. I have had no coursework or training in OOP other than my > recent self-studies. Everything (Other than toy practice examples.) I > have coded to date has been strictly procedural, similar to what I > learned in FORTRAN ages ago. Also, I am continuing to try to do TDD > and keep things DRY. So I am hoping for a thorough critique of what I > will shortly present. Everything is fair game! Hopefully my > self-esteem is up to this critique! > > The intent of this project is more than just calculate chess ratings. > I also envision being able to store a record of all game results and > player information for my school chess players that I give lessons to. > Some functionality I will strive to include: 1) Be able to re-rate > all games from a given date to the present. 2) Record demographic > information about my players, such as names, current school year, > current grade, etc. 3) Have a very friendly GUI so that when I have a > finished project, a student could easily take over responsibility of > maintaining the school's player ratings, if I so choose to allow this. > 4) Having an editor where games and player data (But NOT ratings; > these would have to be re-rated starting from a corrected game(s).) > can be edited, inserted, or deleted with games (If game data is > edited.) automatically be re-rated. 5) Et cetera ... > > My current project structure is: > > /Projects > /rating_calculator > /rating_calculator > __init__.py > main.py > /tests > __init__.py > test_main.py > > I have started my coding with a RatingCalculator class. The intent of > this class is to gather all methods together needed to validate and > calculate chess ratings. My intent is to only ever have a single > instance of this class existing during a given running of the program. Why? > (BTW, is there a technique to _guarantee_ that this is always true?) Why? Would it harm one instance if there were another one? If so you have global state which usually runs counter to the idea of a class. > I am only at the very beginning of coding this class. I have not > added any methods yet. Currently I am adding class constants that > will be used in this class' methods. These constants are for internal > class use only and should not be altered from outside the class. Still, a good unit test might be to initialise a RatingCalcultator with different "highest possible ratings" and then verify for each instance that actual ratings never exceed the specified value. > Here > is the current state of the main.py program (Which will probably be > broken into multiple modules as the project develops.): > > = > #!/usr/bin/env python3 > > """Module to process chess ratings.""" > > class RatingCalculator: > """This class contains methods to validate and calculate chess > ratings.""" > > # Overestimated extremes for possible chess rating values. Values > # outside this range should not be achievable. If there are no hard limits, why give a limit at all? > > _LOWEST_POSSIBLE_RATING = 0 Again, this is just a number. Is it really worthwile explicit checking in a unit test? The most relevant tests address behaviour. > _HIGHEST_POSSIBLE_RATING = 3000 > > # Fundamental constants for use in the chess rating formula. The keys > # to these values are tuples giving the rating ranges for which these > # K-factors are valid. > > _K_MULTIPLIERS = { > (_LOWEST_POSSIBLE_RATING, 2099): 0.04, The Python way uses half-open intervals. This has the advantage that there is a well-defined k-multiplier for a rating of 2099.5. Note that if you apply that modification you no longer need any upper limits. Use bisect (for only threee values linear search is OK, too) to find the interval from a list like [0, 2100, 2400], then look up the multiplier in a second list [0.04, 0.03, 0.02]) > (2100, 2399): 0.03, > (2400, _HIGHEST_POSSIBLE_RATING): 0.02} > > _K_ADDERS = { > (_LOWEST_POSSIBLE_RATING, 2099): 16, > (2100, 2399): 12, > (2400, _HIGHEST_POSSIBLE_RATING): 8} > = > > The test code in test_main.py is: > > = > #!/usr/bin/env python3 > > """Module to test all functions and methods of main.py.""" > > import unittest > import rating_calculator.main as main > > class TestRatingCalculatorConstants(unittest.TestCase): > # Check that the constants in RatingCalculator have the proper values. > > def setUp(self): > # Create instance of Ratin
Re: [Tutor] Long post: Request comments on starting code and test code on chess rating project.
Based upon the feedback thus far (Many thanks!) I think that perhaps I should expand on my design thoughts as I have already stubbed my big toe at the very start! On Sun, Aug 13, 2017 at 12:22 AM, boB Stepp wrote: > The intent of this project is more than just calculate chess ratings. CALCULATION OF RATINGS My calculation of a new chess rating is a simpler process than what is used for FIDE Elo ratings or USCF Elo ratings for over-the-board play. Instead, I am adapting the USCF (United States Chess Federation) correspondence chess rating formula for use with my students. These students usually come to me often not even knowing the moves and rules of the game. They have no previous tournament chess experience and in general are quite weak players even if they sometimes believe otherwise. ~(:>)) A link to the USCF's explanation of this rating system is: http://www.uschess.org/content/view/7520/393/ It is not very well-written in my opinion. But anyway ... The basic formula is: new_rating = old_rating + K_MULTIPLIER * (opponent_rating - old_rating) + K_ADDER * (your_result - opponent_result) If you win your_result = 1; lose = 0; draw = 0.5. Note that the opponent's result would be, respectively, 0, 1 and 0.5, corresponding to these values of your_result. I am not using the article's section on unrated players. This is of little use to me as I usually start off without anyone who has ever had a chess rating. So there is no existing rating pool to meaningfully compare my unrated players against. So I start out each unrated player with a rating of 1000. For most of my students who already know and play chess, this turns out to be a slightly generous estimate of their rating if I were to compare them to the USCF over-the-board rating population. Another change from the article I am making is that if there is more than a 350 rating point difference between players, then the game is rated as if it is a 350 point difference. I have changed this to 400 points as I feel that the higher rated player should _not_ gain any rating points for beating the lower rated player. In practice I am the only person 400+ points greater than anyone. To date I have a 100% win rate against my students, so I would rather they not obsess about losing rating points against their peers if I have to fill in for a game due to an odd number of players showing up. There are several conditionals that have to be applied to calculate the rating even in this simplified system. K_MULTIPLIER and K_ADDER are dependent on whether the player's old_rating falls into one of these rating ranges: 1) 0 - 2099; 2) 2100 - 2399; 3) >= 2400. Additionally, if the initial new_rating calculation causes new_rating to fall into a different K-factor rating range, then the new rating must be proportionately adjusted for the number of points that fall into the new K-factor rating range, using a ratio of the new and old K-factors. So based on what Steve said, the rating calculation makes more sense to be done with functions. One of the things I wish to do with the completed program, is to enter all of my old game data spanning ~ 5 years, and have the program re-rate everything from scratch. So I do not see how it would be helpful to create a new RatingCalculator instance for each game. PERSISTENT STORAGE OF GAME DATA AND PLAYER DATA I've decided not to use an actual database as I will never have so much data that it cannot be fully loaded into RAM for even quite modest PCs. The approach I am thinking of taking is using a dictionary of dictionaries to store the game data and player data. Something like: players = {0: {'last name': 'Smith', 'first name': 'John', ... }, 1: {'last name': 'Doe', 'first name': 'Doe', ... }, ... , n: {...}} and similarly for games. I would use the csv library's dictread and dictwrite methods to load into memory and on program exit write back to disk these two dictionaries, which are meant to mimic two database tables. REPORT GENERATION There are a variety of reports that I would like to be able to print to screen or paper. Things such as a "Top x List" of rated players, full rating list sorted from highest rating to lowest, rating lists for the current school year only or a particular past school year, and so. I could see wanting to sort such lists by various criteria depending on the type of report I would like to generate. Currently this section of the program is a big gray area for me, but I see no reason why my two dictionaries cannot be manipulated to pull the desired results for each imagined type of report. USER INTERFACE Until all of the above is worked out, I will use a CLI, perhaps even play around with the curses library, which I have been meaning to do. Once I get an overall result that I am happy with, I will develop a GUI in tkinter. My intent is to go as Alan suggests and keep the UI well-decoupled from the rest of the program, no matter if it is CLI or
[Tutor] "Path tree"
Hi all: I am trying to formulate a "path-finding" function, and I am stuck on this problem: Please look at the picture attached: Those dots are coordinates of (x,y), and this tree can be thought of as a list of tuples, with each tuple consisting of (x,y). Now I am trying to make a function go through this list of tuples and then return the "path." to go from, say, 4 to 8. If I simply compute for the dot for shortest distance, then the solution would be to go from 4 to 8 direct, but that doesn't work, because the correct solution should have been 4,3,2,5,6,8. How do I do this? Thanks! ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: https://mail.python.org/mailman/listinfo/tutor
Re: [Tutor] Long post: Request comments on starting code and test code on chess rating project.
On 13/08/17 21:15, boB Stepp wrote: I return to the point I made about focusing on the objects not the functionality. > It is not very well-written in my opinion. But anyway ... The basic formula > is: > > new_rating = old_rating + K_MULTIPLIER * (opponent_rating - > old_rating) + K_ADDER * (your_result - opponent_result) What is being rated? A player? A game? a set of games? The rating calculation should probably be a method of the object being rated rather than a standalone function. If you make it a function you will probably have to pass in lots of data objects (or worse use a lot of globals). In OOP you want the data to be in the object and the method to use that data to achieve its aim. > I generally start as above by dividing things up into broad areas of > functionality Which is exactly how you write procedural code - the technique is called functional decomposition. But if your objective is to use OOP you should start by identifying the objects. Then you can assign the functionality(responsibilities) to which ever object (or set of objects) is appropriate. > to be a rather straightforward translation of a formula with various > conditions into code, returning the newly calculated ratings Note that I'm not saying your approach is wrong in a general case, it may well be the most appropriate approach for this application. But it will not easily lead to an OOP style solution, and that I thought was the secondary(or even primary?) objective of this exercise? > So how detailed should I plan out each broad section of the program > before writing any code? Alan seems to be suggesting getting a firm > handle on initially imagined objects and their methods before any code > writing. You need some kind of idea of the classes you are going to write. The CRC description only needs to be a few lines scribbled on a sheet of paper or in a text editor. In OOP you are building classes that represent instances. Your solution is an interaction between the instances (objects). Without a fairly clear idea of how the objects interact you can't make a sensible start. You don't need to analyse the whole system (and indeed should not do so) but start with a couple of basic use cases - what initiates the action? What are the preconditions(eg. data etc), the outcomes, the possible error conditions. When you understand a use case enough to code it, do so. Or even just the initial class/object. It's a good idea to get an early code structure in place, maybe a Player class? It seems that this is the thing being rated. Maybe a game class since it seems that game (results and players) are used in the rating algorithm. So your Player probably needs a list of past games? -- Alan G Author of the Learn to Program web site http://www.alan-g.me.uk/ http://www.amazon.com/author/alan_gauld Follow my photo-blog on Flickr at: http://www.flickr.com/photos/alangauldphotos ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: https://mail.python.org/mailman/listinfo/tutor
Re: [Tutor] Long post: Request comments on starting code and test code on chess rating project.
On Sun, Aug 13, 2017 at 3:52 AM, Peter Otten <__pete...@web.de> wrote: > boB Stepp wrote: >> I am only at the very beginning of coding this class. I have not >> added any methods yet. Currently I am adding class constants that >> will be used in this class' methods. These constants are for internal >> class use only and should not be altered from outside the class. > > Still, a good unit test might be to initialise a RatingCalcultator with > different "highest possible ratings" and then verify for each instance that > actual ratings never exceed the specified value. I just had not gotten around to doing this yet. > = >> #!/usr/bin/env python3 >> >> """Module to process chess ratings.""" >> >> class RatingCalculator: >> """This class contains methods to validate and calculate chess >> ratings.""" >> >> # Overestimated extremes for possible chess rating values. Values >> # outside this range should not be achievable. > > If there are no hard limits, why give a limit at all? My thinking was to put in a sanity check. The ratings should fall into sensible limits. >> >> _LOWEST_POSSIBLE_RATING = 0 > > Again, this is just a number. Is it really worthwile explicit checking in a > unit test? The most relevant tests address behaviour. Shouldn't I check for inadvertent changing of what are constants by later modifications of the code? >> _HIGHEST_POSSIBLE_RATING = 3000 >> >> # Fundamental constants for use in the chess rating formula. The keys >> # to these values are tuples giving the rating ranges for which these >> # K-factors are valid. >> >> _K_MULTIPLIERS = { >> (_LOWEST_POSSIBLE_RATING, 2099): 0.04, > > The Python way uses half-open intervals. This has the advantage that there > is a well-defined k-multiplier for a rating of 2099.5. Note that if you > apply that modification you no longer need any upper limits. Use bisect (for > only threee values linear search is OK, too) to find the interval from a > list like [0, 2100, 2400], then look up the multiplier in a second list > [0.04, 0.03, 0.02]) Note: Valid ratings can only be positive integers. No floats allowed in the final rating result or in the initial input of an established rating that is to be changed. So you are suggesting doing something like: --- py3: from bisect import bisect py3: def get_k_factors(rating): ... RATING_BREAKPOINTS = [0, 2100, 2400] ... K_ADDERS = [16, 12, 8] ... K_MULTIPLIERS = [0.04, 0.03, 0.02] ... index = bisect(RATING_BREAKPOINTS, rating) - 1 ... k_adder = K_ADDERS[index] ... k_multiplier = K_MULTIPLIERS[index] ... return k_adder, k_multiplier ... py3: get_k_factors(1900) (16, 0.04) py3: get_k_factors(2100) (12, 0.03) py3: get_k_factors(2399) (12, 0.03) py3: get_k_factors(2400) (8, 0.02) --- I like it! It will save me from having to use a bunch of conditionals. >> (2100, 2399): 0.03, >> (2400, _HIGHEST_POSSIBLE_RATING): 0.02} >> >> _K_ADDERS = { >> (_LOWEST_POSSIBLE_RATING, 2099): 16, >> (2100, 2399): 12, >> (2400, _HIGHEST_POSSIBLE_RATING): 8} >> > = >> >> The test code in test_main.py is: >> >> > = >> #!/usr/bin/env python3 >> >> """Module to test all functions and methods of main.py.""" >> >> import unittest >> import rating_calculator.main as main >> >> class TestRatingCalculatorConstants(unittest.TestCase): >> # Check that the constants in RatingCalculator have the proper values. >> >> def setUp(self): >> # Create instance of RatingCalculator for use in the following >> # tests. Create tuple of test-value pairs to interate over in >> # subtests. >> >> self.rating_calculator = main.RatingCalculator() >> self.test_value_pairs = ( >> (self.rating_calculator._LOWEST_POSSIBLE_RATING, 0), > > With the _ you are making these constants implementation details. Isn't one > goal of unit tests to *allow* for changes in the implementation while > keeping the public interface? I don't think I am understanding your thoughts. Would you please provide an illustrative example? I am concerned that later program development or maintenance might inadvertently alter values that are expected to be constants. >> (self.rating_calculator._HIGHEST_POSSIBLE_RATING, 3000), >> (self.rating_calculator._K_MULTIPLIERS[( >> self.rating_calculator._LOWEST_POSSIBLE_RATING, >> 2099)], 0.04), >> (self.rating_calculator._K_MULTIPLIERS[(2100, 2399)], >>
Re: [Tutor] Long post: Request comments on starting code and test code on chess rating project.
On Sun, Aug 13, 2017 at 5:49 PM, Alan Gauld via Tutor wrote: > On 13/08/17 21:15, boB Stepp wrote: > > I return to the point I made about focusing on the > objects not the functionality. > >> It is not very well-written in my opinion. But anyway ... The basic formula >> is: >> >> new_rating = old_rating + K_MULTIPLIER * (opponent_rating - >> old_rating) + K_ADDER * (your_result - opponent_result) > > What is being rated? A player? A game? a set of games? > The rating calculation should probably be a method of > the object being rated rather than a standalone function. The result of a game between two players results in the need to calculate new ratings for both players. Ratings are attributes of players. >> I generally start as above by dividing things up into broad areas of >> functionality > > Which is exactly how you write procedural code - the technique > is called functional decomposition. Got me there! Still stuck in the old procedural paradigm. > But if your objective is to use OOP you should start by identifying > the objects. Then you can assign the functionality(responsibilities) > to which ever object (or set of objects) is appropriate. > > >> to be a rather straightforward translation of a formula with various >> conditions into code, returning the newly calculated ratings > > Note that I'm not saying your approach is wrong in a general case, > it may well be the most appropriate approach for this application. > But it will not easily lead to an OOP style solution, and that I > thought was the secondary(or even primary?) objective of this > exercise? You understand correctly! The primary purpose of this project is breakout of procedural programming and break into OOP. >> So how detailed should I plan out each broad section of the program >> before writing any code? Alan seems to be suggesting getting a firm >> handle on initially imagined objects and their methods before any code >> writing. > > You need some kind of idea of the classes you are going to write. > The CRC description only needs to be a few lines scribbled on a > sheet of paper or in a text editor. In OOP you are building classes that > represent instances. Your solution is an interaction between the > instances (objects). Without a fairly clear idea of how the objects > interact you can't make a sensible start. You don't need to analyse > the whole system (and indeed should not do so) but start with a > couple of basic use cases - what initiates the action? What are > the preconditions(eg. data etc), the outcomes, the possible error > conditions. When you understand a use case enough to code it, do so. > Or even just the initial class/object. I just finished reading a couple of online articles about CRC cards. I have some old index cards lying around. I will have at it. > It's a good idea to get an early code structure in place, maybe > a Player class? It seems that this is the thing being rated. > Maybe a game class since it seems that game (results and players) > are used in the rating algorithm. So your Player probably needs > a list of past games? The two most obvious classes that jumped to my mind from the get-go are Player and Game. Thanks, Alan! -- boB ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: https://mail.python.org/mailman/listinfo/tutor