<jupyter_start><jupyter_text>Goodbye!Thanks for having PWSAfrica in Ibadan! We're writing this while people are doing their group projects and it's fantastic to have people solve complex, real-world problems. Whatever you go on to do --- and however much Python you can use in it --- we're certain you'll go far, and we're proud of all our participants. Well done, and thank you!We don't want the teaching to end when we leave, and we want to help you understand how you can teach *yourselves*, so that when problems come up which can be solved with programming, you can write a solution --- even if it doesn't use the techniques we've taught you.In this notebook, you'll find:1. Some extra problems to solve2. Some libraries you might find helpful3. Some tips and techniques we use when *we* write code4. Our solutions to the problems in part 1 ---  Problems to solve *Remember! There are plenty of problems you can work on at [the pwsafrica projects page](http://pwsafrica.org/projects), where we hosted the four projects to be chosen from at the end of the workshop. You can work on these, too!*We've put together a few more puzzles for you to keep learning from when we're gone. Try out any that interest you!1. FizzBuzz2. Tic-Tac-Toe3. Collatz using *graph theory*4. Dictionary for PWSAfrica instructors5. RPN Calculator6. ROT13 Encryption*Advanced*:1. Markov Chain2. Conway's Game of Life3. Elementary Cellular Automata  Fizzbuzz**Fizzbuzz should be a relatively easy program for you to write --- it's similar to the problem you got on the first day of the workshop.****Fizzbuzz is also a _common interview question_! You can already solve professional-level programming tasks. Tom has been asked this multiple times in job interviews.**"Fizzbuzz" is a program where we write a function which takes a number `n`, and returns a list where --- for every number between `1` and `n` --- the list has:* "Fizz" if the number divides by 3* "Buzz" if the number divides by 5* `n` as a string otherwiseSo, if we run `fizzbuzz(20)`, we should be returned:```python['1', '2', 'Fizz', '4', 'Buzz', 'Fizz', '7', '8', 'Fizz', 'Buzz', '11', 'Fizz', '13', '14', 'FizzBuzz', '16', '17', 'Fizz', '19', 'Buzz']```<jupyter_code>def fizzbuzz(n):
    pass # You can write your solution here


# Run this cell after the solution cell above to see whether your solution is correct.
def test_fizzbuzz():
    return fizzbuzz(20) == ['1', '2', 'Fizz', '4', 'Buzz', 'Fizz', '7', '8', 'Fizz', 'Buzz', '11', 'Fizz', '13', '14', 'FizzBuzz', '16', '17', 'Fizz', '19', 'Buzz']
