My long history on this edusig listserv @ python.org has included an agenda around STEM reform. I've wanted to change US K16 by displacing math track scientific calculators with true REPLs, then altering the math content based on the new freedom a REPL provides. Using the IPython REPL: In [129]: from px_class import * In [130]: p = P(range(10)).shuffle() In [131]: p Out[131]: P class: ((0, 6), (1, 4), (2, 0))... In [132]: p._code Out[132]: {0: 6, 1: 4, 2: 0, 3: 3, 4: 8, 5: 9, 6: 1, 7: 5, 8: 2, 9: 7} In [133]: p.cyclic() Out[133]: ((0, 6, 1, 4, 8, 2), (3,), (5, 9, 7)) My __repr__ for the P class actually uses ... i.e. this is unedited output. The idea of a Permutation (P) is very primitive: a mapping of elements to themselves i.e. a dict where keys and values are the same elements, but with values in any order. The identity permutation maps every element to itself. p= P() starts out with the identity permutation, but then any p instance has a .shuffle method to return a new instance with a permuted version of the original dict. p = P().shuffle() creates a random instance in one step. By default, my Permutations use the lowercase alphabet plus space, to form a 27element self._code (the dict). The encrypt and decrypt methods may be fed ordinary strings of lowercase letters. From: p = P().shuffle() s = "able was i ere i saw elba" c = p(s) print("Plain: ", s) print("Cipher: ", c) try: assert p.decrypt(c) == s print("Third Test Succeeds") except AssertionError: print("Third Test Fails") comes the output: Plain: able was i ere i saw elba Cipher: fp vnufcnlnvjvnlncfunv pf Third Test Succeeds I think it's an economic reality the curriculum publishers do not want to compete with their own products. Computer science in high school has, historically speaking, been a backofthebus secondclass citizen, earning only "elective credit" for its course takers, if there's more than a "computer club" (i.e. if the content is not extracurricular), plus a chance to pass the AP test. That made sense until the languages got so friendly and easy to use. Now that we're redoing the mathematics track itself, in light of these changes in technology, we have an opportunity to bring in new topics such as Group Theory and Euclid's Algorithm (EA) for the greatest common divisor (GCD). With GCD in the picture, we're ready for relative primality and therefore concepts of totative / totient. I've harped on these topics before. They're simple and accessible, precollege, not much background required. Unless one grasps the concept of Group, there's no getting Field, with Group, Ring and Field being fundamental algebraic structures. There's no arguing these concepts are irrelevant, even in K12. Permutations may be multiplied (the operation), plus each has an inverse, defined as the key>value mapping that goes the other way i.e. the dict is inverted. How does one invert a dictionary in Python code? Good exercise, so again we're not just earning math credit, we're improving our Python skills. The properties of a Group are sometimes given as 'CAIN': *(C)* Closure: p = P().shuffle(); q = P().shuffle(); p * q is always another P. *(A)* Associativity: (p * q) * r == p * (q * r) for all Permutations p, q, r *(I)* Inverse: every p = P().shuffle() has an inverse such that p * ~p == p ** 0 *(N)* Neutral element: every group has an Identity element with respect to its operation (*) such that p * P() == P() * p == p. I'm currently teaching a course in Python for the State of California. My adult students join me in real time for 10 four hour segments. I've just completed my fourth. We're coding classes (types) already, were looking at simple class constructs from Day One. I've suggested they adopt the mindset of a future high school student, privileged to have a real REPL. Last night, for a lab, I had the class tackle adding __pow__ to the P class, given I'd already implemented __mul__. Playing with a Permutation class is just a tiny fraction of all we do. I spiral back to it in each session as I introduce new Python features. Today, I added more methods to the Permutation so I can simply lecture on them tomorrow evening and invite downloading and reuse. Here's the source code as of now, pretty well tested: # * coding: utf8 * """ Created on Tue Nov 10 17:39:09 2015 @author: Kirby Urner (c) MIT License (fine to reuse / alter / share) Fun for Group Theory + Python """ from random import shuffle from string import ascii_lowercase # all lowercase letters class P: """ A Permutation self._code: a dict, is a mapping of iterable elements to themselves in any order. """ def __init__(self, iterable = ascii_lowercase + ' '): """ start out with Identity """ self._code = dict(zip(iterable, iterable)) def shuffle(self): """ return a random permutation of this permutation """ # use shuffle # something like the_keys = list(self._code.keys()) # grab keys shuffle(the_keys) # shuffles newP = P() newP._code = dict(zip(self._code.keys(), the_keys)) return newP def encrypt(self, plain): """ turn plaintext into cyphertext using self._code """ output = "" # empty string for c in plain: output = output + self._code.get(c, c) return output def decrypt(self, cypher): """ Turn cyphertext into plaintext using ~self """ reverse_P = ~self # invert me! output = "" for c in cypher: output = output + reverse_P._code.get(c, c) return output def __getitem__(self, key): return self._code.get(key, None) def __repr__(self): return "P class: " + str(tuple(self._code.items())[:3]) + "..." def cyclic(self): """ cyclic notation, a compact view of a group """ output = [] the_dict = self._code.copy() while the_dict: start = tuple(the_dict.keys())[0] the_cycle = [start] the_next = the_dict.pop(start) while the_next != start: the_cycle.append(the_next) the_next = the_dict.pop(the_next) output.append(tuple(the_cycle)) return tuple(output) def __mul__(self, other): """ look up my keys to get values that serve as keys to get others "target" values """ new_code = {} for c in self._code: # going through my keys target = other._code[ self._code[c] ] new_code[c] = target new_P = P(' ') new_P._code = new_code return new_P def __pow__(self, exp): """ multiply self * self the right number of times """ if exp == 0: output = P() else: output = self for x in range(1, abs(exp)): output *= self if exp < 0: output = ~output return output def __call__(self, s): """ another way to encrypt """ return self.encrypt(s) def __invert__(self): """ create new P with reversed dict """ newP = P(' ') newP._code = dict(zip(self._code.values(), self._code.keys())) return newP def __eq__(self, other): """ are these permutation the same? Yes if self._code == other._code """ return self._code == other._code if __name__ == "__main__": p = P() # identity permutation new_p = p.shuffle() inv_p = ~new_p try: assert p == inv_p * new_p # should be True print("First Test Succeeds") except AssertionError: print("First Test Fails") #========== p = P().shuffle() try: assert p ** 1 == ~p assert p ** 2 == ~(p * p) assert p ** 2 == (~p * ~p) print("Second Test Succeeds") except AssertionError: print("Second Test Fails") #========== p = P().shuffle() s = "able was i ere i saw elba" c = p(s) print("Plain: ", s) print("Cipher: ", c) try: assert p.decrypt(c) == s print("Third Test Succeeds") except AssertionError: print("Third Test Fails")
On Wed, Nov 18, 2015 at 2:59 PM, kirby urner <kirby.urner@gmail.com> wrote: << SNIP >>
I'm currently teaching a course in Python for the State of California. My adult students join me in real time for 10 four hour segments. I've just completed my fourth. We're coding classes (types) already, were looking at simple class constructs from Day One. I've suggested they adopt the mindset of a future high school student, privileged to have a real REPL.
My pedagogy / andragogy is much influenced by my since2011 use of Steve Holden's four part Python series. He introduces unittesting by the very start of the second course i.e. right after the basic basics (up through writing a first class, along with significant string manipulation). I think that's productive, as it gives flavor and context to not just the importance of unittests, but the philosophy known as Agile and its various tenants such as: (1) no ownership of code (2) release early / often (3) TDD or testdriven development as a way of making these earlyoften microsteps. Write the tests first, as a way of giving your day traction. You end up with a pile of audits. That can be worth a lot when it comes to guarding against corrupting code. Given I get to yak for literally hours, you can bet I was persuasive. :D Although my focus is core Python out to some standard library, some talk of 3rd party is encouraged (we're using Anaconda after all). Plus I'm eager to reach out to that anchoring context (of developing according to some established best practices). Using a language, like using a musical instrument, needs to be embedded in its larger context. I know Steve, a veteran coder, is not unique in stressing TDD. Just letting students know of these ramified schools of thought... whole workflows, practices, such as SCRUM. I've put my own spin on some of this e.g. around (1) "no ownership of code" I talk a lot about learning to play chess against yourself. It's when you have that ability to "turn on yourself" now suddenly white's worst enemy in a good way (ideal sparring partner) that you demonstrate your ability to "let go and play for the other side" (the testers are like the skeptics, with the developers the earnest dreamers striving to best more tests (the athletes, the stars  as a solo coder, your mindset goes back and forth, whereas in a bigger company, these may be different teams (that's what I tell my students)). Anyway, my previous post included what I introduce as "raw" or "naive" tests appended to the module itself. We learn that it's better to split off the testing code, but you'll notice the unit tests are pointedly probing the same features. Two ways of saying the same thing. Kirby # * coding: utf8 * """ Created on Thu Nov 19 10:48:49 2015 @author: kurner """ import unittest from px_class import P class Test_Permutation(unittest.TestCase): def test_1(self): """ any p * ~p should give Identity """ p = P() # identity permutation new_p = p.shuffle() inv_p = ~new_p self.assertEqual(p, inv_p * new_p, "expected identity fails") def test_2(self): """ encrypt and decrypt are inverse operations on string """ p = P().shuffle() # arbitrary permutation s = "the rain in spain stays mainly in the plain" c = p.encrypt(s) self.assertEqual(p.decrypt(c), s, "decrypted phrase differs") if __name__ == "__main__": unittest.main()
import unittest from px_class import P
class Test_Permutation(unittest.TestCase):
def test_1(self): """ any p * ~p should give Identity """ p = P() # identity permutation new_p = p.shuffle() inv_p = ~new_p self.assertEqual(p, inv_p * new_p, "expected identity fails")
Of course where there's multiplication (__mul__) we might reasonably expect not only __pow__, but also __truediv__. These "__ribs__" come as a package with the API we call "Algebra" and based on patterns, we'd expect: def __truediv__(self, other): return self * ~other where unary ~ makes sense especially in a oneoperation Group universe, where each instance is guaranteed to have its unique pair. __invert__ is a part of our P class already: def __invert__(self): """ create new P with reversed dict """ newP = P(' ') newP._code = dict(zip(self._code.values(), self._code.keys())) return newP A pair of inverses (p and ~p) when "__mul__tiplied" together, return the Neutral or Identity P, returned by P(). Put another way, p / p == p * ~p == P(), where p = any P().shuffle(). __mul__ and __truediv__ are examples of the same idea: binary operators with a set of instance type things to work with as a group. The permutation class provides an idealized algebraic "good citizen" in a rather constrained finite world. Adding another test then: def test_3(self): """ lets have a division operation! a / b == a * ~b or equivalently a / b == a * b**1 """ p = P().shuffle() # arbitrary permutation q = P().shuffle() # arbitrary permutation r = p / q # if r = p / q then r * q = p self.assertEqual(p, r * q, "expected identity fails") r = q / p # if r = q / p then r * p = q self.assertEqual(q, r * p, "expected identity fails") Note that from the LCM of the subcycles (their orders i.e. sizes) you get the number of powerings for this permutation to do a full orbit, back to its own beginning. Each one has an orbit through this factorialsized space of possibilities. By subcycles I refer to the cyclic output, a tuple of tuples, which my P supports (in the J language it's one of the primitives): In[118]: p = P().shuffle() # scare up a random scramble of the alphabet In[119]: p._code Out[119]: {' ': 'u', 'a': 'k', 'b': 'd', 'c': 'j', 'd': 'c', 'e': 'f', 'f': 'z', 'g': 'r', 'h': 'p', 'i': 'b', 'j': 'x', 'k': ' ', 'l': 'g', 'm': 'l', 'n': 'h', 'o': 'q', 'p': 'y', 'q': 'o', 'r': 's', 's': 'm', 't': 'v', 'u': 'e', 'v': 'w', 'w': 't', 'x': 'n', 'y': 'a', 'z': 'i'} p.cyclic() Out[120]: (('x', 'n', 'h', 'p', 'y', 'a', 'k', ' ', 'u', 'e', 'f', 'z', 'i', 'b', 'd', 'c', 'j'), ('t', 'v', 'w'), ('s', 'm', 'l', 'g', 'r'), ('o', 'q')) You see how to read these. The dict pairs 'p' with 'y' and then 'y' with 'a', 'a' with 'k' whereas 'y', 'a', 'k' is all part of the first / longest tuple, one of the cycles or subcycles (a subgroup). The last element in a tuple connects back to the first, as what it maps to, and then we're on to the next cycle, covering the same ground the dict version covers, two ways of saying the same thing. The LCM (lowest common multiple) of several numbers is computed from their GCD (greatest common divisor) which we have seen many times  both often developed along with class Q, the Rational Number class (p, q both int, as if fractions.Fraction did not exist yet). We also need GCD for determining relatively prime numbers, ergo the totatives of N. These form more examples of Groups, when multiplied modulo N. Fermat's Little Theorem, then Euler's generalization thereof, branch off from here (or converge to here, depending on if we're arriving at or leaving the train station (hub)). Like some others, I've made conquering RSA (the encryption algorithm) a local peak in my curriculum as well (have for a long time). Neal Stephenson was influential. Here's a web page of mine from around Y2K: http://www.4dsolutions.net/ocn/clubhouse.html Likewise we've been through this material before on edusig. The search, the quest, is for sweet spots, "holy wells" (grails) that contain a digestible portion of: (A) reasonably meaty Python (B) reasonably interesting mathematics We seek these synergies in order to maximize practical mastery of at least one computer language (swap in your current favorite) but also to suggest continuity should more curricula pick this up. A Permutation in Clojure might be the next step, with the same concept reintroduced. Then on to Java? I'm sure this code is out there, many times, but we lack much agreement at the curriculum level that this should be a trunk road. I'm using this framework currently with students mainly just to introduce unit testing, the whole idea, in addition to operator overloading. Tomorrow I plan on having the Permutation retch (as in barf) if a 2nd argument is fed in that's not an iterable. How might that look? If we raise a TypeError in that case, how might we reflect this expected outcome in the form of a unit test? It's not just operators we overload in Python, but objectcalling syntax (__call__) along with getting / setting syntax (__getitem__), as well as attributes (__getattr__, __setattr__). The Permutation provides a natural stepladder up through at least some of those freedoms / capabilities. We keep spiraling back for more. I'm also taking advantage of encrypt and decrypt functions to talk about file I/O. We read in declaration.txt (a mix of upper and lowercase, as well as punctuation  so permute accordingly) and store it out as "encrypted"  along with a JSON string, the "key". With access to such a key, comes easy transformation back to the starting text, a good metaphor for encoding when not encrypting exactly (there's a fine line). I'm always clear these are super easy to break "codes" and it's more a matter of breaking into topical content, wrapping one's mind around some linchpin concepts in both programming and mathematics, that we get our payoff. The group stuff is a great jumping off point towards vectors, vector spaces, and polyhedrons spinning. That's another rich area for meaty code around generic concepts, lots of payoff. In the namespace of my Digital Mathematics, I'm working on a tunnel from Casino to Martian Math. I know its abstruse but I break it down into Neolithic > Martian Math for a timeline and Casino <> Supermarket for two "wings" having to do with mathematics in the real world. http://wikieducator.org/Digital_Math Permutations connect us to combinatorics on the one hand, and to rotating polyhedrons on the other. That's the kind of Lexical <> Graphical bridge I look for, regardless of my level of belief in a left and right brain and all the additional polarities that might line up under left and right respectively. Getting visualizations and symbolic reasoning to work together is nevertheless a goal. Youtubes in the same territory: https://youtu.be/VSB8jisn9xI https://youtu.be/q1riFCCsUU4
participants (1)

kirby urner