Module III - Day 3

Python Made Easy: Science and Math Edition

Sep-Dec 2025 batch, Vikrant Patil

Date: 8th Feb 2026

Click here for All Notes

Live note are here https://vikrant.dev/python-made-easy-science-math/students-module3-day1.html

Please login to https://traininghub.vikrant.dev and create a notebook with name module3-day3.ipynb

© Vikrant Patil

Seeing the patterns visually

def riffle_shuffle(deck):
    n = len(deck)
    mid = n//2 # ineteger
    part1 = deck[:mid] # take first half
    part2 = deck[mid:] # drop first half

    s = []
    for x, y in zip(part1, part2):
        s.extend([x,y])
    if len(part1) < len(part2):
        s.extend([part2[-1]])
    return s

def repeat(func, arg, n):
    """
    applies function func to arg , n times
    """
    for i in range(n):
        arg = func(arg)
        print(f"{i:2d}","".join(arg))
    return arg

def repeat_count(func, arg):
    newarg = arg[:]
    count = 1
    newarg = func(newarg)
    while newarg != arg:
        newarg = func(newarg)
        count += 1
    return count
print("length", "shuffles")
for i in range(1, 53):
    print("{i:6d} {s:8d}".format(i=i, s=repeat_count(riffle_shuffle, list(range(i)))))
length shuffles
     1        1
     2        1
     3        1
     4        2
     5        2
     6        4
     7        4
     8        3
     9        3
    10        6
    11        6
    12       10
    13       10
    14       12
    15       12
    16        4
    17        4
    18        8
    19        8
    20       18
    21       18
    22        6
    23        6
    24       11
    25       11
    26       20
    27       20
    28       18
    29       18
    30       28
    31       28
    32        5
    33        5
    34       10
    35       10
    36       12
    37       12
    38       36
    39       36
    40       12
    41       12
    42       20
    43       20
    44       14
    45       14
    46       12
    47       12
    48       23
    49       23
    50       21
    51       21
    52        8
counts = [repeat_count(riffle_shuffle, list(range(i))) for i in range(1, 401)]
len(counts)
400

import matplotlib.pyplot as plt
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
Cell In[7], line 1
----> 1 import matplotlib.pyplot as plt

ModuleNotFoundError: No module named 'matplotlib'
!pip install matplotlib
Collecting matplotlib

  Using cached matplotlib-3.10.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (52 kB)

Collecting contourpy>=1.0.1 (from matplotlib)

  Using cached contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.metadata (5.5 kB)

Collecting cycler>=0.10 (from matplotlib)

  Using cached cycler-0.12.1-py3-none-any.whl.metadata (3.8 kB)

Collecting fonttools>=4.22.0 (from matplotlib)

  Using cached fonttools-4.61.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl.metadata (114 kB)

Collecting kiwisolver>=1.3.1 (from matplotlib)

  Using cached kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (6.3 kB)

Requirement already satisfied: numpy>=1.23 in /home/vikrant/usr/local/default/lib/python3.13/site-packages (from matplotlib) (2.4.2)

Requirement already satisfied: packaging>=20.0 in /home/vikrant/usr/local/default/lib/python3.13/site-packages (from matplotlib) (25.0)

Collecting pillow>=8 (from matplotlib)

  Downloading pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.metadata (8.8 kB)

Collecting pyparsing>=3 (from matplotlib)

  Using cached pyparsing-3.3.2-py3-none-any.whl.metadata (5.8 kB)

Requirement already satisfied: python-dateutil>=2.7 in /home/vikrant/usr/local/default/lib/python3.13/site-packages (from matplotlib) (2.9.0.post0)

Requirement already satisfied: six>=1.5 in /home/vikrant/usr/local/default/lib/python3.13/site-packages (from python-dateutil>=2.7->matplotlib) (1.17.0)

Using cached matplotlib-3.10.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (8.7 MB)

Using cached contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (362 kB)

Using cached cycler-0.12.1-py3-none-any.whl (8.3 kB)

Using cached fonttools-4.61.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl (4.9 MB)

Using cached kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (1.5 MB)

Downloading pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (7.0 MB)

   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 7.0/7.0 MB 12.0 MB/s eta 0:00:0031m15.8 MB/s eta 0:00:01

Using cached pyparsing-3.3.2-py3-none-any.whl (122 kB)

Installing collected packages: pyparsing, pillow, kiwisolver, fonttools, cycler, contourpy, matplotlib

   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 7/7 [matplotlib] 6/7 [matplotlib]ow]

Successfully installed contourpy-1.3.3 cycler-0.12.1 fonttools-4.61.1 kiwisolver-1.4.9 matplotlib-3.10.8 pillow-12.1.1 pyparsing-3.3.2

import matplotlib.pyplot as plt
#show images from matplotlib in the same HTML page
%matplotlib inline
plt.plot(counts)

Random Walk

You start from home. You can walk unit distance in one step. In every step you choose the direction randomly. Availables directions are east, west, north, south. So after 500 steps how much distance you would be away from home?

import math
import random