test_fizzbuzz()<jupyter_output><empty_output><jupyter_text>Tic-Tac-ToeStandard! This is just to implement the game of tic-tac-toe.Some hints to get things moving: * You can have the user input their move as a number from 1 to 9, where each number refers to an index in an array, or a key in a dictionary.* A loop with an `input()` at the beginning is all you really need to implement a game! Then you can get the user's move, work out how that changes the state of your game, and every iteration of the loop is effectively a "turn".* You'll need to check whether somebody's won yet. I'm not going to give this away --- that'd spoil the challenge! --- but think about how you'll work that out.<jupyter_code># Go ahead and implement a solution here!<jupyter_output><empty_output><jupyter_text>Collatz, using graph theoryToward the end of the first week, we wrote code which produced "Collatz sequences". Our solutions produced lists of collatz sequences.For problems like this, testing small numbers is much easier than testing large numbers. For example, the series for 3 is `7` numbers long, but for `3000`, it's 49! As we get into sequences that are thousands long --- or millions! --- our computers might get a bit slow.Graph theory can help us here. Instead of just producing a list, what if we recorded which numbers lead to others? The example we gave before on the board was the sequence for `3`:```python[3, 10, 5, 16, 8, 4, 2, 1]```If we generate the sequence for `6`, we *know* that, once we reach `3`, we'll end the sequence in exactly the same way:```python[6, 3, 10, 5, 16, 8, 4, 2, 1]```However, the sequence for `20` is similar, too.```python[20, 10, 5, 16, 8, 4, 2, 1]```We can use this to our advantage in a clever way. If we record the number one number leads to, we only ever have to compute the next number *once*! What's more: we just have to store what *single number* comes after another number, and then use that to find *it's* next number, again and again, until we reach 1 like before. (This technique is called "memoization", and is related to "dynamic programming", if you'd like to read more after the problem. It's phenomenally effective!)So. Instead of building a collatz *sequence*, we can put together a *dictionary* representing *all* collatz sequences. The dictionary has integers for both its keys and its values, and for every key in the dictionary, the next number in the sequences will be its value. We can do this because given any number in the collatz sequence, there is exactly one number it will lead to!What we are building is a directed graph. Each relationship in the dictionary is an edge in the graph, indicating the next step of the collatz sequence. A similar graph is drawn in [xkcd 710](https://xkcd.com/710/).*Your task:* Implement a Collatz function which --- like before --- will take a number and return a list representing its Collatz sequence, but which builds the sequence from a dictionary representation as soon as it can, and records every computation it makes in this dictionary for later.<jupyter_code># You can write your solution here!<jupyter_output><empty_output><jupyter_text>Dictionary for PWSAfrica instructorsWhile we spent time in Ibadan we were excited to learn some local phrases! Unfortunately our best way to remember them was to keep them in a note in Ben's phone. *Surely* we can do better.A task we very nearly set you while you were learning dictionaries was to write a translator between English and the various languages spoken in Nigeria. This should be implemented using a Python dictionary: english phrases are the keys, and the local translations are the values!Your task is the following:1. Write a program which reads a file containing English and phrases in your Nigerian language of choice. The translations are on the same line, and take the form `english:your chosen language`. (Note: that means you can split on a `:`...).         You should read the file into a Python dictionary, and then print the translations.2. Write a prompt, where instead of printing the dictionary, a user is asked for an English phrase as `input()`, and they are given the translation in the file.3. Write another prompt where a user can enter an English phrase --- *then* the translation in your chosen language! --- and writes it to the file you're *reading* from, so you can *add* to the dictionary. [This may be helpful when writing to a file.](https://stackoverflow.com/a/6160082)<jupyter_code># You can write your solution here!<jupyter_output><empty_output><jupyter_text>RPN CalculatorA *Reverse Polish Notation* (RPN) calculator was a popular kind of calculator in the Soviet Union. RPN calculators are also supposed to be easier for children to learn to use than regular calculators, and it turns out that writing them is quite a good exercise!A traditional calculator works using *infix notation*. This looks like this:```python6 * 4 + 6   = 30```Infix puts the operators *between* the digits they relate to. By contrast, RPN puts the operators *after* the digits they relate to:```python6 6 4 * +   = 30```This has some benefits. One is that it turns out to be quite easy for computers to understand! More relevant to us humans, it also turns out that you never have to specify your order of operations with RPN. For example: if I wanted to write:```python6 * (4 + 6)   = 60```...I have to include brackets. This isn't the case when working with RPN, because the intended order of operation is specified by the order your operators are in:```python6 6 4 + *   = 60``` How is RPN executed?!The computer takes the first operator and the last two digits, calculates the result, and leaves the result at the front of the digits. So:```python6 6 4 + *   ...becomes...6 10 *      ...which becomes...60``` Your taskWrite a calcuator, which takes a string as `input()` and evaluates it as if it was an RPN expression. Print the result.To keep things simple, we'll assume that the digits and the operators *never mix*. Actual RPN calculators don't really work this way, but it makes our code much easier. The solution found at the end of this notebook also makes this assumption.<jupyter_code># When you're ready, go ahead and write us a calculator here.<jupyter_output><empty_output><jupyter_text>ROT13 EncryptionROT13 encryption is a very simple encryption scheme, where for every character in a message, we replace it with the 13th next one in the alphabet. This means that when we see the letter `a` we replace it with `n`, and when we see the letter `L` we replace it with `Y`, and when we see the letter `o` we replace it with `b` (because we loop back around to the beginning).That is to say, we map:```a -> nb -> oc -> p...l -> ym -> zn -> ao -> b```And so on. Your task is to write a function which will encrypt a string using ROT13. You should also write a function which decrypts an encrypted string --- can you see how this would work? Advanced: use `ord()` and `chr()`Python has useful functions for this, called `ord()` and `chr()`. If you use them correctly, they'll make your code for this much more elegant!* `ord()` will take a *single character* and return the *number* associated with that character.* `chr()` will take a *number* and return the *single character* associated with that number.This exists because all computers really understand is numbers, and when we type characters into the computer, it has to store them in some numerical way. The system for doing this is called *Unicode*, and every character has a number associated with it defined by unicode!Sometimes the best explanation is to see something working, so here's an explanatory code snippet:<jupyter_code>print("number associated with 'a' is:\t", ord('a'))
print("number associated with 'y' is:\t", ord('y'))
print("capital letters are treated separately! look:\t ord('A') is ", ord('A'))
print()
print("we can also use chr() to go backwards. Look:\t chr(100) should be 'd': ", chr(100))
print("infact, one is the inverse of the other. check it out:\t chr(ord('a')) is ", chr(ord('a')))
print()
print("chr(ord('Z')) == 'Z' is ", chr(ord('Z')) == 'Z')
print()
print("We can also use this to manipulate numbers text. Look:\t chr(ord('a')+1) is ", chr(ord('a') + 1))
# You can implement your solution here, with or without using ord() and chr().<jupyter_output><empty_output><jupyter_text>*Advanced:* Markov ChainA Markov Chain is a graph, where every edge on the graph is weighted by a probability. We start on a node on the graph, and then select a future point on the graph by selecting an edge according to their probability weightings. [You can learn more about Markov Chains here](http://setosa.io/ev/markov-chains/), or alternatively [in more detail here](https://en.wikipedia.org/wiki/Markov_chain).Basically: for every state, we want to move to a future state according to some probability.Your task is to work out how to encode a Markov Chain in Python.<jupyter_code># You can write your solution here!<jupyter_output><empty_output><jupyter_text>*Advanced:* Conway's Game of LifeConway's Game of Life is a famous programming task built around the concept of *cellular automata*. More general cellular automata are explored in the next task.Conway's Game of Life is a simple simulation on a two dimensional grid of squares. From simple rules, fascinating and complex behaviours can be derived. [You can read detail about Conway's Game of Life here.](https://simple.wikipedia.org/wiki/Conway%27s_Game_of_Life)The basic idea behind the task is that, on the grid, squares are thier black ("alive") or white ("dead"). From a given state of the grid at time `t`, the state at time `t+1` is determined by applying the following rules for each square:1. Any alive cell that is touching less than two alive neighbours dies.2. Any alive cell touching four or more alive neighbours dies.3. Any alive cell touching two or three alive neighbours does nothing.4. Any dead cell touching exactly three alive neighbours becomes alive.Try writing this and animating it! You can animate it in pygame, matplotlib or just by printing characters on the screen.Feel free to look up solutions online; there are lots of ways to implement this.  *Advanced:* Elementary Cellular AutomataCellular automata can be very complex, but simple ones can be made out of grids like Conway's Game of Life uses. *Even simpler ones* can be made using a simple line, or a one-dimensional grid!For an automaton with cells which are either dead or alive, and have a one-dimensional grid, we can specify the rules for each cell in terms of its previous state and the state of its two neighbours. If `0` means a cell is dead, and `1` means a cell is alive, we can determine the future states by deciding whether each of these previous states results in a future dead or alive state:* `000` * `001` * `010` * `011` * `100` * `101` * `110` * `111` Here, the cell we're deciding the future state for is the middle number. The numbers to their left and right are their left and right neighbours. So, for an automata with state `00110` at time `t`, the third cell at time `t+1` is determined by the `011` rule. [You can read more about this here if you're stuck.](https://en.wikipedia.org/wiki/Cellular_automatonElementary_cellular_automata)It turns out that some combinations of dead-alive rules for these automata are actually *"Turing-Complete"*, meaning that any mathematical proof can be encoded using them. That's amazing!Your task is to implement a *general* automata, which is given a set of eight rules --- one for each possible state for each possible cell, as detailled above --- and will animate this automata, using pygame, matplotlib, or just by printing lines of characters.<jupyter_code># You can implement your solution here!<jupyter_output><empty_output><jupyter_text>Extra libraries & resourcesParts more useful for software engineering are at the top of this section, while parts more useful for mathematicians are kept toward the bottom. So, if you're mostly interested in building large projects, pay most attention to the bits at the top. If you'd like to use Python to help with coursework and projects for your studies, pay attention to the bits at the bottom. GitHubThere's lots of useful code on GitHub! GitHub is a place where people publish their code for others to use. (Actually, it's much more than this, but for our purposes GitHub is a little like a shop window you can display your projects from.)Having a GitHub page is essential as a professional programmer, and if you're curioous about taking your programming further than PWSAfrica playing with GitHub is a great way to start. It also backs up your projects, in case you have a hard drive failure or similar, and is designed to take as little data as possible to work! Try it out. Keon's Algorithms repositoryA useful GitHub repository is the [Keon repository of algorithms in Python](https://github.com/keon/algorithms). Keon is a GitHub user who has curated implementations of lots of algorithms and it's worth reading through the code in this repository. One nice thing about it being a GitHub repository is that lots of people can write and submit implemetations of things they've found useful, or have noticed are missing. Tom even contributed one of the implementations a couple of years ago![You can see Keon's repository of Python algorithms here.](https://github.com/keon/algorithms) Learning more ProgrammingThere are tons of resources to learn more programming skills!A wonderful tool to begin with --- especially if you are interested in learning web technologies --- is [codeacademy](https://codeacademy.com/). This free website is packed with excellent lessons on a variety of topics, including Python, and it's highly recommended for practice and picking up further Python fundamentals.Another useful project for practicing Python skills you've picked up at PWSAfrica is [Project Euler](https://projecteuler.net/). This is a repository of problems to solve that are mathematical in nature. Many of you noticed that we took some of our own problems from the list at Project Euler! There are hundreds of problems there of increasing difficulty, so however confident you are in both mathematics and Python --- whether you're still getting to grips or feel you're professional grade --- Project Euler will contain problems that are just challenging enough for you to tackle *today*.If you'd like to play with other languages, Haskell is a brilliant language for mathematicians who want to write reliable code. However, it comes with a very steep learning curve. [You can read a fantastic free Haskell book which Tom and Ben learned from here.](http://learnyouahaskell.com/) A language with a smaller learning curve is Clojure. Clojure works alongside Java code, so if you're already a Java developer, this might interest you. It is also very interesting from the perspective of language design. However, Clojure also comes with a steep learning curve. [You can read an excellent Clojure book that Tom recommends here.](https://www.braveclojure.com/)Note that these languages come with a steep learning curve because they require you to:* Understand more fundamental aspects of computation than Python does* Think harder about problem solving than about the expression of ideas* Write programs which don't work at all like the Python programs you already know*Tom strongly recommends that you become proficient at solving problems in Python before approaching these languages.* However, if you become interested in programming, learning these languages will bring massive advantages in training you how to solve problems, *and* in expressing your solutions to those problems in a way the computer will understand. Learning Data ScienceA great resource for learning data science is [towardsdatascience.com](https://towardsdatascience.com/). If you're interested in more hardcore data science, taking and academic perspective, [distill.pub](https://distill.pub/) publishes academic papers which are designed to be as easy to read as possible.These might be pretty heavy if you're only just getting into data science! If you're curious about the mathematics behind things like AI and ML but don't know where to start, [Data Skeptic](https://dataskeptic.com/podcast/) is a great podcast which gives accessible introductions to an incredible number of topics.Remember: [codeacademy](https://codeacademy.com/) has lessons on data science, too! Constraint ProgrammingConstraint programming is a technique used when we have certain descriptions of problems, such as criteria that they have to meet, and want to produce a solution or set of solutions which satisfy those criteria. This technique is *very* popular in fields such as operations research.Constraint Programming don't have a massive Python community, but a good place to start is [the python-constraint library](https://github.com/python-constraint/python-constraint) SymPySymPy is a library that helps us to perform *Symbolic Computation*. Symbolic computation lets us manipulate equations like you might manipulate data with a regular program --- we figure this might be quite helpful for you!You can get started writing SymPy code [at this tutorial](http://docs.sympy.org/latest/tutorial/preliminaries.html). There's plenty to learn, but now that you know how to write Python code, we're sure you can work things out! When you get stuck, you can check StackOverflow; we talk about that below. ---  Our tips & TechniquesWe thought it might be helpful to make some notes here of useful Python things, and tips on how professional programmers write code, to help you out when you're teaching yourself. Good programmers read as much code as they writeIn school, when we want to write an essay, we don't just write and write until we produce something we are happy with. The first step is to *read*! Indeed, the best writers spend lots of their time reading. Similarly, the best programmers read other people's code. This has a few benefits:* It helps to inform your own sense of style* It trains you in computational thinking, because you have to learn to follow what the code *means* in your head.* Sometimes, when you get good at reading code, you spot ways not to do things!* You're constantly exposed to new ideas and ways of approaching problems* You become more naturally familiar with the language you are working with, *helping you to express your own ideas in that language*.A good place to begin reading Python code is [Keon's repository of Python algorithms](https://github.com/keon/algorithms). Readable code is absolutely essential.We don't just write our code --- other people have to read it too, and *we* might have to read it if we revisit a project a few months after we've written something. To make your code readable, remember to give your variable names some names that remind you what the variable is for! Also, remember to place many comments in your code. These are useful because they can serve as reminders to yourself of why you wrote particular parts of code the way you did; they are also a great way of making your code readable to others, as they explain what's happening *inline* with the code. Markdown cells in a Jupyter notebook are another great way to achieve this! DebuggingPay attention to error messages! They contain useful information: usually they contain a line number and sometimes even an arrow pointing to the *character* in your code the error occurs on. If you pay attention, you'll spot when your error is indentation, or a missing colon at the end of a loop/if statement, and all of these common errors.Some debugging has to happen when code *runs*, but doesn't give us the right output. If your code isn't giving you the result you expect, the best way to find out what's going on is to try to execute it by hand or in your head. Get some paper and begin writing out th steps the computer will take when it runs your code, and the way it changes the state of your variables. Also, try placing as many print statements in your code as you need. This lets you see what's happening in the machine!*Hint*: with functional code, you can avoid a lot of errors with state! For this reason, a lot of professional software engineers are beginning to use functional code as much as possible. [StackOverflow](https://stackoverflow.com) and Google are your friendsYou've seen us fail to solve problems *plenty* of times over the last few weeks, and you've probably heard us say that we don't always know the answer!The truth is that --- especially in a language like Python --- there are all sorts of things a language can do, and it takes years and years to learn them all. And in that time, the language changes! (Something that tripped Tom up a lot while teaching was that all of his research uses Python 2.7; we taught Python 3.6.)For example, did you know that Python is a very popular language for writing web servers in? It's also used sometimes in the software that runs banks. Python is also very popular for machine learning and AI. One person can't possible learn all of these fields properly!So, when we want to solve a problem, many software engineers will google for hints toward solving certain problems. These can be really simple problems, like how to replace characters in a string (Python has a method for this, but it's much easier to google all of the methods you rarely use than to memorise them).[StackOverflow](https://stackoverflow.com) is a *very* popular site for asking questions about code, or finding answers to the questions other people have asked too!And if you ever feel silly about googling something you think is simple, remember that [we all do the same](http://imposterroster.com). Use GitHub to store your codeAs we mentioned earlier: if you're interested in software engineering, GitHub works as a kind of portfolio site, which is helpful when applying for further study or jobs. [You can find GitHub here.](https://github.com) Play with Object Orientation*Object orientation* is a powerful concept used frequently in professional software engineering, which Python comes with excellent built-in support for. The concept of object orientation is that a variable should refer not just to data, but to the *behaviour* of the data. This is something you've actually already used! For example, when we call `mystring.split()`, the `.split()` function is actually a *"method"* called on a string *"object"*. What's especially useful is that we can define our own objects! The type of an object is known as its class. We often say that a variable of a certain class is an *instance* of that class. We can define classes like so:<jupyter_code>class MyClass:

    def __init__(self, some_argument):
        print("The __init__ function is run every time we make an instance of a class!")
        self.data = some_argument

    # Methods *must* have at least one argument. It will refer to the current instance of our class.
    # Pytohn programmers *always* call this argument "self".
    def example_method(self):
        print("I'm printing a message to show a method running on an instance of a class.")

        

