I am working on the file "Python/Chapter-07/Heap.ipynb".
The first task is:
$p_{\mathrm{top}} \leq p \;\wedge\; l.\texttt{count}() \leq r.\texttt{count}() \;\rightarrow\;
     \texttt{Node}(p_{\mathrm{top}},v_\mathrm{top},l,r).\texttt{insert}(p,v) =
     \texttt{Node}\bigl(p_\mathrm{top},v_\mathrm{top},l.\texttt{insert}(p,v), r\bigr)$
$p_{\mathrm{top}} \leq p \;\wedge\; l.\texttt{count}() > r.\texttt{count}() \;\rightarrow
     \texttt{Node}(p_{\mathrm{top}},v_\mathrm{top},l,r).\texttt{insert}(p,v) =
     \texttt{Node}\bigl(p_\mathrm{top},v_\mathrm{top},l,r.\texttt{insert}(p,v)\bigr)$
$p_{\mathrm{top}} > p \;\wedge\; l.\texttt{count}() \leq r.\texttt{count}() \;\rightarrow
     \texttt{Node}(p_{\mathrm{top}},v_\mathrm{top},l,r).\texttt{insert}(p,v) =
     \texttt{Node}\bigl(p,v,l.\texttt{insert}(p_\mathrm{top},v_\mathrm{top}), r\bigr)$
$p_{\mathrm{top}} > p \;\wedge\; l.\texttt{count}() > r.\texttt{count}() \;\rightarrow
     \texttt{Node}(p_{\mathrm{top}},v_\mathrm{top},l,r).\texttt{insert}(p,v) =
     \texttt{Node}\bigl(p,v,l,r.\texttt{insert}(p_\mathrm{top},v_\mathrm{top})\bigr)$
Can you write Python code for it?

def insert(self, p, v):
    p_top, v_top, l, r = self._extract()
    if p_top <= p:
        if l._count() <= r._count():
            return Node(p_top, v_top, l.insert(p, v), r)
        else:
            return Node(p_top, v_top, l, r.insert(p, v))
    else:
        if l._count() <= r._count():
            return Node(p, v, l.insert(p_top, v_top), r)
        else:
            return Node(p, v, l, r.insert(p_top, v_top))

Node.insert = insert
del insert


I am working on the file "Python/Chapter-07/Heap.ipynb".
The first task is:
Defining $\texttt{Nil}$ and $\texttt{Node}(p, v, l, r)$ as Classes
The class Nil represents an empty heap.
Can you write Python code for it?

class Nil(Heap):
    def _count(self):
        return 0

    def __str__(self):
        return 'Nil()'


I am working on the file "Python/Chapter-07/Heap.ipynb".
The first task is:
Implementing Priority Queues as Heaps
Ths notebook presents heaps.  We define the set $\mathcal{H}$ of heaps by induction:

$\texttt{Nil} \in \mathcal{H}$.
$\texttt{Node}(p,v,l,r) \in \mathcal{H}$ if and only if the following is true:

$p \leq l \;\wedge\; p \leq r$
The priority stored at the root is less than or equal to every other priority stored in
the heap. This condition is known as the heap condition.
It is important to remember that we associate high priorities
with small numbers.
      - $\mid l.\texttt{count}() - r.\texttt{count}() \mid \;\leq\, 1$
The number of elements in the left subtree differs from the number of elements stored in
the right subtree by at most one.
This condition is known as the  balancing condition.
      - $l \in \mathcal{H} \;\wedge\; r \in \mathcal{H}$
Both the left and the right subtree of a heap are heaps.

The class Heap is a superclass for constructing heaps.  We will later define the classes Nil and Node that represent heaps of the form $\texttt{Nil}$ and $\texttt{Node}(p, v, l, r)$ respectively. The class Heap has one static variable sNodeCount which is needed to assign unique identifiers to different nodes.  Every object of class Heap has a uniques identifier mID that is stored as a member variable.  This identifier is used by graphviz.  In order to generate new identifiers we use the static variable sNodeCount as a counter.
Can you write Python code for it?

class Heap:
    sNodeCount = 0

    def __init__(self):
        Heap.sNodeCount += 1
        self.mID = str(Heap.sNodeCount)

    def getID(self):
        return self.mID  # used only by graphviz


I am working on the file "Python/Chapter-07/Heap.ipynb".
The first task is:
The class Node represents a heap of the form $\texttt{Node}(p,v,l,r)$ where
  - $p$ is the priority stored as mPriority,
  - $v$ is the value stored as mValue,
  - $l$ is the left subtree stored as mLeft,
  - $r$ is the right subtree stored as mRight,
  - The number of nodes is stored in the member variable mCount.
Can you write Python code for it?

class Node(Heap):
    def __init__(self, priority, value, left, right):
        Heap.__init__(self)
        self.mPriority = priority
        self.mValue    = value
        self.mLeft     = left
        self.mRight    = right
        self.mCount    = left._count() + right._count() + 1

    def _extract(self):
        return self.mPriority, self.mValue, self.mLeft, self.mRight

    def _count(self):
        return self.mCount

    def __str__(self):
        return _make_string(self, ['mPriority', 'mValue', 'mLeft', 'mRight'])


I am working on the file "Python/Chapter-07/Heap.ipynb".
The first task is:
The function make_string is a helper function that is used to simplify the implementation of the method __str__.
  - self is the object that is to be rendered as a string
  - attributes is a list of the names of those member variables of the object self         that are used to create the string that is returned.
Can you write Python code for it?

def _make_string(self, attributes):
        # get the name of the class of the object self
        name = self.__class__.__name__
        # map the function __str__ to all attributes and join them with a comma
        return f"{name}({', '.join(map(str, [getattr(self, at) for at in attributes]))})"

Heap._make_string = _make_string


I am working on the file "Python/Chapter-07/Heap.ipynb".
The first task is:
The method $t.\texttt{collectIDs}(d)$ takes a binary trie $t$ and a dictionary $d$ and updates the dictionary so that the following holds:
$$ d[\texttt{id}] = n \quad \mbox{for every node $n$ in $t$.} $$
Here, $\texttt{id}$ is the unique identifier of the node $n$, i.e. $d$ associates the identifiers with the corresponding nodes.
Can you write Python code for it?

def _collectIDs(self, nodeDict):
    nodeDict[self.getID()] = self
    if isinstance(self, Node):
        self.mLeft ._collectIDs(nodeDict)
        self.mRight._collectIDs(nodeDict)

Heap._collectIDs = _collectIDs


I am working on the file "Python/Chapter-07/Heapsort.ipynb".
The first task is:
Heapsort
The function call swap(A, i, j) takes an array A and  two indexes i and j and exchanges the elements at these indexes.
Can you write Python code for it?

def swap(A, i, j):
    A[i], A[j] = A[j], A[i]


I am working on the file "Python/Chapter-07/Heapsort.ipynb".
The first task is:
Testing
Can you write Python code for it?

import random as rnd

def demo():
    L = [ rnd.randrange(1, 200) for n in range(1, 16) ]
    print("L = ", L)
    heap_sort(L)
    print("L = ", L)

demo()


I am working on the file "Python/Chapter-07/Heapsort.ipynb".
The first task is:
The function call heapSort(A) has the task to sort the array A and proceeds in two phases.
- In phase one our goal is to transform the array Ainto a heap that is stored in A.
In order to do so, we traverse the array A in reverse in a loop.
  The invariant of this loop is that before
  sink is called, all trees rooted at an index greater than
  k satisfy the heap condition.  Initially this is true because the trees that
  are rooted at indices greater than  $(n + 1) // 2 - 1$ are trivial, i.e. they only
  consist of their root node.
In order to maintain the invariant for index k, sink is called with
  argument k,  since at this point, the tree rooted at index k satisfies
  the heap condition except possibly at the root.  It is then the job of $\texttt{sink}$ to
  establish the heap condition at index k.  If the element at the root has a
  priority that is too low, sink ensures that this element sinks down in the tree
  as far as necessary.
- In phase two we remove the elements from the heap one-by-one and insert them at the end of
  the array.
When the while-loop starts, the array A contains a heap.  Therefore,
  the smallest element is found at the root of the heap.  Since we want to sort the
  array A descendingly, we move this element to the end of the array A and in
  return move the element from the end of the arrayAto the front.
  After this exchange, the sublist A[0:n-1] represents a heap, except that the
  heap condition might now be violated at the root.  Next, we decrement n, since the
  last element of the array A is already in its correct position.
  In order to reestablish the heap condition at the root, we call sink with index
  0.
Can you write Python code for it?