class RandomWalk:

    def __init__(self, home=(0,0)):
        self.x, self.y = home
        self.home = home
        
    def north(self):
        self.y += 1

    def south(self):
        self.y -= 1

    def east(self):
        self.x += 1

    def west(self):
        self.x -= 1

    def distance(self):
        x1, y1 = self.home
        x2, y2 = self.x, self.y
        return math.sqrt((x1-x2)**2 + (y1-y2)**2)

    def reset(self):
        self.x, self.y = self.home

    def execute_random_step(self):
        direction = [self.north, self.south, self.east, self.west]
        walk = random.choice(direction)
        walk()


class RandomWalkExecuter:

    def __init__(self, iterations, walk: RandomWalk): # this is called as typehint it is just for information
        self.iterations = iterations
        self.walk = walk

    def execute(self):
        for i in range(self.iterations):
            self.walk.execute_random_step()

    def get_walk(self):
        return self.walk


def simulate(experiments=100):

    distances = []
    for i in range(experiments):
        walk = RandomWalk()
        executer = RandomWalkExecuter(500, walk)
        executer.execute()
        d = executer.get_walk().distance()
        distances.append(d)
    return distances
def add_number(a, b=5): # default value of b is 5
    return a + b
add_number(10)
15
add_number(10, 4)
14
import random
random.choice("hello world")
'o'
random.choice("hello world")
'l'
random.choice("hello world")
'w'
random.choice("hello world")
'e'
x = [add_number, 23, 43, 5, 5]
x[0]
<function __main__.add_number(a, b=5)>
x[0](100)
105
x = 10
x = [1 , 2, 3, 4]
x = add_number
def add_numbers(a: int, b: int=5):
    """where a in an integer
    """
    return a + b
add_numbers("a" , "b")
'ab'
simulate(100)
[21.2602916254693,
 11.661903789690601,
 4.47213595499958,
 16.1245154965971,
 28.319604517012593,
 21.400934559032695,
 16.55294535724685,
 25.96150997149434,
 18.867962264113206,
 28.071337695236398,
 14.212670403551895,
 9.055385138137417,
 5.830951894845301,
 21.587033144922902,
 31.622776601683793,
 26.076809620810597,
 35.4400902933387,
 23.53720459187964,
 13.038404810405298,
 13.92838827718412,
 22.80350850198276,
 15.556349186104045,
 10.198039027185569,
 32.31098884280702,
 28.178005607210743,
 26.570660511172846,
 7.615773105863909,
 26.076809620810597,
 17.029386365926403,
 10.198039027185569,
 29.732137494637012,
 6.324555320336759,
 15.231546211727817,
 37.73592452822641,
 15.297058540778355,
 25.495097567963924,
 11.661903789690601,
 4.47213595499958,
 7.211102550927978,
 22.80350850198276,
 26.419689627245813,
 40.496913462633174,
 5.656854249492381,
 11.045361017187261,
 30.886890422961002,
 3.1622776601683795,
 5.656854249492381,
 38.47076812334269,
 33.015148038438355,
 28.319604517012593,
 7.0710678118654755,
 16.1245154965971,
 45.5411901469428,
 19.849433241279208,
 43.08131845707603,
 24.698178070456937,
 29.832867780352597,
 8.94427190999916,
 28.844410203711913,
 34.058772731852805,
 21.95449840010015,
 22.0,
 5.0990195135927845,
 8.94427190999916,
 8.94427190999916,
 18.973665961010276,
 10.295630140987,
 7.0710678118654755,
 4.0,
 21.95449840010015,
 35.4682957019364,
 5.656854249492381,
 12.083045973594572,
 14.7648230602334,
 12.806248474865697,
 10.770329614269007,
 45.60701700396552,
 11.661903789690601,
 23.40939982143925,
 17.029386365926403,
 35.4682957019364,
 42.37924020083418,
 9.486832980505138,
 12.727922061357855,
 4.0,
 17.4928556845359,
 8.246211251235321,
 22.67156809750927,
 15.811388300841896,
 23.021728866442675,
 2.8284271247461903,
 27.018512172212592,
 40.0,
 3.1622776601683795,
 12.0,
 17.029386365926403,
 13.92838827718412,
 6.0,
 9.899494936611665,
 25.059928172283335]
plt.plot(simulate(100))

import statistics
distances = simulate(100)
statistics.stdev(distances)
9.89799976218993
statistics.mean(distances)
19.237705286769813
distances1 = simulate(100)
statistics.mean(distances1)
19.55662881014937
statistics.stdev(distances1)
9.039658390682582
ones = [1 for i in range(100)]
statistics.stdev(ones)
0.0
statistics.mean(ones)
1
def simulate(experiments=100, steps=500):

    distances = []
    for i in range(experiments):
        walk = RandomWalk()
        executer = RandomWalkExecuter(steps, walk)
        executer.execute()
        d = executer.get_walk().distance()
        distances.append(d)
    return distances
results = [simulate(100, s) for s in range(50, 501, 25)]
    
len(results)
19
plt.plot(results[0]) # 50

plt.plot(results[5])

results = [simulate(100, s) for s in range(50, 501, 25)]
means = [statistics.mean(r) for r in results]
stddev = [statistics.stdev(r) for r in results]
plt.plot(range(50, 501, 25), means)

plt.plot(range(50, 501, 25), stddev)