# Here, we make an instance of our class, by calling the class name. The __init__ function is automatically run.
# We can see that the __init__ function has been run in this example, because it prints a message for us.
myInstance = MyClass("Arguments can be provided when we make an instance of a class.")


# You can see that methods can be called on our objects, just like we've called methods on strings etc in the past.
# This method will print a second message.
myInstance.example_method()


# The object is made so that, when we run the __init__ function, 
#   we set an *attribute* of the object to the argument we passed in.
# Because we're accessing data, not a function, we're not calling anything.
# This means we don't need () brackets at the end when we access an attribute.
print(myInstance.data)  # prints "Arguments can be provided when we make an instance of a class."<jupyter_output><empty_output><jupyter_text>We encourage you to experiment with writing your own classes, and becoming proficient in object oriented code!You can begin to learn a little about object orientation in Python [at this documentation](https://python.swaroopch.com/oop.html). ---  Some example solutions to part 1  FizzBuzz<jupyter_code># Normal python code:
def fizzbuzz(n):
    out = []
    for curr in range(1,n+1):
        result = ""
        if curr % 3 is 0: result += "Fizz"
        if curr % 5 is 0: result += "Buzz"
        if result is "": result = str(curr)
        out.append(result)
    return out

print("Normal imperative function call:", end="\n\t")
print(fizzbuzz(20))
print()