def heap_sort(A):
    n = len(A) - 1
    for k in range((n + 1) // 2 - 1, -1, -1):
        sink(A, k, n)
    while n >= 1:
        swap(A, 0, n)
        n -= 1
        sink(A, 0, n)


I am working on the file "Python/Chapter-07/Heapsort.ipynb".
The first task is:
The function toDot takes three arguments:
- A is an array of natural numbers of length $n$,
- f is a natural number such that $0 \leq f < n$ holds,
- g is a natural number such that $f < g < n$ holds.
The function returns a graphical representation of the array A as a heap.
This graphical representation is stored as a directed graph with an encoding suitable for graphviz.
The part A[0:g] is represented as a binary tree, while the part A[g:] is represented
as a list.  Furthermore, all indexes in the range A[k:g] satisfy the heap condition.
Can you write Python code for it?

def toDot(A, f, g):
    n   = len(A)
    dot = gv.Digraph(node_attr={'shape': 'record'})
    for k, p in enumerate(A):
        if k < f:
            dot.node(str(k), label='{' + str(p) + '|' + str(k) + '}', style='filled', fillcolor='red')
        elif k < g:
            dot.node(str(k), label='{' + str(p) + '|' + str(k) + '}', style='rounded')
        else:
            dot.node(str(k), label='{' + str(p) + '|' + str(k) + '}', style='filled', fillcolor='green')
    for k in range(0, n // 2):
        if 2 * k + 1 < g:
            dot.edge(str(k), str(2 * k + 1))
        if 2 * k + 2 < g:
            dot.edge(str(k), str(2 * k + 2))
    return dot


I am working on the file "Python/Chapter-07/Heapsort.ipynb".
The first task is:
The procedure sink takes three arguments.
- A is the array representing the heap.
- k is an index into the array A.
- n is the upper bound  of the part of this array that has to be transformed into a heap.
The array A itself might actually have more than $n+1$ elements, but for the
purpose of the method sink we restrict our attention to the subarray
A[k:n].
When calling sink, the assumption is that A[k:n+1] should represent a heap
that possibly has its heap condition violated at its root, i.e. at index k.  The
purpose of the procedure sink is to restore the heap condition at index k.
- We compute the index j of the left subtree below index k.
- We check whether there also is a right subtree at position j+1.
This is the case if j + 1 <= n.
- If the heap condition is violated at index k, we exchange the element at  position k
  with the child that has the higher priority, i.e. the child that is smaller.
- Next, we check in line 9 whether the heap condition is violated at index k.
  If the heap condition is satisfied, there is nothing left to do and the procedure returns.

Otherwise, the element at position k is swapped with
  the element at position j.

Of course, after this swap it is possible that the heap condition is
  violated at position j.  Therefore,  k is set to j and the while-loop continues
  as long as the node at position k has at least one child, i.e. as long as
  2 * k + 1 <= n.
Can you write Python code for it?

def sink(A, k, n):
    while 2 * k + 1 <= n:
        j = 2 * k + 1
        if j + 1 <= n and A[j] > A[j + 1]:
            j += 1
        if A[k] < A[j]:
            return
        swap(A, k, j)
        k = j


I am working on the file "Python/Chapter-07/Heapsort.ipynb".
The first task is:
The version of heap_sort given below adds some animation.
Can you write Python code for it?

def heap_sort(A):
    n = len(A) - 1
    display(toDot(A, n // 2, len(A)))
    for k in range((n + 1) // 2 - 1, -1, -1):
        sink(A, k, n)
        print(f'k = {k}')
        display(toDot(A[:], k, len(A)))
    while n >= 1:
        swap(A, 0, n)
        display(toDot(A[:], 1, n + 1))
        n -= 1
        sink(A, 0, n)
        display(toDot(A[:], 0, n + 1))


I am working on the file "Python/Chapter-07/Heapsort.ipynb".
The first task is:
This notebook implements an array-based version of Heapsort.
Graphical Representation of Heaps as Trees
Can you write Python code for it?


I am working on the file "Python/Chapter-07/ListMap.ipynb".
The first task is:
A MapIterator is an iterator that iterates over the elements of a linked list.  It maintains a pointer mPtr that points to the next element.
It is implemented via the function __next__.  This function either returns the next key-vale pair or, if there are no more key-value pairs left, then it raises a
StopIteration exception.
If the __iter__ method of a class $C$ returns an iterator, then we can use a
for-loop to iterate over the elements contained in class $C$.
Can you write Python code for it?

class MapIterator:
    def __init__(self, ptr):
        self.mPtr = ptr

    def __next__(self):
        if self.mPtr == None:
            raise StopIteration
        key   = self.mPtr.mKey
        value = self.mPtr.mValue
        self.mPtr = self.mPtr.mNextPtr
        return key, value

def main(n = 100):
    S = ListMap()
    for i in range(2, n + 1):
        S.insert(i, True)
    for i in range(2, n // 2 + 1):
        for j in range(i, n // i + 1):
            S.delete(i * j)
    print([p for p, _ in S]) # iterates over ListMap
    print(S.find(83))
    print(S.find(99))

main()


I am working on the file "Python/Chapter-07/ListMap.ipynb".
The first task is:
Given the first node of a linked list $L$, the function $L.\texttt{insert}(k, v)$ inserts the key-value pair $(k, v)$ into the list $L$.  If there is already a key value pair in $L$ that has the same key, then the old value is overwritten.  It returns a boolean that is true if a new node has been allocated.
Can you write Python code for it?

def insert(self, key, value):
    while True:
        if self.mKey == key:
            self.mValue = value
            return False
        elif self.mNextPtr != None:
            self = self.mNextPtr
        else:
            self.mNextPtr = ListNode(key, value)
            return True

ListNode.insert = insert
del insert


I am working on the file "Python/Chapter-07/ListMap.ipynb".
The first task is:
Given the first node of a linked list $L$, the function $L.\texttt{toString}()$ returns a string representing $L$.
Can you write Python code for it?

def toString(self):
    if self.mNextPtr != None:
        return f'{self.mKey} ↦ {self.mValue}, ' + self.mNextPtr.__str__()
    else:
        return f'{self.mKey} ↦ {self.mValue}'

ListNode.__str__ = toString
del toString


I am working on the file "Python/Chapter-07/ListMap.ipynb".
The first task is:
Implementing Maps as Lists of Key-Value-Pairs
The class ListNode implements a node of a linked lists of
key-value pairs.  Every node has three member variables:
- mKey     stores the key,
- mValue   stores the value associated with this key, and
- mNextPtr stores a reference to the next node.  If there is no next node, then
  mNextPtr is None.
Objects of class ListNode are used to represent linked lists.
The constructor of the class ListNode creates a single node that stores the given key and its associated value.
Can you write Python code for it?

class ListNode:
    def __init__(self, key, value):
        self.mKey     = key
        self.mValue   = value
        self.mNextPtr = None


I am working on the file "Python/Chapter-07/ListMap.ipynb".
The first task is:
The class ListMap implements a map using a linked list of key-value pairs.  Basically, it is a wrapper for the class
ListNode.  Furthermore, an object of type ListMap is iterable.
Can you write Python code for it?

class ListMap:
    def __init__(self):
        self.mPtr = None

    def find(self, key):
        if self.mPtr != None:
            return self.mPtr.find(key)

    def insert(self, key, value):
        if self.mPtr != None:
            return self.mPtr.insert(key, value)
        else:
            self.mPtr = ListNode(key, value)
            return True

    def delete(self, key):
        if self.mPtr != None:
            self.mPtr, flag = self.mPtr.delete(key)
            return flag
        return False

    def __iter__(self):
        return MapIterator(self.mPtr)

    def __str__(self):
        if self.mPtr != None:
            return '{' + self.mPtr.__str__() + '}'
        else:
            return '{}'

    def __repr__(self):
        return self.__str__()


I am working on the file "Python/Chapter-07/Ordered-Binary-Tree.ipynb".
The first task is:
Given a non-empty ordered binary tree $t$, the expression $t.\texttt{delMin}()$ removes the smallest key $k_m$ and its associated value $v_m$ from $t$ and returns the triple
$$(r,k_m,v_m),$$
where $r$ is the tree that  results from removing $k_m$ and $v_m$ from $t$.  The function is defined via the following equations:
  - $\texttt{Node}(k, v, \texttt{Nil}, r).\texttt{delMin}() = (r, k, v)$
If the left subtree is empty, $k$ has to be the smallest key in the tree
$\texttt{Node}(k, v, \texttt{Nil}, r)$.  If $k$ is removed, we are left with the subtree $r$.

$l\not= \texttt{Nil} \wedge l.\texttt{delMin}() = (l',k_{min}, v_{min}) \;\rightarrow
      \texttt{Node}(k, v, l, r).\texttt{delMin}() = \bigl(\texttt{Node}(k, v, l', r), k_{min}, v_{min}\bigr)$.
If the left subtree $l$ in the binary tree $t = \texttt{Node}(k, v, l, r)$
is not empty, then the smallest key of  $t$ is located inside the left subtree $l$.
This smallest key is recursively removed from  $l$. This yields the tree
$l'$.  Next,  $l$ is replaced by $l'$ in $t$.  The resulting tree is
$t' = \texttt{Node}(k, v, l', r)$.
Can you write Python code for it?

def _delMin(self):
    if self.mLeft.isEmpty():
        return self.mRight, self.mKey, self.mValue
    else:
        ls, km, vm = self.mLeft._delMin()
        self.mLeft = ls
        return self, km, vm

OrderedBinaryTree._delMin = _delMin
del _delMin


I am working on the file "Python/Chapter-07/Ordered-Binary-Tree.ipynb".
The first task is:
Given an ordered binary tree $t$ and a key $k$, the expression $t.\texttt{find}(k)$ returns the value stored unter the key $k$.
The method find is defined inductively as follows:
  - $\texttt{Nil}.\texttt{find}(k) = \Omega$,
because the empty tree is interpreted as the empty map.

$\texttt{Node}(k, v, l, r).\texttt{find}(k) = v$,
because the node $\texttt{Node}(k,v,l,r)$ stores the assignment $k \mapsto v$.
  - $k_1 < k_2 \rightarrow \texttt{Node}(k_2, v, l, r).\texttt{find}(k_1) = l.\texttt{find}(k_1)$,
because if $k_1$ is less than $k_2$, then any mapping for $k_1$ has to be stored in the left subtree  $l$.
  - $k_1 > k_2 \rightarrow \texttt{Node}(k_2, v, l, r).\texttt{find}(k_1) = r.\texttt{find}(k_1)$,
because if $k_1$ is greater than $k_2$, then any mapping for $k_1$ has to be stored in the right subtree  $r$.
Can you write Python code for it?

def find(self, key):
    if self.isEmpty():
        return None
    elif self.mKey == key:
        return self.mValue
    elif key < self.mKey:
        return self.mLeft.find(key)
    else:
        return self.mRight.find(key)

OrderedBinaryTree.find = find
del find


I am working on the file "Python/Chapter-07/Ordered-Binary-Tree.ipynb".
The first task is:
Given an ordered binary tree $t$, the expression $t.\texttt{isEmpty}()$ checks whether $t$ is the empty tree.
Can you write Python code for it?

def isEmpty(self):
    return self.mKey == None

OrderedBinaryTree.isEmpty = isEmpty
del isEmpty


I am working on the file "Python/Chapter-07/Ordered-Binary-Tree.ipynb".
The first task is:
Given an ordered binary tree $t$, the function $t.\texttt{toDot}()$ renders the tree graphically using graphviz.
Can you write Python code for it?

def toDot(self):
    OrderedBinaryTree.sNodeCount = 0 # static variable of class OrderedBinaryTree
    dot = gv.Digraph(node_attr={'shape': 'record', 'style': 'rounded'})
    NodeDict = {}
    self._assignIDs(NodeDict)
    for n, t in NodeDict.items():
        if t.mValue != None:
            dot.node(str(n), label='{' + str(t.mKey) + '|' + str(t.mValue) + '}')
        elif t.mKey != None:
            dot.node(str(n), label=str(t.mKey))
        else:
            dot.node(str(n), label='', shape='point')
    for n, t in NodeDict.items():
        if not t.mLeft == None:
            dot.edge(str(n), str(t.mLeft.mID))
        if not t.mRight == None:
            dot.edge(str(n), str(t.mRight.mID))
    return dot

OrderedBinaryTree.toDot = toDot
del toDot


I am working on the file "Python/Chapter-07/Ordered-Binary-Tree.ipynb".
The first task is:
Given two ordered binary trees s and t, the expression s._update(t) overwrites the attributes of s with the corresponding attributes of t.
Can you write Python code for it?

def _update(self, t):
    self.mKey   = t.mKey
    self.mValue = t.mValue
    self.mLeft  = t.mLeft
    self.mRight = t.mRight

OrderedBinaryTree._update = _update
del _update


I am working on the file "Python/Chapter-07/Ordered-Binary-Tree.ipynb".
The first task is:
In order to check whether the method delete works as expected, we try the following:
Can you write Python code for it?

for k in range(30):
    t.delete(k)
t.toDot()


I am working on the file "Python/Chapter-07/Ordered-Binary-Tree.ipynb".
The first task is:
Let us compute the set $S$ of prime numbers up to some given number $n\in\mathbb{N}$.  Mathematically, this set can be defined as
$$ S = { 2, \cdots, n } - \bigl{ p \cdot q \;\big|\; p, q \in {2, \cdots, n } \bigr}. $$
Can you write Python code for it?

n = 100
S = OrderedBinaryTree()
L = list(range(2, n + 1))
rnd.shuffle(L)  # avoid worst case performance
for x in L:
    S.insert(x, None)
for p in range(2, n // 2 + 1):
    for q in range(p, n // p + 1):
        S.delete(p * q)
S.toDot()

print(S.keyList())


I am working on the file "Python/Chapter-07/Ordered-Binary-Tree.ipynb".
The first task is:
Let's generate an ordered binary tree with random keys.
Can you write Python code for it?

import random as rnd

t = OrderedBinaryTree()
for k in range(30):
    k = rnd.randrange(100)
    t.insert(k, None)
t.toDot()


I am working on the file "Python/Chapter-07/Ordered-Binary-Tree.ipynb".
The first task is:
Ordered Binary Trees
This notebook implements ordered binary trees.  In order to define this notion, we first have to define
the concept of ordered binary trees.  In the following, assume a set $\texttt{Key}$ and a set $\texttt{Value}$ are given.   Then, the
set $\mathcal{B}$ of all ordered binary trees is defined inductively.
  - $\texttt{Nil} \in \mathcal{B}$
  - $\texttt{Node}(k, v, l, r) \in \mathcal{B}$  iff the following conditions hold:
      - $k \in\texttt{Key}$,
      - $v \in\texttt{Value}$,
      - $l \in\mathcal{B}$,
      - $r \in\mathcal{B}$,
      - all keys that occur in the left subtree $l$ are smaller than $k$,
      - all keys that occur in the right subtree $r$ are bigger than $k$,
    therefore $l < k < r$.

The class OrderedBinaryTree represents the nodes of an ordered binary tree.
  - $\texttt{Nil}$           is created as $\texttt{OrderedBinaryTree}()$.
  - $\texttt{Node}(k,v,l,r)$ is created as follows:
    t = OrderedBinaryTree()
    t.mKey   = k
    t.mValue = v
    t.mLeft  = l
    t.mRight = r
The constructor below creates the empty tree.
Can you write Python code for it?

class OrderedBinaryTree:
    def __init__(self):
        self.mKey   = None
        self.mValue = None
        self.mLeft  = None
        self.mRight = None


I am working on the file "Python/Chapter-07/Ordered-Binary-Tree.ipynb".
The first task is:
The function $\texttt{demo}()$ creates a small ordered binary tree.
Can you write Python code for it?

def demo():
    m = OrderedBinaryTree()
    m.insert("anton",  123)
    m.insert("hugo" ,  345)
    m.insert("gustav", 789)
    m.insert("mariam", 345)
    m.insert("jens",   234)
    m.insert("hubert", 432)
    m.insert("andre",  342)
    m.insert("philipp", 342)
    m.insert("rene",   345)
    m.insert("ans", 123)
    m.insert("alfa", 123)
    m.insert("algo", 345)
    return m

t = demo()
t.toDot()

t.delete('mariam')
t.toDot()

t.delete('anton')
t.toDot()

t.delete('gustav')
t.toDot()

t.delete('hubert')
t.toDot()


I am working on the file "Python/Chapter-07/Ordered-Binary-Tree.ipynb".
The first task is:
This tree looks more or less balanced.  Lets us create a tree where things do not work out that well.
Can you write Python code for it?

t = OrderedBinaryTree()
for k in range(30):
    t.insert(k, None)
t.toDot()


I am working on the file "Python/Chapter-07/Trie.ipynb".
The first task is:
For a trie  $t \in \mathbb{T}$ and a string $w \in \Sigma^*$ the value $t.\texttt{delete}(w)$
is defined by induction on the length of  $w$.
  - $\texttt{Trie}(v,C,T).\texttt{delete}(\varepsilon) = \texttt{Trie}(\Omega,C,T)$
The value that is associated with the empty  string $\varepsilon$ is stored at the root of the
trie where is can be deleted without further ado.

$\begin{array}[t]{ll}
     t_i.\texttt{delete}(r).\texttt{isEmpty}()   & \rightarrow \
     \texttt{Trie}(v, [c_1,\cdots,c_i,\cdots,c_n],[t_1,\cdots,t_i,\cdots,t_n]).\texttt{delete}(c_ir)
     & = \
     \qquad
     \texttt{Trie}(v, [c_1,\cdots,c_{i-1},c_{i+1},\cdots,c_n],[t_1,\cdots,t_{i-1},t_{i+1},\cdots,t_n]).
     \end{array}
    $
If  the key that is to be deleted starts with the character $c_i$ and if deletion of  the key
$r$ in the $i$th  trie $t_i$ yields an empty
trie, then both the $i^\mathrm{th}$ character $c_i$ and the $i^\mathrm{th}$ trie $t_i$ are deleted.
  - $\begin{array}[t]{ll}
 \neg t_i.\texttt{delete}(r).\texttt{isEmpty}()   & \rightarrow \
 \texttt{Trie}(v, [c_1,\cdots,c_i,\cdots,c_n],[t_1,\cdots,t_i,\cdots,t_n]).\texttt{delete}(c_ir)
 & = \
 \qquad \texttt{Trie}(v, [c_1,\cdots,c_i,\cdots,c_n],[t_1,\cdots,t_i.\texttt{delete}(r),\cdots,t_n]).
 \end{array}
$
If  the key that is to be deleted starts with the character $c_i$ and if deletion of  the key
$r$ in the $i^\mathrm{th}$  trie $t_i$ yields a non-empty trie, then the key $r$ has to be deleted recursively
in the trie $t_i$.
  - $c \notin C \rightarrow \texttt{Trie}(v, C, T).\texttt{delete}(cr) =
   \texttt{Trie}(v, C, T)$.
If  the key that is to be deleted starts with the character $c$ and  $c$ does not occur in
the list of characters $C$, then the trie does not contain the key $cr$ and therefore there
is nothing  to do:  The trie is left unchanged.
Can you write Python code for it?

def delete(self, s):
    if s == '':
        self.mValue = None
        return
    c, r = s[0], s[1:]
    for i, ci in enumerate(self.mChars):
        if c == ci:
            self.mTries[i].delete(r)
            if self.mTries[i].isEmpty():
                self.mChars.pop(i)
                self.mTries.pop(i)
            return

Trie.delete = delete
del delete


I am working on the file "Python/Chapter-07/Trie.ipynb".
The first task is:
Graphical Representation via GraphViz
Can you write Python code for it?

import graphviz as gv

def toDot(self):
    Trie.sTrieCount = 0 # this is a static variable of the class Trie
    dot = gv.Digraph(node_attr={'shape': 'record', 'style': 'rounded'})
    TrieDict = {}
    self._assignIDs(TrieDict)
    for id, t in TrieDict.items():
        if t.mValue != None:
            dot.node(id, label='{' + t.mParent + '|' + str(t.mValue) + '}')
        else:
            dot.node(id, label=t.mParent)
    for id, t in TrieDict.items():
        for x in t.mTries:
            dot.edge(id, x.mID)
    return dot

Trie.toDot = toDot
del toDot

def _assignIDs(self, TrieDict):
    Trie.sTrieCount += 1
    self.mID = str(Trie.sTrieCount)
    TrieDict[self.mID] = self
    for t in self.mTries:
        t._assignIDs(TrieDict)

Trie._assignIDs = _assignIDs
del _assignIDs


I am working on the file "Python/Chapter-07/Trie.ipynb".
The first task is:
In order to implement deletion in tries, we need the auxiliary function isEmpty.  Its signature is given as
$$ \texttt{isEmpty}: \mathbb{T} \rightarrow \mathbb{B}. $$
For a trie $t$, we have $t.\texttt{isEmpty}() = \texttt{True}$ if and only if the trie $t$ does not
store any key.  The following formula specifies the function $\texttt{isEmpty}$:
$$ \texttt{Trie}(v, L, T).\texttt{isEmpty}() = \mathtt{True} \Leftrightarrow v = \Omega \wedge L = []. $$
Note that the condition $L = []$ implies $T = []$.
Can you write Python code for it?

def isEmpty(self):
    return self.mValue == None and self.mChars == []

Trie.isEmpty = isEmpty
del isEmpty


I am working on the file "Python/Chapter-07/Trie.ipynb".
The first task is:
Let us compute all key stored in this trie, as these are the prime numbers.
Can you write Python code for it?

print(sorted([int(p) for p in S.allKeys()]))


I am working on the file "Python/Chapter-07/Trie.ipynb".
The first task is:
Next, we specify the auxiliary function replacePrefix that has the following signature:
$$ \texttt{replacePrefix}: \mathbb{T} \times \Sigma^ \times \Sigma^ \rightarrow \texttt{Set}(\Sigma^*) $$
Given a trie $T$, a string $s$, and a string $p$, it returns the set of all strings that are used as keys in $T$
and, furthermore, have the prefix $s$.  Additionally, it replaces the prefix $s$ with the string $p$.  Therefore
we have
$$ T.\texttt{replacePrefix}(s, p) = \bigl{ p+r \mid s + r \in T \bigr}. $$
The second argument $p$ is needed in order to make the inductive definition of the function
replacePrefix work out.  This definition is given next:
- $T.\texttt{replacePrefix}(\varepsilon, p) = T.\texttt{allKeys}(p)$,
- $\texttt{Trie}\bigl(v, [c_1, \cdots, c_n], [t_1,\cdots,t_n]\bigr).\texttt{replacePrefix}(c_ir, p) =
   t_i.\texttt{replacePrefix}(r, p)
  $
- $c \not\in C!s \rightarrow \texttt{Trie}(v, C!s, T!s).\texttt{replacePrefix}(cr, p) = {}$.
Can you write Python code for it?

def replacePrefix(self, s, p):
    if s == '':
        return self.allKeys(p)
    c, r = s[0], s[1:]
    for i, ci in enumerate(self.mChars):
        if c == ci:
            return self.mTries[i].replacePrefix(r, p)
    return set()

Trie.replacePrefix = replacePrefix
del replacePrefix


I am working on the file "Python/Chapter-07/Trie.ipynb".
The first task is:
String Completion
In order to implement string completion we define the function allKeys, which
has the following signature:
$$\texttt{allKeys}: \mathbb{T} \times \Sigma^ \rightarrow \texttt{Set}(\Sigma^)$$
Given a trie $T$ and a string $p$, the function $T.\texttt{allKeys}(p)$ computes the set of all strings that
are stored as keys in the trie $T$.  Furthermore, the string $p$ is added as a prefix to all these string.
Therefore we specify the semantics of the function $\texttt{allKeys}$ as follows:
$$ T.\texttt{allKeys}(p) = { p+w \mid w \in T }. $$
Here, $p+w$ denotes the concatenation of the strings $p$ and $w$ and the expression $w \in T$ is true iff
$T.\texttt{find}(w) \not= \Omega$.
Given a trie $T$, the value $T.\texttt{allKeys}(p)$ is computed by induction on $T$.  There are two cases:
- $\texttt{Trie}(\Omega, [c_1, \cdots, c_n], [t_1,\cdots,t_n]).\texttt{allKeys}(p) =
   \bigcup\limits_{i=1}^n t_i.\texttt{allKeys}(p+c_i)
  $,
- $v \not= \Omega \rightarrow
   \texttt{Trie}(v, [c_1, \cdots, c_n], [t_1,\cdots,t_n]).\texttt{allKeys}(p) =
   {p} \cup \bigcup\limits_{i=1}^n t_i.\texttt{allKeys}(p+c_i)
  $.
Can you write Python code for it?

def allKeys(self, prefix=''):
    Result = set()
    if self.mValue != None:
        Result.add(prefix)
    for i, ci in enumerate(self.mChars):
        Result |= self.mTries[i].allKeys(prefix + ci)
    return Result

Trie.allKeys = allKeys
del allKeys


I am working on the file "Python/Chapter-07/Trie.ipynb".
The first task is:
The signature of the method $\texttt{insert}$ is given as follows:
$$ \texttt{insert}: \mathbb{T} \times \Sigma^ \times \texttt{Value} \rightarrow \mathbb{T}. $$
The result of evaluating
$$ \texttt{Trie}(v_1, [c_1, \cdots, c_n], [t_1, \cdots, t_n]).\texttt{insert}(w, v_2) $$
for a string $w\in \Sigma^$ and a value $v_2 \in \texttt{Value}$ is defined by induction on the
length of $w$:
  - $\texttt{Trie}(v_1,L,T).\texttt{insert}(\varepsilon, v_2) = \texttt{Trie}(v_2,L,T)$.
If a new value $v_2$ is associated with the empty string $\varepsilon$, then the old value
$v_1$, which had been stored at the root before, is overwritten.

$\texttt{Trie}\bigl(v_1,[c_1,\cdots,c_i,\cdots,c_n], [t_1,\cdots,t_i,\cdots,t_n]\bigr).\texttt{insert}(c_ir,v_2) =
     \texttt{Trie}\bigl(v_1,[c_1,\cdots,c_i,\cdots,c_n], [t_1,\cdots,t_i.\texttt{insert}(r,v_2),\cdots,t_n]\bigr)$.
In order to associate a value $v_2$ with the string $c_ir$ in the trie
$$ \texttt{Trie}\bigl(v_1,[c_1,\cdots,c_i,\cdots,c_n], [t_1,\cdots,t_i,\cdots,t_n]\bigr) $$
we have to recursively associate the value $v_2$ with the string $r$ in the trie $t_i$.
  - $c \not\in{c_1,\cdots,c_n} \;\rightarrow\;\texttt{Trie}\bigl(v_1,[c_1,\cdots,c_n], [t_1,\cdots,t_n]\bigr).\texttt{insert}(cr,v_2) =
 \texttt{Trie}\bigl(v_1,[c_1,\cdots,c_n,c], [t_1,\cdots,t_n,\texttt{Trie}(\Omega,[],[]).\texttt{insert}(r,v_2)]\bigr)$.
If we want to associate a value $v$ with the key $cr$ in the trie
$\texttt{Trie}\bigl(v_1,[c_1,\cdots,c_n], [t_1,\cdots,t_n]\bigr)$ then, if the character $c$
does not occur in the list $[c_1,\cdots,c_n]$, we first have to create a new empty trie.
This trie has the form
$$ \texttt{Trie}(\Omega, [], []). $$
Next, we associate the value $v_2$ with the key $r$ in this empty trie.  Finally,
we append the character $c$ to the end of the list $[c_1,\cdots,c_n]$ and append the trie
$$ \texttt{Trie}(\Omega, [], []).\texttt{insert}(r,v_2) $$
to the end of the list $[t_1,\cdots,t_n]$.
Can you write Python code for it?

def insert(self, s, v):
    if s == '':
        self.mValue = v
        return
    c, r = s[0], s[1:]
    for i, ci in enumerate(self.mChars):
        if c == ci:
            self.mTries[i].insert(r, v)
            return
    t = Trie()
    t.insert(r, v)
    t.mParent = c # necessary for visualization
    self.mChars.append(c)
    self.mTries.append(t)

Trie.insert = insert
del insert


I am working on the file "Python/Chapter-08/Heap.ipynb".
The first task is:
Implementing Priority Queues as Heaps
Ths notebook presents heaps.  We define the set $\mathcal{H}$ of heaps by induction:

$\texttt{Nil} \in \mathcal{H}$.
$\texttt{Node}(p,v,l,r) \in \mathcal{H}$ if and only if the following is true:

$p \leq l \;\wedge\; p \leq r$
The priority stored at the root is less than or equal to every other priority stored in
the heap. This condition is known as the heap condition.
It is important to remember that we associate high priorities
with small numbers.
      * $\mid l.\texttt{count}() - r.\texttt{count}() \mid \;\leq\, 1$
The number of elements in the left subtree differs from the number of elements stored in
the right subtree by at most one.
This condition is called the  balancing condition.
      * $l \in \mathcal{H} \;\wedge\; r \in \mathcal{H}$
Both the left and the right subtree of a heap are themselves heaps.

The class Heap is a superclass for constructing heaps.  We will later define the classes Nil and Node that represent heaps of the form $\texttt{Nil}$ and $\texttt{Node}(p, v, l, r)$ respectively.
* The class Heap has one static variable sNodeCount which is needed to assign unique identifiers to different nodes.
  Every object of the class Heap has a uniques identifier mID that is stored as a member variable.  This identifier is used
  by graphviz in order to display a heap as a tree.
* In order to generate new identifiers we use the static variable sNodeCount as a counter.
  Every time a new node is created this counter is incremented.
Can you write Python code for it?

class Heap:
    sNodeCount = 0

    def __init__(self):
        Heap.sNodeCount += 1
        self.mID = str(Heap.sNodeCount)

    def getID(self):
        return self.mID  # used only by graphviz


I am working on the file "Python/Chapter-08/Heap.ipynb".
The first task is:
Implementing the Method  remove
The method self.remove() takes one parameters:
* self is an object of the abstract class Heap that either has the class Nil or Node.
The method self.remove() returns the heap that results if the value with the highest priority in the heap self is removed from self.
In the class Nil the method remove is specified as follows:
- $\texttt{Nil}.\texttt{remove}() = \texttt{Nil}$
Can you write Python code for it?

def remove(self):
    return self

Nil.remove = remove
del remove


I am working on the file "Python/Chapter-08/Heap.ipynb".
The first task is:
Implementing the Method  top
For the class Nil, the function topis specified via a single equation:
 - $\texttt{Nil}.\texttt{top}() = \Omega$
Can you write Python code for it?

def top(self):
    return None

Nil.top = top
del top


I am working on the file "Python/Chapter-08/Heap.ipynb".
The first task is:
In the class Node the method remove is specified as follows:
- $\texttt{Node}(p,v,\texttt{Nil},r).\texttt{remove}() = r$
- $\texttt{Node}(p,v,l,\texttt{Nil}).\texttt{remove}() = l$
- $l = \texttt{Node}(p_1,v_1,l_1,r_1) \;\wedge\; r = \texttt{Node}(p_2,v_2,l_2,r_2) \;\wedge\; p_1 \leq p_2 \;\rightarrow
       \texttt{Node}(p,v,l,r).\texttt{remove}() =      \texttt{Node}(p_1,v_1,l.\texttt{remove}(),r)$
- $l = \texttt{Node}(p_1,v_1,l_1,r_1) \;\wedge\; r = \texttt{Node}(p_2,v_2,l_2,r_2) \;\wedge\; p_1 > p_2 \rightarrow
       \texttt{Node}(p,v,l,r).\texttt{remove}() = \texttt{Node}(p_2,v_2,l,r.\texttt{remove}())$
Note that the heap returned by the method remove might violate the balancing condition.  As heaps only shrink when remove is called,
this fact does not cause performance problems.
Can you write Python code for it?

def remove(self):
    p, v, l, r = self._extract()
    if isinstance(l, Nil):
        return r
    if isinstance(r, Nil):
        return l
    p1, v1, l1, r1 = l._extract()
    p2, v2, l2, r2 = r._extract()
    if p1 <= p2:
        return Node(p1, v1, l.remove(), r)
    else:
        return Node(p2, v2, l, r.remove())

Node.remove = remove
del remove


I am working on the file "Python/Chapter-08/Heap.ipynb".
The first task is:
Next, we sort a million random integers.  As we have stored information for displaying heaps a s trees, the function heap_sort isn't really competitive.
Can you write Python code for it?

%%time
k = 100_000
L = [ rnd.randrange(2 * k) for x in range(k) ]
S = heap_sort(L)


I am working on the file "Python/Chapter-08/Heap.ipynb".
The first task is:
Testing
Below are some simple tests to check whether the methods insert and delete work as expected.
Can you write Python code for it?

h = Nil()
h.toDot()

h = h.insert(2, 'a')
h.toDot()

h = h.insert(1, 'b')
h.toDot()

h = h.insert(7, 'c')
h.toDot()

h = h.insert(0, 'd')
h.toDot()

h = h.insert(8, 'e')
h.toDot()

h = h.insert(3, 'f')
h.toDot()

h = h.insert(4, 'g')
h.toDot()

h = h.remove()
h.toDot()

h = h.remove()
h.toDot()


I am working on the file "Python/Chapter-08/Heap.ipynb".
The first task is:
The function make_string is a helper function that is used to simplify the implementation of the method __str__.
  - self is the object that is to be rendered as a string
  - attributes is a list of the names of those member variables of the object self         that are used to create the string that is returned.
Can you write Python code for it?

def _make_string(self, attributes):
        # get the name of the class of the object self
        name = self.__class__.__name__
        # call the function str for all attributes and join the resulting strings with a comma
        return f"{name}({', '.join(map(str, [getattr(self, at) for at in attributes]))})"

Heap._make_string = _make_string


I am working on the file "Python/Chapter-08/Heap.ipynb".
The first task is:
The method $t.\texttt{collectIDs}(d)$ takes a binary trie $t$ and a dictionary $d$ and updates the dictionary $d$ so that the following holds:
$$ d[\texttt{id}] = n \quad \mbox{for every node $n$ in $t$ such that $\texttt{id}$ is the identifier of node $n$.} $$
Here, $\texttt{id}$ is the unique identifier of the node $n$, i.e. $d$ maps the identifiers to their corresponding nodes.
Can you write Python code for it?

def _collectIDs(self, nodeDict):
    nodeDict[self.getID()] = self
    if isinstance(self, Node):
        self.mLeft ._collectIDs(nodeDict)
        self.mRight._collectIDs(nodeDict)

Heap._collectIDs = _collectIDs


I am working on the file "Python/Chapter-08/Heap.ipynb".
The first task is:
The method $t.\texttt{toDot}()$ takes a binary trie $t$ and returns a graph dot that represents $t$.
This graph can be rendered visually as a tree via the command display(dot).
Can you write Python code for it?

def toDot(self):
    dot = gv.Digraph(node_attr={'shape': 'record', 'style': 'rounded'})
    nodeDict = {}  # assign node identifiers to tree nodes
    self._collectIDs(nodeDict)
    for n, t in nodeDict.items():
        if isinstance(t, Nil):
            dot.node(n, label='', shape='point')
        elif isinstance(t, Node):
            if t.mValue != None:
                dot.node(n, label='{' + str(t.mPriority) + '|' + str(t.mValue) + '}')
            else:
                dot.node(n, label= str(t.mPriority))
        else:
            assert False, f'Unknown node {t}'
    for n, t in nodeDict.items():
        if isinstance(t, Node):
            dot.edge(n, t.mLeft .getID())
            dot.edge(n, t.mRight.getID())
    return dot

Heap.toDot = toDot


I am working on the file "Python/Chapter-08/HeapSort-Ascend-Descend.ipynb".
The first task is:
HeapSort
The function call swap(A, i, j) takes an array A and  two indexes i and j and exchanges the elements at these indexes.
Can you write Python code for it?

def swap(A, i, j):
    A[i], A[j] = A[j], A[i]


I am working on the file "Python/Chapter-08/HeapSort-Ascend-Descend.ipynb".
The first task is:
Testing
Can you write Python code for it?

import random as rnd

def demo():
    L = [ rnd.randrange(1, 200) for n in range(12) ]
    print("L = ", L)
    heap_sort(L)
    print("L = ", L)

demo()


I am working on the file "Python/Chapter-08/HeapSort-Ascend-Descend.ipynb".
The first task is:
The function ascend takes two arguments:
- A is an array that is represented as a list in Python.
- k is an index into the array A.
Therefore we have $k \in \bigl{0, \cdots, \texttt{len}(A)-1\bigr}$.
The array A represents a heap.  However, the heap condition might be violated
at index k: It might be the case that the element at this index is to small and needs to rise to the top
of the heap.  The function ascend will fix the heap condition and will rise the element A[k] as much
as is necessary to turn A into a heap.
Can you write Python code for it?

def ascend(A, k):
    while k > 0:
        p = (k - 1) // 2
        if A[k] < A[p]:
            swap(A, p, k)
            k = p
        else:
            return


I am working on the file "Python/Chapter-08/HeapSort-Ascend-Descend.ipynb".
The first task is:
The function toDot takes four arguments:
- A is an array of natural numbers of length $n$,
- f is a natural number such that $0 \leq f < n$ holds,
- g is a natural number such that $f < g < n$ holds,
- u is a natural number such that $0 \leq u < n$ holds.
  This argument is optional.
The function returns a graphical representation of the array A as a heap.
This graphical representation is stored as a directed graph with an encoding suitable for graphviz.
The part A[0:g] is represented as a binary tree, while the part A[g:] is represented
as a list.  Furthermore, all indexes in the range A[k:g] satisfy the heap condition.  The nodes in the range [0:k-1]
are colored red.  If u is set, the node A[u] is colored orange.
Can you write Python code for it?

def toDot(A, f, g, u=None):
    n   = len(A)
    dot = gv.Digraph(node_attr={'shape': 'record'})
    for k, p in enumerate(A):
        if k == u:
            dot.node(str(k), label='{' + str(p) + '|' + str(k) + '}', style='filled', fillcolor='orange')
        elif k < f:
            dot.node(str(k), label='{' + str(p) + '|' + str(k) + '}', style='filled', fillcolor='red')
        elif k < g:
            dot.node(str(k), label='{' + str(p) + '|' + str(k) + '}', style='rounded')
        else:
            dot.node(str(k), label='{' + str(p) + '|' + str(k) + '}', style='filled', fillcolor='green')
    for k in range(0, n // 2):
        if 2 * k + 1 < g:
            dot.edge(str(k), str(2 * k + 1))
        if 2 * k + 2 < g:
            dot.edge(str(k), str(2 * k + 2))
    return dot


I am working on the file "Python/Chapter-08/Heapsort-Performance.ipynb".
The first task is:
Testing
Can you write Python code for it?

import random as rnd

def isOrdered(L):
    for i in range(len(L) - 1):
        assert L[i] >= L[i+1]

from collections import Counter

def sameElements(L, S):
    assert Counter(L) == Counter(S)


I am working on the file "Python/Chapter-08/Heapsort-Performance.ipynb".
The first task is:
The function $\texttt{testSort}(n, k)$ generates $n$ random lists of length $k$, sorts them, and checks whether the output is sorted and contains the same elements as the input.
Can you write Python code for it?

def testSort(n, k):
    for i in range(n):
        L = [ rnd.randrange(2*k) for x in range(k) ]
        oldL = L[:]
        heap_sort(L)
        isOrdered(L)
        sameElements(L, oldL)
        assert len(L) == len(oldL)
        print('.', end='')
    print()
    print("All tests successful!")

%%time
testSort(100, 20_000)


I am working on the file "Python/Chapter-08/Heapsort-Performance.ipynb".
The first task is:
The procedure sink takes three arguments.
- A is the array representing the heap.
- k is an index into the array A.
- n is the upper bound  of the part of this array that has to be transformed into a heap.
The array A itself might actually have more than $n+1$ elements, but for the
purpose of the method sink we restrict our attention to the subarray
A[k:n].
When calling sink, the assumption is that A[k:n+1] should represent a heap
that possibly has its heap condition violated at its root, i.e. at index k.  The
purpose of the procedure sink is to restore the heap condition at index k.
- We compute the index j of the left subtree below index k.
- We check whether there also is a right subtree at position j+1.
This is the case if j + 1 <= n.
- If the heap condition is violated at index k, we exchange the element at  position k
  with the child that has the higher priority, i.e. the child that is smaller.
- Next, we check in line 9 whether the heap condition is violated at index k.
  If the heap condition is satisfied, there is nothing left to do and the procedure returns.

Otherwise, the element at position k is swapped with
  the element at position j.

Of course, after this swap it is possible that the heap condition is
  violated at position j.  Therefore,  k is set to j and the while-loop continues
  as long as the node at position k has at least one child, i.e. as long as
  2 * k + 1 <= n.
Can you write Python code for it?

def sink(A, k, n):
    while 2 * k + 1 <= n:
        j = 2 * k + 1
        if j + 1 <= n and A[j] > A[j + 1]:
            j += 1
        if A[k] < A[j]:
            return
        swap(A, k, j)
        k = j


I am working on the file "Python/Chapter-08/Heapsort-Performance.ipynb".
The first task is:
The version of heap_sort given below adds some animation.
Can you write Python code for it?

def heap_sort(A):
    n = len(A) - 1
    for k in range((n + 1) // 2 - 1, -1, -1):
        sink(A, k, n)
    while n >= 1:
        swap(A, 0, n)
        n -= 1
        sink(A, 0, n)


I am working on the file "Python/Chapter-08/Heapsort-Performance.ipynb".
The first task is:
This notebook implements an array-based version of Heapsort.
Heapsort
The function call swap(A, i, j) takes an array A and  two indexes i and j and exchanges the elements at these indexes.
Can you write Python code for it?


I am working on the file "Python/Chapter-08/Heapsort.ipynb".
The first task is:
Heapsort
The function call swap(A, i, j) takes an array A and  two indexes i and j and exchanges the elements at these indexes.
Can you write Python code for it?


I am working on the file "Python/Chapter-08/Heapsort.ipynb".
The first task is:
The function toDot takes three arguments:
- A is an array of natural numbers of length $n$,
- f is a natural number such that $0 \leq f < n$ holds,
- g is a natural number such that $f < g < n$ holds.
The function returns a graphical representation of the array A as a heap.
This graphical representation is stored as a directed graph with an encoding suitable for graphviz.
The part A[0:g] is represented as a binary tree, while the part A[g:] is represented
as a list.  Furthermore, all indexes in the range A[k:g] satisfy the heap condition.
Can you write Python code for it?

def toDot(A, f, g):
    n   = len(A)
    dot = gv.Digraph(node_attr={'shape': 'record'})
    for k, p in enumerate(A):
        if k < f:
            dot.node(str(k), label='{' + str(p) + '|' + str(k) + '}', style='filled', fillcolor='red')
        elif k < g:
            dot.node(str(k), label='{' + str(p) + '|' + str(k) + '}', style='rounded')
        else:
            dot.node(str(k), label='{' + str(p) + '|' + str(k) + '}', style='filled', fillcolor='green')
    for k in range(0, n // 2):
        if 2 * k + 1 < g:
            dot.edge(str(k), str(2 * k + 1))
        if 2 * k + 2 < g:
            dot.edge(str(k), str(2 * k + 2))
    return dot


I am working on the file "Python/Chapter-08/Huffman.ipynb".
The first task is:
Building a Coding Tree
The module heapq provides priority queues.  The api is given at https://docs.python.org/3/library/heapq.html.
This module represents heaps as arrays.
Can you write Python code for it?

import heapq

H = []
heapq.heappush(H, 7)
heapq.heappush(H, 1)
heapq.heappush(H, 0)
heapq.heappush(H, 6)
H

a = heapq.heappop(H)
print('a = ', a)
H


I am working on the file "Python/Chapter-08/Huffman.ipynb".
The first task is:
Huffman's Algorithm for Lossless Data Compression
Can you write Python code for it?


I am working on the file "Python/Chapter-08/Huffman.ipynb".
The first task is:
The class Leaf represents a leaf of the form $\texttt{Leaf}(c, f)$.  It maintains two member variables.
  - $c$ represents the character that is encoded.  This character is stored in the member variable mCharacter.
  - $f$ represents the number of occurrences of the character $c$ in the string $s$ that is to be encoded and is stored in the member variable mFrequancy.
Can you write Python code for it?

class Leaf(CodingTree):
    def __init__(self, c, f):
        CodingTree.__init__(self)
        self.mCharacter = c
        self.mFrequency = f

    def count(self):
        return self.mFrequency

    def cost(self):
        return 0

    def __str__(self):
        return _make_string(self, ['mCharacter', 'mFrequency'])

    def __lt__(self, other):
        if isinstance(other, Node):
            return True
        return self.mCharacter < other.mCharacter


I am working on the file "Python/Chapter-08/Huffman.ipynb".
The first task is:
The function log2(n) computes $\log_2(n)$.
Can you write Python code for it?

def log2(n):
    return math.log(n) / math.log(2)

log2(8)

def demo(M):
    K = coding_tree(M)
    display(K.toDot())
    n = math.ceil(log2(len(M)))
    cost_huffman  = K.cost()
    cost_constant = n * K.count()
    savings       = (cost_constant - cost_huffman) / cost_constant
    print(f'cost of encoding with Huffman coding tree : {"{:,}".format(cost_huffman)} bits')
    print(f'cost of encoding with {n} bits              : {"{:,}".format(cost_constant)} bits')
    print(f'savings: {100 * savings}%')
    return savings

demo({ ('a', 990), ('b', 8), ('c', 1), ('d', 1) })

demo({ ('a', 4), ('b', 9), ('c', 16), ('d', 25), ('e', 36), ('f', 49), ('g', 64) })

demo({ ('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5), ('f', 6), ('g', 7), ('h', 8), ('i', 9), ('j', 10) })

demo({ ('a', 1), ('b', 1), ('c', 2), ('d', 3), ('e', 5), ('f', 8), ('g', 13) })


I am working on the file "Python/Chapter-08/Huffman.ipynb".
The first task is:
The function make_string is a helper function that is used to simplify the implementation of __str__.
  - self is the object that is to be rendered as a string
  - attributes is a list of those member variables that are used to produce the string
Can you write Python code for it?

def _make_string(self, Attributes):
        # map the function __str__ to all attributes and join them with a comma
        name = self.__class__.__name__
        return f"{name}({', '.join(map(str, [getattr(self, at) for at in Attributes]))})"

CodingTree._make_string = _make_string


I am working on the file "Python/Chapter-08/Huffman.ipynb".
The first task is:
The method $t.\texttt{collectIDs}(d)$ takes a coding tree $t$ and a dictionary $d$ and updates the dictionary so that the following holds:
$$ d[\texttt{id}] = n \quad \mbox{for every node $n$ in $t$.} $$
Here, $\texttt{id}$ is the unique identifier of the node $n$, i.e. $d$ associates the identifiers with the corresponding nodes.
Can you write Python code for it?

def _collectIDs(self, nodeDict):
    nodeDict[self.getID()] = self
    if isinstance(self, Node):
        self.mLeft ._collectIDs(nodeDict)
        self.mRight._collectIDs(nodeDict)

CodingTree._collectIDs = _collectIDs


I am working on the file "Python/Chapter-08/Huffman.ipynb".
The first task is:
The method $t.\texttt{toDot}()$ takes a binary trie $t$ and returns a graph that depicts the tree $t$.
Can you write Python code for it?

def toDot(self):
    dot = gv.Digraph(node_attr={'shape': 'record', 'style': 'rounded'})
    nodeDict = {}
    self._collectIDs(nodeDict)
    for n, t in nodeDict.items():
        if isinstance(t, Leaf):
            if t.mCharacter == ' ':
                dot.node(str(n), label='{ \' \' |' + "{:,}".format(t.mFrequency) + '}')
            elif t.mCharacter == '\t':
                dot.node(str(n), label='{ \'\\\\t\' |' + "{:,}".format(t.mFrequency) + '}')
            elif t.mCharacter == '\n':
                dot.node(str(n), label='{ \'\\\\n\' |' + "{:,}".format(t.mFrequency) + '}')
            elif t.mCharacter == '\v':
                dot.node(str(n), label='{ \'\\\\v\' |' + "{:,}".format(t.mFrequency) + '}')
            else:
                dot.node(str(n), label='{' + str(t.mCharacter) + '|' + "{:,}".format(t.mFrequency) + '}')
        elif isinstance(t, Node):
            dot.node(str(n), label="{:,}".format(t.count()))
        else:
            assert False, f'Unknown node {t}'
    for n, t in nodeDict.items():
        if isinstance(t, Node):
            dot.edge(str(n), str(t.mLeft .getID()), label='0')
            dot.edge(str(n), str(t.mRight.getID()), label='1')
    return dot

CodingTree.toDot = toDot


I am working on the file "Python/Chapter-09/Dijkstra-Heap.ipynb".
The first task is:
$\texttt{left_right}(S)$ describes a crossing of the tunnel from left to right.
Can you write Python code for it?

def left_right(S):
    return [(NodeDict[S - B], duration(B)) for B in power(S) if 'Torch' in B and 2 <= len(B) <= 3]


I am working on the file "Python/Chapter-09/Dijkstra-Heap.ipynb".
The first task is:
$\texttt{right_left}(S)$ describes a crossing of the tunnel from right to left.
Can you write Python code for it?

def right_left(S):
    return [(NodeDict[S | B], duration(B)) for B in power(All - S) if 'Torch' in B and 2 <= len(B) <= 3]

Edges = { Node(S): left_right(S) + right_left(S) for S in power(All) }
len(Edges)


I am working on the file "Python/Chapter-09/Dijkstra-Heap.ipynb".
The first task is:
Code for Testing
Can you write Python code for it?


I am working on the file "Python/Chapter-09/Dijkstra-Heap.ipynb".
The first task is:
Code to Display the Directed Graph
Can you write Python code for it?


I am working on the file "Python/Chapter-09/Dijkstra-Heap.ipynb".
The first task is:
Dijkstra's Shortest Path Algorithm
Can you write Python code for it?

%run Heap-Array.ipynb

def shortest_path(source, Edges):
    Distance = { source: 0 }
    Visited  = { source }  # this set is only needed for visualization
    Fringe   = []          # priority queue, organized as array based heap
    insert(Fringe, (0, source))
    while Fringe != []:
        display(heapToDot(Fringe))
        d, u = remove(Fringe)
        display(toDot(source, u, Edges, Fringe, Distance, Visited))
        print('_' * 80)
        for v, l in Edges[u]:
            dv = Distance.get(v, None)
            if dv == None or d + l < dv:
                if dv != None:
                    elevate(Fringe, v, d + l)
                else:
                    insert(Fringe, (d + l, v))
                Distance[v] = d + l
        Visited |= { u }
    display(toDot(source, None, Edges, Fringe, Distance, Visited))
    return Distance


I am working on the file "Python/Chapter-09/Dijkstra-Heap.ipynb".
The first task is:
Given to nodes source and goal and a dictionary containing the parent of every node, the function
find_path returns the path from source to goal.
Can you write Python code for it?

def find_path(source, goal, Parent):
    p = Parent.get(goal)
    if p == None:
        return [source]
    return find_path(source, p, Parent) + [goal]

Path = [x.mValue for x in find_path(NodeDict[frozenset(All)], NodeDict[frozenset()], Parent)]

def print_path():
    total = 0
    print("_" * 81);
    for i in range(len(Path)):
        Left  = set(Path[i])
        Right = set(All) - set(Left)
        if Left == set() or Right == set():
            print(Left, " " * 25, Right)
        else:
            print(Left, " " * 30, Right)
        print("_" * 81);
        if i < len(Path) - 1:
            if "Torch" in Path[i]:
                Diff = set(Path[i]) - set(Path[i+1])
                time = duration(Diff)
                total += time
                print(" " * 20, ">>> ", Diff, ':', time, " >>>")
            else:
                Diff = set(Path[i+1]) - set(Path[i])
                time = duration(Diff)
                total += time
                print(" " * 20, "<<< ", Diff, ':', time, " <<<")
            print("_" * 81)
    print('Total time:', total, 'minutes.')

print_path()


I am working on the file "Python/Chapter-09/Dijkstra-Heap.ipynb".
The first task is:
Let us see whether the goal was reachable and how long it takes to reach the goal.
Can you write Python code for it?

goal = NodeDict[frozenset()]
Distance[goal]


I am working on the file "Python/Chapter-09/Dijkstra-Heap.ipynb".
The first task is:
The function $\texttt{power}(M)$ defined below computes the power list of the set $M$, i.e. we have:
$$ \texttt{power}(M) = 2^M = \bigl{A \mid A \subseteq M \bigr} $$
Can you write Python code for it?

def power(M):
    if M == set():
        return { frozenset() }
    else:
        C  = set(M)  # C is a copy of M as we don't want to change the set M
        x  = C.pop() # pop removes the element x from the set C
        P1 = power(C)
        P2 = { A | {x} for A in P1 }
        return P1 | P2


I am working on the file "Python/Chapter-09/Dijkstra-Heap.ipynb".
The first task is:
The function shortest_path is Dikstra's algorithm.  It returns both a dictionary containing the parents and a dictionary
with the distances.
Can you write Python code for it?

def shortest_path(source, Edges):
    Distance = { source: 0 }
    Parent   = {}
    Fringe   = []
    insert(Fringe, (0, source) )
    while Fringe != []:
        d, u = remove(Fringe)
        for v, l in Edges[u]:
            dv = Distance.get(v, None)
            if dv == None or d + l < dv:
                if dv != None:
                    elevate(Fringe, v, d + l)
                else:
                    insert(Fringe, (d + l, v))
                Distance[v] = d + l
                Parent[v] = u
    return Parent, Distance

start = Node(frozenset(All))

Parent, Distance = shortest_path(start, Edges)


I am working on the file "Python/Chapter-09/Dijkstra.ipynb".
The first task is:
$\texttt{left_right}(S)$ describes a crossing of the tunnel from the entrance at the left side left to the exit at the right side of the tunnel.
Can you write Python code for it?

def left_right(S):
    return [(S - B, duration(B)) for B in power(S) if 'Torch' in B and 2 <= len(B) <= 3]


I am working on the file "Python/Chapter-09/Dijkstra.ipynb".
The first task is:
$\texttt{right_left}(S)$ describes a crossing of the tunnel from right to left.
Can you write Python code for it?

def right_left(S):
    return [(S | B, duration(B)) for B in power(All - S) if 'Torch' in B and 2 <= len(B) <= 3]

Edges = { S: left_right(S) + right_left(S) for S in power(All) }
len(Edges)


I am working on the file "Python/Chapter-09/Dijkstra.ipynb".
The first task is:
Crossing the Tunnel
Four persons, Alice, Britney, Charly and Daniel have to cross a tunnel.
The tunnel is so narrow, that at most two persons can cross it together.
In order to cross the tunnel, a torch is needed.  Together, they only
have a single torch.
  1. Alice   is the fastest and can cross the tunnel in 1 minute.
  2. Britney needs 2 minutes to cross the tunnel.
  3. Charly  is slower and needs 4 minutes.
  4. Daniel  is slowest and takes 5 minutes to cross the tunnel.
What is the fastest plan to cross the tunnel?
We will model this problem as a graph theoretical problem.  The nodes of the graph will be sets
of people.  In particular, it will be the set of people at the entrance of the tunnel. In order to model the torch, the torch can also be a member of these sets.
Can you write Python code for it?

All  = frozenset({ 'Alice', 'Britney', 'Charly', 'Daniel', 'Torch' })


I am working on the file "Python/Chapter-09/Dijkstra.ipynb".
The first task is:
Dijkstra's Shortest Path Algorithm
The notebook Set.ipynb implements sets as
AVL trees.
The API provided by Set offers the following API:
- Set() creates an empty set.
- S.isEmpty() checks whether the set Sis empty.
- S.member(x) checks whether x is an element of the given set S.
- S.insert(x) inserts x into the set S.
   This does not return a new set but rather modifies the given set S.
- S.delete(x) deletes x from the set S.
   This does not return a new set but rather modifies the set S.
- S.pop() returns the smallest element of the set S.
   Furthermore, this element is removed from the given set S.
Since sets are implemented as ordered binary trees, the elements of a set need to be comparable, i.e. if
x and y are inserted into a set, then the expression x < y has to be defined and has to return a
Boolean value.  Furthermore, the relation < has to be a
linear order.
The class Set can be used to implement a priority queue that supports the
removal of elements.
Can you write Python code for it?


I am working on the file "Python/Chapter-09/Dijkstra.ipynb".
The first task is:
Given to nodes source and goal and a dictionary containing the parent of every node, the function
find_path returns the path from source to goal.
Can you write Python code for it?

def find_path(source, goal, Parent):
    p = Parent.get(goal)
    if p == None:
        return [source]
    return find_path(source, p, Parent) + [goal]

Path = find_path(frozenset(All), frozenset(), Parent)

def print_path():
    total = 0
    print("_" * 81);
    for i in range(len(Path)):
        Left  = set(Path[i])
        Right = set(All) - set(Left)
        if Left == set() or Right == set():
            print(Left, " " * 25, Right)
        else:
            print(Left, " " * 30, Right)
        print("_" * 81);
        if i < len(Path) - 1:
            if "Torch" in Path[i]:
                Diff = set(Path[i]) - set(Path[i+1])
                time = duration(Diff)
                total += time
                print(" " * 20, ">>> ", Diff, ':', time, " >>>")
            else:
                Diff = set(Path[i+1]) - set(Path[i])
                time = duration(Diff)
                total += time
                print(" " * 20, "<<< ", Diff, ':', time, " <<<")
            print("_" * 81)
    print('Total time:', total, 'minutes.')

print_path()


I am working on the file "Python/Chapter-09/Dijkstra.ipynb".
The first task is:
Let us see whether the goal was reachable and how long it takes to reach the goal.
Can you write Python code for it?


I am working on the file "Python/Chapter-09/Dijkstra.ipynb".
The first task is:
The function $\texttt{power}(M)$ defined below computes the power list of the set $M$, i.e. we have:
$$ \texttt{power}(M) = 2^M = \bigl{A \mid A \subseteq M \bigr} $$
Can you write Python code for it?


I am working on the file "Python/Chapter-09/Dijkstra.ipynb".
The first task is:
The function call shortest_path takes a node source and a set Edges.
The function shortest_path takes two arguments.
- source is the start node.
- Edges is a dictionary that encodes the set of edges of the graph.  For every node x the value of Edges[x] has the form
   $$ \bigl[ (y_1, l_1), \cdots, (y_n, l_n) \bigr]. $$
   This list is interpreted as follows: For every $i = 1,\cdots,n$ there is an edge
   $(x, y_i)$ pointing from $x$ to $y_i$ and this edge has the length $l_i$.
The function returns the dictionary Distance.  For every node u such that there is a path from source to
u, Distance[u] is the length of the shortest path from source to u.  The implementation uses
Dijkstra's algorithm and proceeds as follows:

Distance is a dictionary mapping nodes to their estimated distance from the node
  source.  If d = Distance[x], then we know that there is a path of length d leading
  from source to x.  However, in general we do not know whether there is a path shorter
  than d that also connects the source to the node x.
The function shortest_path maintains an additional variable called Visited.
  This variable contains the set of those nodes that have been  visited
  by the algorithm.
  To be more precise, Visited contains those nodes u that have been removed from the
  Fringe and for which all neighboring nodes, i.e. those nodes y such that
  there is an edge (u,y), have been examined.  It can be shown that once a node u is added to
  Visited, Distance[u] is the length of the shortest path from source to u.
Fringe is a priority queue that contains pairs of the form (d, x), where x is a node and d
  is the distance that x has from the node source.  This priority queue is implemented as a set,
  which in turn is represented by an ordered binary tree.  The fact that we store the node x and the
  distance d as a pair (d,x) implies that the distances are used as priorities because pairs are
  compared...
Can you write Python code for it?

def shortest_path(source, Edges):
    Distance = { source: 0 }
    Visited  = { source }
    Fringe   = Set()
    Fringe.insert( (0, source) )
    while not Fringe.isEmpty():
        d, u = Fringe.pop() # get and remove smallest element
        for v, l in Edges[u]:
            dv = Distance.get(v, None)
            if dv == None or d + l < dv:
                if dv != None:
                    Fringe.delete( (dv, v) )
                Distance[v] = d + l
                Fringe.insert( (d + l, v) )
        Visited.add(u)
    return Distance


I am working on the file "Python/Chapter-09/Dijkstra.ipynb".
The first task is:
The function shortest_path is Dijkstra's algorithm.  It returns both a dictionary Parent containing
the parent nodes and a dictionary Distance with the distances.  The dictionary Parent can be used to
compute the shortest path leading from the node source to some other node.
Can you write Python code for it?

def shortest_path(source, Edges):
    Distance = { source: 0 }
    Parent   = {}
    Fringe   = Set()
    Fringe.insert( (0, source) )
    while not Fringe.isEmpty():
        d, u = Fringe.pop()
        for v, l in Edges[u]:
            dv = Distance.get(v, None)
            if dv == None or d + l < dv:
                if dv != None:
                    Fringe.delete( (dv, v) )
                Distance[v] = d + l
                Fringe.insert( (d + l, v) )
                Parent[v] = u
    return Parent, Distance

Parent, Distance = shortest_path(frozenset(All), Edges)


I am working on the file "Python/Chapter-09/Dijkstra.ipynb".
The first task is:
The timining is modelled by a dictionary.
Can you write Python code for it?

Time = { 'Alice': 1, 'Britney': 2, 'Charly': 4, 'Daniel': 5, 'Torch': 0 }


I am working on the file "Python/Chapter-09/Heap-Array.ipynb".
The first task is:
Implementing the API
The function insert(H, x) takes two arguments:
- H is a heap that is represented as an array.
- x is a pair of the form (p, o) where
  - p is a natural number interpreted as a priority.  The smaller the number, the higher the priority.
  - o is an object.
This method inserts the pair x into the heap H.  Furthermore, the object o is modified so that it remembers
the index at which it is stored in H.  This is done by storing this index in o.mIndex.
Can you write Python code for it?

def insert(H, x):
    n = len(H)
    H.append(x)
    _, o = x
    o.mIndex = n
    k = ascend(H, n)
    o.mIndex = k


I am working on the file "Python/Chapter-09/Heap-Array.ipynb".
The first task is:
Testing
Can you write Python code for it?

class Node:
    def __init__(self, value):
        self.mValue = value

    def __hash__(self):
        return hash(self.mValue)

    def __repr__(self):
        try:
            return 'Node(' + str(self.mValue) + ':' + str(self.mIndex) + ')'
        except AttributeError:
            return 'Node(' + str(self.mValue) + ')'

    def __str__(self):
        return str(self.mValue)

    def __eq__(self, other):
        if other == None:
            return False
        return self.mValue == other.mValue

    def __lt__(self, other):
        return self.mValue < other.mValue

import string

def demo1():
    L = [ (i, Node(c)) for i, c in enumerate(string.ascii_lowercase) ]
    _, w = L[22]
    H = []
    for i, x in enumerate(L):
        insert(H, x)
    display(heapToDot(H))
    print('Elevating "w" to priority 2:')
    elevate(H, w, 2)
    display(heapToDot(H))

demo1()

import random as rnd

def heap_sort(L):
    H = []
    for x in L:
        insert(H, x)
    display(heapToDot(H))
    S = []
    while H != []:
        p, _ = remove(H)
        display(heapToDot(H))
        S.append(p)
    return S

def demo2():
    L = [ rnd.randrange(1, 200) for n in range(12) ]
    L = [ (n, Node(n)) for n in L ]
    print("L = ", L)
    S = heap_sort(L)
    print("S = ", S)

demo2()


I am working on the file "Python/Chapter-09/Heap-Array.ipynb".
The first task is:
The function elevate(H, o, p) takes three arguments.
- H is a list that is organized as a heap.
- o is an object that occurs in the heap H at index o.mIndex, i.e. we have H[o.mIndex] = p_old, o.mIndex,
  where p_old is the priority that was used when o was stored in H.
- p is the new priority of o in H.  This priority must be higher than the priority p_old, i.e. we must have p < p_old.
The function call elevate(H, o, p) elevates the priority of the object o to p in H and takes care that o is stored further up in H
so that the heap property of H is maintained.
Can you write Python code for it?

def elevate(H, o, p):
    k = o.mIndex
    H[k] = (p, o)
    ascend(H, k)

def remove(H):
    pFirst, oFirst = H[0]
    pLast , oLast  = H[-1]
    oLast.mIndex   = 0
    H[0]           = pLast, oLast
    H.pop(-1)
    descend(H)
    return pFirst, oFirst


I am working on the file "Python/Chapter-09/Heap-Array.ipynb".
The first task is:
The function toDot takes four arguments:
- A is an array of natural numbers of length $n$,
- f is a natural number such that $0 \leq f < n$ holds,
- g is a natural number such that $f < g < n$ holds,
- u is a natural number such that $0 \leq u < n$ holds.
  This argument is optional.
The function returns a graphical representation of the array A as a heap.
This graphical representation is stored as a directed graph with an encoding suitable for graphviz.
The part A[0:g] is represented as a binary tree, while the part A[g:] is represented
as a list.  Furthermore, all indexes in the range A[k:g] satisfy the heap condition.  The nodes in the range [0:k-1]
are colored red.  If u is set, the node A[u] is colored orange.
Can you write Python code for it?

def heapToDot(A):
    n   = len(A)
    dot = gv.Digraph(node_attr={'shape': 'record'})
    for k, (p, o) in enumerate(A):
        if str(p) != str(o):
            dot.node(str(k), label='{' + str(p) + '|' + str(o) + '|' + str(o.mIndex) + '|' + str(k) + '}', style='rounded')
        else:
            dot.node(str(k), label='{' + str(p) + '|' + str(k) + '}', style='rounded')
    for k in range(0, n // 2):
        if 2 * k + 1 < n:
            dot.edge(str(k), str(2 * k + 1))
        if 2 * k + 2 < n:
            dot.edge(str(k), str(2 * k + 2))
    return dot


I am working on the file "Python/Chapter-09/Huffman.ipynb".
The first task is:
Building a Coding Tree
The module heapq provides priority queues.  The api is given at https://docs.python.org/3/library/heapq.html.
We will use two methods:
* heapq.heappush(H, p)
  pushes the priority p onto the heap.  For this to be possible, p has to be an object of a class that provides the
  method __lt__.  The expression p.__lt__(q) is True if p is smaller than q.
* heapq.heappop(H)
  removes and returns the element from the heap H that has the highest priority, i.e. the smallest element.
The implementation of this module represents heaps as arrays.  Therefore,
* to test whether a heap H is empty we can write H == [] since [] is the empty heap.
* to create an empty heap we write H = [],
* to get the element with the highest priority in the heap H we write H[0],
* to get the number of elements in H we can write len(H).
Can you write Python code for it?

import heapq

H = []
heapq.heappush(H, 7)
heapq.heappush(H, 1)
heapq.heappush(H, 0)
heapq.heappush(H, 6)
H

a = heapq.heappop(H)
print('a = ', a)
H


I am working on the file "Python/Chapter-09/Huffman.ipynb".
The first task is:
The function coding_tree implements Huffman's algorithm for data compression.
The input $M$ is a set of pairs of the form
$$ \bigl{ (c_1, f_1), \cdots, (c_k, f_k)\bigr} $$
where $c_i$ is a character and $f_i$ is the number of times this character occurs in the string $s$ that is to be encoded.  Huffman's algorithm is
greedy:  It always combines those coding trees that have the least character count so far as this results in the smallest cost increase.
The heap H that is maintained by this function is a priority queue which is represented by an array that is structured as a heap.  The items in this priority queue are pairs of the form
$$ \bigl( t.\texttt{count}(), t \bigr)  $$
where $t$ is a coding tree and $t.\texttt{count}()$ is the count of this coding tree.
Can you write Python code for it?

def coding_tree(M):
    H = []  # empty priority queue
    for c, f in M:
        heapq.heappush(H, (f, Leaf(c, f)))
    while len(H) > 1:
        ac, a = heapq.heappop(H)
        bc, b = heapq.heappop(H)
        heapq.heappush(H, (ac + bc, Node(a, b)))
    return H[0][1]


I am working on the file "Python/Chapter-09/Huffman.ipynb".
The first task is:
The function demo(M)takes one argument M that is a set of pairs of the form
$$ M = \bigl{ \langle c_1, f_1 \rangle, \cdots,  \langle c_n, f_n \rangle \bigr} $$
Here, $c_1$, $\cdots$, $c_n$ are characters and $f_1, \cdots, f_n$ are the frequencies with which these
characters occur in a given string that is to be encoded.
The function builds the Huffman tree that is optimal for these characters. Additionally the function prints the
percentage of memory savings that can be achieved with a Huffman tree.  This number is also returned.
Can you write Python code for it?

def demo(M):
    K = coding_tree(M)
    display(K.toDot())
    n = math.ceil(log2(len(M)))
    cost_huffman  = K.cost()
    cost_constant = n * K.count()
    savings       = (cost_constant - cost_huffman) / cost_constant
    print(f'cost of encoding with Huffman coding tree : {"{:,}".format(cost_huffman)} bits')
    print(f'cost of encoding with {n} bits              : {"{:,}".format(cost_constant)} bits')
    print(f'savings: {100 * savings}%')
    return savings

demo({ ('a', 990), ('b', 8), ('c', 1), ('d', 1) })

demo({ ('a', 4), ('b', 9), ('c', 16), ('d', 25), ('e', 36), ('f', 49), ('g', 64) })

demo({ ('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5), ('f', 6), ('g', 7), ('h', 8), ('i', 9), ('j', 10) })

demo({ ('a', 1), ('b', 1), ('c', 2), ('d', 3), ('e', 5), ('f', 8), ('g', 13) })


I am working on the file "Python/Chapter-09/Huffman.ipynb".
The first task is:
The function make_string is a helper function that is used to simplify the implementation of __str__.
  - self is the object that is to be rendered as a string
  - attributes is a list of those member variables that are used to produce the string
Can you write Python code for it?

def _make_string(self, Attributes):
        # map the function __str__ to all attributes and join them with a comma
        name = self.__class__.__name__
        return f"{name}({', '.join(map(str, [getattr(self, at) for at in Attributes]))})"

CodingTree._make_string = _make_string


I am working on the file "Python/Chapter-09/Huffman.ipynb".
The first task is:
The method $t.\texttt{collectIDs}(d)$ takes a coding tree $t$ and a dictionary $d$ and updates the dictionary so that the following holds:
$$ d[\texttt{id}] = n \quad \mbox{for every node $n$ in $t$.} $$
Here, $\texttt{id}$ is the unique identifier of the node $n$, i.e. $d$ associates the identifiers with the corresponding nodes.
Can you write Python code for it?

def _collectIDs(self, nodeDict):
    nodeDict[self.getID()] = self
    if isinstance(self, Node):
        self.mLeft ._collectIDs(nodeDict)
        self.mRight._collectIDs(nodeDict)

CodingTree._collectIDs = _collectIDs


I am working on the file "Python/Chapter-09/Huffman.ipynb".
The first task is:
The method $t.\texttt{toDot}()$ takes a binary trie $t$ and returns a graph that depicts the tree $t$.
Can you write Python code for it?

def toDot(self):
    dot = gv.Digraph(node_attr={'shape': 'record', 'style': 'rounded'})
    nodeDict = {}
    self._collectIDs(nodeDict)
    for n, t in nodeDict.items():
        if isinstance(t, Leaf):
            if t.mCharacter == ' ':
                dot.node(str(n), label='{ \' \' |' + "{:,}".format(t.mFrequency) + '}')
            elif t.mCharacter == '\t':
                dot.node(str(n), label='{ \'\\\\t\' |' + "{:,}".format(t.mFrequency) + '}')
            elif t.mCharacter == '\n':
                dot.node(str(n), label='{ \'\\\\n\' |' + "{:,}".format(t.mFrequency) + '}')
            elif t.mCharacter == '\v':
                dot.node(str(n), label='{ \'\\\\v\' |' + "{:,}".format(t.mFrequency) + '}')
            else:
                dot.node(str(n), label='{' + str(t.mCharacter) + '|' + "{:,}".format(t.mFrequency) + '}')
        elif isinstance(t, Node):
            dot.node(str(n), label="{:,}".format(t.count()))
        else:
            assert False, f'Unknown node {t}'
    for n, t in nodeDict.items():
        if isinstance(t, Node):
            dot.edge(str(n), str(t.mLeft .getID()), label='0')
            dot.edge(str(n), str(t.mRight.getID()), label='1')
    return dot

CodingTree.toDot = toDot


I am working on the file "Python/Chapter-09/Huffman.ipynb".
The first task is:
This notebook presents coding trees.  Given an alphabet $\Sigma$ of characters,
we define the set $\mathcal{K}$ of coding trees by induction:

$\texttt{Leaf}(c,f) \in \mathcal{K} $ if $c \in \Sigma$ and $f \in \mathbb{N}$
An expression of the form $\texttt{Leaf}(c,f)$ represent a leaf in a coding tree.
 $c$ is a letter from the alphabet $\Sigma$ and
* $f$ is the frequency* of the letter $c$ in the string $s$ that is to be encoded.

$\texttt{Node}(l,r) \in \mathcal{K}$ if $l \in\mathcal{K}$ and $r \in \mathcal{K}$
The expressions $\texttt{Node}(l,r)$ represent the inner nodes of the coding-tree.

The class CodingTree is a superclass for constructing coding trees.  It has one static variable sNodeCount.  This variable is used to equip all nodes with a unique identifier.  This identifier is used to draw the trees using graphviz.
Every object of class CodingTree has a uniques identifier mID that is stored as a member variable.  This identifier is only used by graphviz.
Can you write Python code for it?

class CodingTree:
    sNodeCount = 0

    def __init__(self):
        CodingTree.sNodeCount += 1
        self.mID = CodingTree.sNodeCount

    def count(self):
        "compute the number of characters"
        pass

    def cost(self):
        "compute the number of bits used by this coding tree"
        pass

    def getID(self):
        return self.mID  # used only by graphviz


I am working on the file "Python/Chapter-09/Kruskal.ipynb".
The first task is:
Furthermore, we need a priority queue. The module heapq implements a priority queue.  The part
of the API from this module that we utilize is the following:
- H.heappush(x) pushes x onto the heap H,
- H.heappop()   removes the smallest element from the heap H and returns this element,
- H = []        creates an empty heap.
Can you write Python code for it?


I am working on the file "Python/Chapter-09/Kruskal.ipynb".
The first task is:
Kruskal's Algorithm for Computing the Minimum Spanning Tree
In our implementation of Kruskal's algorithm for finding the
minimum spanning tree we use the union-find data structure that we have defined previously.
Can you write Python code for it?


I am working on the file "Python/Chapter-09/Kruskal.ipynb".
The first task is:
The function $\texttt{mst}(V, E)$ takes a set of nodes $V$ and a set of weighted edges $E$ to compute a minimum spanning tree.  It is assumed that the pair $(V, E)$ represents a weighted graph $G$ that is connected.  The weighted edges in the set $E$ have the form
$$ \bigl\langle w, \langle x, y\rangle\bigr\rangle.  $$
Here, $x$ and $y$ are nodes from the set $V$, while $w$ is the cost of the edge ${x,y}$.
The function call mst(V, E) returns a set of weighted edges that define a minimum spanning tree
of the weighted graph $G$. The function mst does not check whether $G$ is connected.
Can you write Python code for it?

def mst(V, E):
    UF  = UnionFind(V)
    MST = set()         # minimum spanning tree, represented as set of weighted edges
    H   = []            # empty priority queue for weighted edges
    for edge in E:
        hq.heappush(H, edge)
    while True:
        w, (x, y) = hq.heappop(H)
        root_x = UF.find(x)
        root_y = UF.find(y)
        if root_x != root_y:
            MST.add((w, (x, y)))
            UF.union(x, y)
            if len(MST) == len(V) - 1:
                return MST


I am working on the file "Python/Chapter-09/Kruskal.ipynb".
The first task is:
The function demoFile(fn) takes a filename fn as its argument. The corresponding file is expected to hold the description of
an undirected weighted graph.  The function computes the minimum spanning tree for this graph.
Can you write Python code for it?

def demoFile(fn):
    with open(fn, 'r') as file:
        data = file.readlines()
    Edges = set()
    Nodes = set()
    for line in data:
        x, y, weight = line.split()
        x, y, weight = int(x), int(y), int(weight)
        Edges.add((weight, (x, y)))
        Nodes.add(x)
        Nodes.add(y)
    MST = mst(Nodes, Edges);
    print(MST)
    return toDot(Edges, MST)

MST = demoFile('tiny.txt')
MST


I am working on the file "Python/Chapter-09/Kruskal.ipynb".
The first task is:
The implementation of mst that is given below traces its computation via graphviz.
Can you write Python code for it?

def mst(V, E):
    UF  = UnionFind(V)
    MST = set()         # minimum spanning tree, represented as set of weighted edges
    H   = []            # empty priority queue for weighted edges
    for edge in E:
        hq.heappush(H, edge)
    while True:
        w, (x, y) = hq.heappop(H)
        print(f'testing {x} - {y}, weight {w}')
        root_x = UF.find(x)
        root_y = UF.find(y)
        if root_x != root_y:
            print(f'connect {x} - {y}')
            MST.add((w, (x, y)))
            UF.union(x, y)
            display(toDot(E, MST))
            print('_' * 120)
            if len(MST) == len(V) - 1:
                return MST

import graphviz as gv


I am working on the file "Python/Chapter-09/Moore.ipynb".
The first task is:
Code for Testing
Can you write Python code for it?


I am working on the file "Python/Chapter-09/Moore.ipynb".
The first task is:
Code to Display the Directed Graph
Can you write Python code for it?


I am working on the file "Python/Chapter-09/Moore.ipynb".
The first task is:
The function $\texttt{toDot}(\texttt{source}, \texttt{Edges}, \texttt{Fringe}, \texttt{Distance})$ takes a graph that is represented by
its Edges, a set of nodes Fringe, and a dictionary Distance that has the distance of a node from the node source.
Can you write Python code for it?

def toDot(source, p, Edges, Fringe, Distance):
    V = set()
    for x in Edges.keys():
        V.add(x)
    dot = gv.Digraph(node_attr={'shape': 'record', 'style': 'rounded'})
    dot.attr(rankdir='LR', size='8,5')
    for x in V:
        if x == source:
            if x != p and x in Fringe:
                dot.node(str(x), color='red', shape='doublecircle')
            elif x == p:
                dot.node(str(x), color='magenta', shape='doublecircle')
            else:
                dot.node(str(x), color='blue', shape='doublecircle')
        else:
            d = str(Distance.get(x, ''))
            if x == p:
                dot.node(str(x), label='{' + str(x) + '|' + d + '}', color='magenta')
            elif x in Fringe:
                dot.node(str(x), label='{' + str(x) + '|' + d + '}', color='red')
            else:
                dot.node(str(x), label='{' + str(x) + '|' + d + '}')
    for u in V:
        for v, l in Edges[u]:
            dot.edge(str(u), str(v), label=str(l))
    return dot


I am working on the file "Python/Chapter-09/Moore.ipynb".
The first task is:
The version of shortest_path that is given below adds some animation to the algorithm.
Can you write Python code for it?

def shortest_path(source, Edges):
    Distance = { source: 0 }
    Fringe   = [ source ]
    while len(Fringe) > 0:
        display(toDot(source, None, Edges, Fringe, Distance))
        print('_' * 80)
        u = Fringe.pop()
        display(toDot(source, u, Edges, Fringe, Distance))
        print('_' * 80)
        for v, l in Edges[u]:
            dv = Distance.get(v, None)
            if dv == None or Distance[u] + l < dv:
                Distance[v] = Distance[u] + l
                if v not in Fringe:
                    Fringe += [v]
    display(toDot(source, None, Edges, Fringe, Distance))
    return Distance


I am working on the file "Python/Chapter-09/Topological-Sorting.ipynb".
The first task is:
Graphical Representation
Can you write Python code for it?


I am working on the file "Python/Chapter-09/Topological-Sorting.ipynb".
The first task is:
Testing
Can you write Python code for it?


I am working on the file "Python/Chapter-09/Topological-Sorting.ipynb".
The first task is:
The function toDot(Edges, Order) takes two arguments:
- Edges is a set of pairs of the form (x, y) where x and y are nodes of a graph
  and (x, y) is a directed edge from xto y.
- Order is a dictionary assigning natural numbers to some of the nodes.
The set of edges is displayed as a directed graph and for those nodes x such that Order[x] is defined, both x and the label Order[x] is depicted.
Can you write Python code for it?

def toDot(Edges, Order={}):
    V = set()
    for x, y in Edges:
        V.add(x)
        V.add(y)
    dot = gv.Digraph(node_attr={'shape': 'record', 'style': 'rounded'})
    dot.attr(rankdir='LR', size='8,5')
    for x in V:
        o = Order.get(x, None)
        if o != None:
            dot.node(str(x), label='{' + str(x) + '|' + str(o) + '}')
        else:
            dot.node(str(x))
    for u, v in Edges:
        dot.edge(str(u), str(v))
    return dot


I am working on the file "Python/Chapter-09/Topological-Sorting.ipynb".
The first task is:
Topological Sorting
The function topo_sort implements Kahn's algorithm for
topological sorting.
- The first argument T is the set of the nodes of the directed graph.
- The second argument D is the set of the edges. Edges are represented as pairs.
The function returns a list Sorted that contains all nodes of $T$.  If
Sorted[i] = x, Sorted[j] = y, and (x, y) in D,
then we must have i < j.
Can you write Python code for it?

def topo_sort(T, D):
    Parents  = { t: set() for t in T }  # dictionary of parents
    Children = { t: set() for t in T }  # dictionary of children
    for s, t in D:
        Children[s].add(t)
        Parents [t].add(s)
    Orphans = { t for (t, P) in Parents.items() if len(P) == 0 }
    Sorted  = []
    count   = 0
    Order   = {}
    while len(T) > 0:
        assert Orphans != set(), 'The graph is cyclic!'
        t        = Orphans.pop()
        Order[t] = count
        count   += 1
        Orphans -= { t }
        T       -= { t }
        Sorted.append(t)
        for s in Children[t]:
            Parents[s] -= { t }
            if Parents[s] == set():
                Orphans.add(s)
    return Sorted

def topo_sort(T, D):
    print('_' * 100)
    display(toDot(D))
    Parents  = { t: set() for t in T }  # dictionary of parents
    Children = { t: set() for t in T }  # dictionary of children
    for s, t in D:
        Children[s].add(t)
        Parents [t].add(s)
    Orphans = { t for (t, P) in Parents.items() if len(P) == 0 }
    Sorted  = []
    count   = 0
    Order   = {}
    while len(T) > 0:
        assert Orphans != set(), 'The graph is cyclic!'
        t        = Orphans.pop()
        Order[t] = count
        count   += 1
        Orphans -= { t }
        T       -= { t }
        Sorted.append(t)
        for s in Children[t]:
            Parents[s] -= { t }
            if Parents[s] == set():
                Orphans.add(s)
        print('_' * 80)
        display(toDot(D, Order))
    return Sorted


I am working on the file "Python/Chapter-09/Union-Find-Naive.ipynb".
The first task is:
Given a partition $\mathcal{P}$ of a set $M$ and an element $x$ from $M$, the function $\texttt{find}(x, \mathcal{P})$
returns the set $S \in \mathcal{P}$ such that $x \in S$.
Can you write Python code for it?


I am working on the file "Python/Chapter-09/Union-Find-Naive.ipynb".
The first task is:
The function $\texttt{arb}(S)$ returns an arbitrary element from the set $S$,
Can you write Python code for it?

def arb(S):
    for x in S:
        return x


I am working on the file "Python/Chapter-09/Union-Find-OO.ipynb".
The first task is:
Given an element $x$ from the set $M$, the function $\texttt{self}.\texttt{find}(x)$
returns the ancestor of $x$ that is at the root of the tree containing $x$.
Can you write Python code for it?

def find(self, x):
    p = self.mParent[x]
    if p == x:
        return x
    return self.find(p)

UnionFind.find = find
del find


I am working on the file "Python/Chapter-09/Union-Find-OO.ipynb".
The first task is:
Given two elements $x$ and $y$ and an object $o$ of type UnionFind, the call $o.\texttt{union}(x, y)$ changes the unionFind object $o$ so that afterwards the equation
$$ o.\texttt{find}(x) = o.\texttt{find}(y) $$
holds.
Can you write Python code for it?

def union(self, x, y):
    root_x = self.find(x)
    root_y = self.find(y)
    if root_x != root_y:
        if self.mHeight[root_x] < self.mHeight[root_y]:
            self.mParent[root_x]  = root_y
        elif self.mHeight[root_x] > self.mHeight[root_y]:
            self.mParent[root_y]  = root_x
        else:
            self.mParent[root_y]  = root_x
            self.mHeight[root_x] += 1

UnionFind.union = union

def partition(M, R):
    UF = UnionFind(M)
    for x, y in R:
        UF.union(x, y)
    Roots = { x for x in M if UF.find(x) == x }
    return [{y for y in M if UF.find(y) == r} for r in Roots]

def demo():
    M = set(range(1, 10))
    R = { (1, 4), (7, 9), (3, 5), (2, 6), (5, 8), (1, 9), (4, 7) }
    P = partition(M, R)
    return P

P = demo()
P


I am working on the file "Python/Chapter-09/Union-Find-Tree.ipynb".
The first task is:
Given a dictionary Parent and an element $x$ from $M$, the function $\texttt{find}(x, \texttt{Parent})$
returns the ancestor of $x$ that is its own parent.
Can you write Python code for it?

def find(x, Parent):
    p = Parent[x]
    if p == x:
        return x
    return find(p, Parent)

def demo():
    M = set(range(1, 10))
    R = { (1, 4), (7, 9), (3, 5), (2, 6), (5, 8), (1, 9), (4, 7) }
    P = union_find(M, R)
    return P

demo()

def worst_case(n):
    M = set(range(1, n+1))
    R = [ (k+1, k) for k in M if k < n ]
    print(f'R = {R}')
    P = union_find(M, R)
    print(f'P = {P}')

worst_case(10)


I am working on the file "Python/Chapter-09/Union-Find-Tree.ipynb".
The first task is:
The function $\texttt{toDot}(\texttt{Parent})$ takes a dictionary $\texttt{Parent}$.
For every node $x$, $\texttt{Parent}[x]$ is the parent of $x$.   It draws this dictionary
as a family tree using graphviz.
Can you write Python code for it?

def toDot(Parent):
    dot = gv.Digraph()
    M   = Parent.keys()
    for x in M:
        p = Parent[x]
        dot.node(str(x))
        if x == p:
            dot.edge(str(p), str(x), dir='back', headport='nw', tailport='ne')
        else:
            dot.edge(str(p), str(x), dir='back')
    return dot


I am working on the file "Python/Chapter-09/Union-Find-Tree.ipynb".
The first task is:
The previous example was a worst case scenario because we had defined the relation $R$ as a list so that we were able to control the order of the
joining of different trees.  If we represent $R$ as a set, the order of the pairs is more or less random and the trees do no degenerate to lists.
Can you write Python code for it?

def worst_case_set(n):
    M = set(range(1, n+1))
    R = { (k+1, k) for k in M if k < n }
    print(f'R = {R}')
    P = union_find(M, R)
    print(f'P = {P}')

worst_case_set(20)


I am working on the file "Python/Chapter-09/Union-Find.ipynb".
The first task is:
Given a dictionary Parent and an element $x$ from $M$, the function $\texttt{find}(x, \texttt{Parent})$
returns the ancestor of $x$ that is its own parent.
Can you write Python code for it?

def find(x, Parent):
    p = Parent[x]
    if p == x:
        return x
    return find(p, Parent)

def demo():
    M = set(range(1, 10))
    R = { (1, 4), (7, 9), (3, 5), (2, 6), (5, 8), (1, 9), (4, 7) }
    P = union_find(M, R)
    return P

demo()


I am working on the file "Python/Chapter-09/Union-Find.ipynb".
The first task is:
The former worst case is now actually the best case.
Can you write Python code for it?

def worst_case(n):
    M = set(range(1, n+1))
    R = [ (k+1, k) for k in M if k < n ]
    print(f'R = {R}')
    P = union_find(M, R)
    print(f'P = {P}')

worst_case(10)


I am working on the file "Python/Chapter-10/Monte-Carlo-Pi.ipynb".
The first task is:
The method $\texttt{confidence_interval}(k, n)$ runs $k$ approximations of $\pi$ using $n$ trials in each approximation run.
It computes a $97.3\%$ confidence interval for the value of $\pi$.
Can you write Python code for it?

def confidence_interval(k, n):
    L = []
    for _ in range(k):
        L.append(approximate_pi(n))
    𝜇, 𝜎 = std_and_mean(L)
    return 𝜇 - 3 * 𝜎, 𝜇, 𝜇 + 3 * 𝜎

%%time
n = 100
while n <= 10000000:
    lower, pi, upper = confidence_interval(100, n)
    print('%9d: %6f < 𝜋 < %6f, 𝜋 ≈ %6f, error: %6f' % (n, lower, upper, pi, abs(pi - math.pi)))
    n *= 10


I am working on the file "Python/Chapter-10/Monty-Hall-Problem.ipynb".
The first task is:
The Monty Hall Problem
The Monty Hall problem is a famous probability
puzzle that is based on the TV show
Let's Make a Deal, which was aired in the
US from the sixties through the seventies.  The host of this show was
Monty Hall. In his show, a player had to choose one
of three doors.  Monty Hall had placed goats behind two of the doors but there was a shiny new car
behind the third door.  Of course, the player did not know the location of the door with the car.
Once the player had told Monty Hall the door she had chosen, Monty Hall would open one of the other
two doors.  However, Monty Hall would never open the door with the car behind it.  Therefore, if the
player had chosen the door with the car, Monty Hall would have randomly chosen a door leading to a
goat.  If, instead, the player had chosen a door leading to a goat, Monty Hall would have opened the
door showing the other goat.  In either case, after opening the door  Monty Hall would ask the
player whether she wanted to stick with her first choice or whether, instead, she wanted to pick the
remaining closed door.  In order to better understand the problem you can try the animation at
http://math.ucsd.edu/~crypto/Monty/monty.html.
The question now is whether it is a good strategy to stick with the door chosen first or whether it
is a better idea to switch doors.
Mathematically, the reasoning is quite simple: As there are three doors initially and the probability that the car is behind any of them is the same, the probability that the door chosen first leads to the car is
$$\frac{1}{3}. $$
Therefore, the probability that the car is behind the other unopened door has to be
$$\frac{2}{3}, $$
as the two probabilities have to add up to $1$.  Hence the best strategy is to switch the door.
Although the reasoning given...
Can you write Python code for it?

def arb(S):
    for x in S: return x

import random as rnd


I am working on the file "Python/Chapter-10/Monty-Hall-Problem.ipynb".
The first task is:
The function calculate_chances simulates the game of Monty Hall $n$ times and keeps track how often two different strategies win.
  1. The persistent strategy never switches the door and always sticks to the door chosen first.
  2. The wavering strategy always switches the door.
Can you write Python code for it?

def calculate_chances(n):
    success_persistent = 0
    success_wavering   = 0
    for _ in range(n):
        car    = rnd.randint(1, 3)
        choice = rnd.randint(1, 3)
        opened = rnd.choice(list({1,2,3} - {choice,car}))
        last   = arb({ 1, 2, 3 } - { choice, opened })
        if car == choice:
            success_persistent += 1
        if car == last:
            success_wavering  += 1
    print(f'The persistent strategy wins {success_persistent} cars.')
    print(f'The wavering   strategy wins {success_wavering  } cars.')

%%time
calculate_chances(1000000)


I am working on the file "Python/Chapter-10/Permutation.ipynb".
The first task is:
Case Study: Computation of Probabilities in Poker
This notebook shows how to compute probabilities in
Texas Hold'em Poker.  Texas Hold'em is played with a deck of 52 cards.  The cards have 13 different values and four different suits.  The values are:
Can you write Python code for it?


I am working on the file "Python/Chapter-10/Permutation.ipynb".
The first task is:
Generating Random Permutations
Can you write Python code for it?

import random as rnd

def permute(L):
    if len(L) == 1:
        return L
    k = rnd.randint(0, len(L)-1)
    return permute(L[:k] + L[k+1:]) + [L[k]]

for _ in range(20):
    print(permute([1,2,3,4,5]))


I am working on the file "Python/Chapter-10/Permutation.ipynb".
The first task is:
Now the deck of cards is convieniently represented by the Cartesian product of the two sets <tt>Values</tt> and <tt>Suits</tt>.
Can you write Python code for it?

Deck = { (v, s) for v in Values for s in Suits }


I am working on the file "Python/Chapter-10/Permutation.ipynb".
The first task is:
The remaining cards are all other cards, lets call them <tt>rest</tt>:
Can you write Python code for it?