Re: [Tutor] Long post: Request comments on starting code and test code on chess rating project.
boB Stepp wrote: > On Sun, Aug 13, 2017 at 3:52 AM, Peter Otten <__pete...@web.de> wrote: >> boB Stepp wrote: >> 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. >> 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? In what scenario would such an inadvertent change occur? I prefer the indirect check sketched out above. Assuming there is a check_rating() method you can make tests like rc = RatingCalculator(lowest_rating=0, highest_rating=100) for r in [0, 50, 100]: rc.check_rating(r) for r in [-1, 101]: with self.assertRaises(FailedSanityCheck): rc.check_rating(101) that ensure that a method works as expected rather than verifying a certain constant value. (You have to do the above for at least two ranges to make sure that the arguments are used for the check.) > >>> _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. Then you need a test for that ;) > So you are suggesting doing something like: > py3: from bisect import bisect > py3: def get_k_factors(rating): Yes, but I would pass the lists as arguments to allow for easier testing. > ... 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 > ... >> 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? When you are sending a parcel to a friend you don't care about the path it takes or that the delivery service obeys the speed limit. You only demand that it arrives at the specified place within the specified time interval. That way the service is free to choose the best (quickest, shortest, or cheapest) route and the best (fastest, cheapest) means of transportation. A class is a service provider, too. If you test private methods and states you are either stuck with one implementation or have to change unit tests and implementation in lockstep -- and these unit test modifications may introduce bugs, too. If instead you manage to cover the code with tests that only use the public interface you can replace one implementation of the tested class with a completely different one -- and when your unit tests continue to succeed you are pretty sure that the replacement is correct, too. Thus the hurdle to trying out improvements ("refactoring") is lower and you are more likely to end up with clean and efficient code. > I am concerned that later program > development or maintenance might inadvertently alter values that are > expected to be constants. Unlike 0 the value of 3000 is in no way special. Your script will be more robust if it continues to work with an upper bound of 2900 or 4000 -- or even floats in the range 0 <= rating < 1. >> Values are the most volatile and least important part of a program. >> Start coding with the algorithms and tests covering those. > > I was just looking for the lowest hanging fruit to get an easy start > on this project, and use it as a check to see if I was going off in > inadvisable directions. I think that is mostly a distraction. Unfortunately there are many ways to burn time dealing with the irrelevant aspects of a program. I know some of them -- from hearsay, of course ;) ___
Re: [Tutor] "Path tree"
Martin A. Brown wrote: > The image: > >> http://imgur.com/a/CwA2G > > To me, this looks like a 'graph', which is a more general data > structure -- it does not look like a 'tree' (in the computer-science > meaning of the term, anyway). > import networkx as nx While Martin's solution is certainly more robust it may also be instructive to see it done "by hand": edges = [ (1, 2), (2, 5), (2, 3), (3, 4), (5, 7), (5, 6), (6, 8), # remove the comment to see what happens # when there is more than one path between two nodes #(1, 8), ] graph = {} # make a lookup table node --> neighbours for a, b in edges: graph.setdefault(a, []).append(b) graph.setdefault(b, []).append(a) print(graph) def connect(start, end, path, graph): path += (start,) if start == end: # we found a connection yield path neighbours = graph[start] # try all neighbours, but avoid cycles to nodes already in the path for node in neighbours: if node not in path: # recurse to find connection from neigbour to end yield from connect(node, end, path, graph) for path in connect(4, 8, (), graph): print(path) ___ 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 2017-08-13, boB Stepp wrote: > 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. You really can do it, but to me this requirement argues for going back to a database backend. Why rewrite SQL? -- Neil Cerutti ___ 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 15/08/17 15:09, Neil Cerutti wrote: >> 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. ... > You really can do it, but to me this requirement argues for going > back to a database backend. Why rewrite SQL? I agree with Neil, this is exactly what SQL is good for and would make this part of the project much easier and would have the added benefit of introducing you to one of the trickiest, but most common, bits of OOP - object persistence... You might even find yourself writing some class methods! ;-) And using SQLite, (even using in-memory mode if you don't want to keep a database file) is about as easy as SQL gets. If you do want to do it by hand consider using the itertools module. (eg. its filterfalse(), dropwhile()/takewhile() combinations() and groupby() functions.) -- 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] "Path tree"
On 14Aug2017 12:10, Michael C wrote: http://imgur.com/a/CwA2G Ok. So you have a graph like this: 1 -- 2 -- 3 -- 4 | 7 -- 5 -- 6 -- 8 Have a read of a graph theory textbook. Also, wikipedia has an article on finding the shortest path through a graph: https://en.wikipedia.org/wiki/Shortest_path_problem which references several algorithms. You could pick one (eg Dijkstra's algorithm) and try to implement it. For a graph this small you could try your own and do something rather brute force; in larger graphs efficiency becomes very important. You will need to express the graph as a data structure in your code, with a data structure that expresses which nodes connect to each other node. Typically this often includes weights for the edges and a direction, but your graph has no weights (cost of traversing a particular edge) and is undirected (you can traverse an edge in either direction). It is also "simply connected" - there are no loops. All these things make your task simpler. You can express a graph as a direcionary with keys being your node numbers (i.e. 1, 2, 3 etc) and the values being a list of the other nodes to which each connects. Eg: graph = { 1: [2], 2: [1, 3], 3: [2, 4], 4: [3], 5: [7, 6], 6: [5, 8], 7: [5], 8: [6] } The you need to write code that starts somewhere (4 in your example) and moves to other nodes until it reaches the target node (8 in your example). You can see which other nodes are reachable your current from the dictionary above. You need to keep some kind of record of which nodes you have visted (i.e. that is the path). See if that gets you started. For debugging, make your program print out what node it is at as it traverses the graph - that will be helpful to you in figuring out what is working and what is not. Cheers, Cameron Simpson (formerly c...@zip.com.au) ___ 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 Tue, Aug 15, 2017 at 12:29 PM, Alan Gauld via Tutor wrote: > On 15/08/17 15:09, Neil Cerutti wrote: > >>> 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. ... > >> You really can do it, but to me this requirement argues for going >> back to a database backend. Why rewrite SQL? I just thought this would be an interesting problem type to tackle. No intentions of reinventing SQL! > I agree with Neil, this is exactly what SQL is good for and > would make this part of the project much easier and would have > the added benefit of introducing you to one of the trickiest, > but most common, bits of OOP - object persistence... Would you expand on this in SQLite terms? > You might even find yourself writing some class methods! ;-) Nah. I might expand my array of constants, and test a few more ... ~(:>)) -- boB ___ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: https://mail.python.org/mailman/listinfo/tutor