# Functional-style python code:
three_rep = lambda n: "Fizz" if n%3 is 0 else ""
five_rep  = lambda n: "Buzz" if n%5 is 0 else ""
fb_representation   = lambda n: str(n) if n%3 is not 0 and n%5 is not 0 else three_rep(n) + five_rep(n)
fizzbuzz_functional = lambda n: list(map(fb_representation, range(1,n+1)))

print("Functional implementation call:", end="\n\t")
print(fizzbuzz_functional(20))<jupyter_output><empty_output><jupyter_text>Tic-Tac-Toe**Solution generously donated by <NAME>, with slight modification. *Thanks, Ify!***<jupyter_code>theBoard = {'1': ' ', '2': ' ', '3': ' ', '4': ' ', '5': ' ', '6': ' ', '7': ' ', '8': ' ', '9': ' '}

def gameBoard(board):
    print(board['1'] + '|' + board['2'] + '|' + board['3'])
    print('-+-+-')
    print(board['4'] + '|' + board['5'] + '|' + board['6'])
    print('-+-+-')
    print(board['7'] + '|' + board['8'] + '|' + board['9'])
    
def checkWin(board, turn):
    
    position_matches_turn = lambda position: board[position] == turn
    winningCombinations = [['1','2','3'],
                           ['4','5','6'],
                           ['7','8','9'],
                           ['1','4','7'],
                           ['2','5','8'],
                           ['3','6','9'],
                           ['1','5','9'],
                           ['3','5','7']]
    
    # If we find at least one combination where all positions match the turn,
    # return true.
    for combo in winningCombinations:
        if all(map(position_matches_turn, combo)):
            return True
        
    # We *only* reach this block of code if we didn't return earlier, meaning that
    # no winning combination matched the turn completely. Therefore, return False.
    return False

turn = "X"
gameWon = False
for i in range(9):
    print('Player ' + turn + '\'s Turn')
    move = input()
    theBoard[move] = turn
    gameBoard(theBoard)
    if checkWin(theBoard, turn):
        print('You won! Player ' + turn)
        gameWon = True
        break
    if turn == 'X':
        turn = 'O'
    else:
        turn = 'X'
        
if not gameWon:
    print("Draw!....Try Again")<jupyter_output><empty_output><jupyter_text>Collatz, with graph theory<jupyter_code>memo = {}
next_in_sequence = lambda n: n//2 if n%2 == 0 else n*3 + 1

def collatz(n):    
    # Terminate on 1
    if n == 1:
        return [1]
    
    if n not in memo.keys():
        memo[n] = next_in_sequence(n)
        
    return [n] + collatz(memo[n])

print(collatz(3))

# And, to prove we're building it in this dictionary:
collatz(6)
collatz(20)
print("=====")
print(memo)<jupyter_output><empty_output><jupyter_text>RPN Calculator<jupyter_code># We keep all of our allowed operators in a dictionary.
# The benefit of this approach is that, if we want to support more functions, we just
#     add more entries to the dictionary!
#     We use whatever symbol the user gives as the key in the dictionary, so it's easy to look up.
#     Then, we can use the function in the value to actually perform calculations.
funcs = {
    "+": lambda a, b: a+b,
    "-": lambda a, b: a-b,
    "*": lambda a, b: a*b,
    "/": lambda a, b: a/b
}

# Get the input, and filter into operators and digits
uin = input("Give me an RPN string! Allowed operators are +,-,*,/\t\t")
digits = filter(lambda n: n.isdigit(), uin.split(" "))
digits = list(map(int, digits))  # convert the digits to integers for later calculation
operators = list(filter(lambda o: not o.isdigit(), uin.split(" ")))

# Loop over the input
for op in operators:
    
    # Get our data for the calculation
    a, b = digits.pop(), digits.pop()
    
    # Use `a` and `b` as arguments to the function `op` indicates in our dictionary. 
    # Add the result to `digits` so we can use it later.
    digits.append(funcs[op](a,b))

print(digits[-1])  # 30<jupyter_output><empty_output><jupyter_text>ROT13 EncryptionA trick you may have noticed: the function for encryption is *the same* as the function for decryption.<jupyter_code># Without ord or chr
pairs = [['a', 'n'],
         ['b', 'o'],
         ['c', 'p'],
         ['d', 'q'],
         ['e', 'r'],
         ['f', 's'],
         ['g', 't'],
         ['h', 'u'],
         ['i', 'v'],
         ['j', 'w'],
         ['k', 'x'],
         ['l', 'y'],
         ['m', 'z']]
rotations = {}
# Build the dictionary from pairs
for pair in pairs:
    rotations[pair[0]] = pair[1]
    rotations[pair[1]] = pair[0]

def rot13_no_ord_chr(plaintext):
    cyphertext = ""
    for char in plaintext:
        upper = char.isupper()
        char = char.lower()
        if char.isalpha():
            char = rotations[char]
        if upper:
            char = char.upper()
        cyphertext += char
    return cyphertext

print(rot13_no_ord_chr('aB c'))
print(rot13_no_ord_chr(rot13_no_ord_chr('aB c')))
# With ord and chr
def rot13(text):
    text = list(text)
    for index in range(len(text)):
        if text[index].isalpha():
            if (text[index].islower() and text[index] < 'n') or (text[index].isupper() and text[index] < 'N'):
                text[index] = chr(ord(text[index]) + 13)
            else:
                text[index] = chr(ord(text[index]) - 13)
                
    return ''.join(text)


print(rot13('aB c'))
print(rot13(rot13('aB c')))<jupyter_output><empty_output><jupyter_text>*Advanced:* Markov Chain<jupyter_code>import random
from time import sleep

my_chain = {
    'A': {'A': 0.6,
          'E': 0.4},
    'E': {'A': 0.7,
          'E': 0.3}
}

def __choose_state(state_map):
    choice = random.random()
    probability_reached = 0
    for state, probability in state_map.items():
        probability_reached += probability
        if probability_reached > choice:
            return state

def next_state(chain, current_state):
    next_state_map = chain.get(current_state)
    next_state = __choose_state(next_state_map)
    return next_state


# Print a new state every second, for 25 seconds
state = 'A'  # Choose an initial state
for _ in range(25):
    sleep(1)
    state = next_state(my_chain, state)
    print(state)

print("===END===")<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.08355271030274022, 'fraction_numerical': 0.0098719636233098, 'mean_word_length': 4.063465616479855, 'pattern_counts': {'":': 5, '<': 64, '<?xml version=': 0, '>': 70, 'https://': 20, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '15', 'dir': 'jupyter-structured-clean-dedup', 'id': '79181', 'license': 'MIT', 'n_tokens_mistral': 9098, 'n_tokens_neox': 8624, 'n_words': 4820, 'path': 'assets/future_work.ipynb', 'repo_name': 'pwsafrica/pwsafrica.github.io', 'size': '33430'}