Exam - Wed 31, Aug 2022
Scientific Programming - Data Science Master @ University of Trento
Download exercises and solutions
Part A - I CHING Divination (32 points)
NOTICE: this part of the exam was ported to softpython website
There you can find a more curated version (notice it may be longer than here)
The I Ching or Book of Changes, is a chinese divination manual and one of the world’s oldest books, dating from over 3,000 years ago. The great mathematician Gottfried Wilhelm Leibniz (1646 - 1716) is considered the first information theorist, and extensively documented the binary numeral system. Leibniz was also interested in Chinese culture, and saw in the I Ching diagrams showing solid and broken lines called yin and
yang, which progressed in a sequence: that was unmistakably a binary encoding. You will parse a dataset of hexagrams and develop a divinator software which will predict the outcome of your exam. Open Jupyter and start editing this notebook exam-2022-08-31.ipynb
Data source: Wikipedia, July 2021, Bagua page
The dataset
Yin and yang: Yin and yang are represented by lines:
name |
line |
bit |
---|---|---|
yin |
|
0 |
yang |
|
1 |
Trigrams: Different constructions of three yin and yang lines lead to 8 trigrams. We can express a trigram as a sequence of bits, reading lines from bottom to top. For example Fire is 101
, Thunder is 100
.
Hexagrams: Combining a lower trigram with an upper trigram leads to 64 hexagrams. Each hexagram can be represented as a sequence of bits and the outcome of a divination. For example trigrams Fire (lower) and Thunder (upper) gives outcome hexagram Abounding: 101100
1. load_db (14 points)
Parse iching.csv and output a dictionary mapping each sequence to a dictionary with all the information you can extract. Use CSV reader.
in headers and first column you will find a bit sequence like
011
in body cells, you will not find a bit sequence: you will have to determine it according to the corresponding tri-sequences from the header and first column
note for hexagrams you must extract only
name-en
, ignore the decimal numbers
Example (complete output is in file expected_iching_db.py):
>>> load_db('iching.csv')
{
'111': {'name-en': 'Heaven', 'name-ch': '乾', 'spelling': 'Qián'}
'000': {'name-en': 'Earth', 'name-ch': '坤', 'spelling': 'Kūn'}
'100': {'name-en': 'Thunder', 'name-ch': '震', 'spelling': 'Zhèn'}
'010': {'name-en': 'Water', 'name-ch': '坎', 'spelling': 'Kǎn'}
'001': {'name-en': 'Mountain', 'name-ch': '艮', 'spelling': 'Gèn'}
'011': {'name-en': 'Air', 'name-ch': '巽', 'spelling': 'Xùn'}
'101': {'name-en': 'Fire', 'name-ch': '離', 'spelling': 'Lí'}
'110': {'name-en': 'Lake', 'name-ch': '兌', 'spelling': 'Duì'}
'111111': {'name-en': 'Force'}
'111000': {'name-en': 'Pervading'}
'111100': {'name-en': 'Great Invigorating'}
'111010': {'name-en': 'Attending'}
'111001': {'name-en': 'Great Accumulating'}
'111011': {'name-en': 'Small Harvest'}
'111101': {'name-en': 'Great Possessing'}
.
.
}
[2]:
import csv
def load_db(filepath):
raise Exception('TODO IMPLEMENT ME !')
iching_db = load_db('iching.csv')
iching_db
[4]:
# EXECUTE FOR TESTING
from pprint import pformat; from expected_iching_db import expected_iching_db
for seq in expected_iching_db.keys():
if seq not in iching_db: print('\nERROR: MISSING sequence', seq); break
for k in expected_iching_db[seq]:
if k not in iching_db[seq]:
print('\nERROR at sequence', seq,'\n\n MISSING key:', k); break
if expected_iching_db[seq][k] != iching_db[seq][k]:
print('\nERROR at sequence', seq, 'key:',k)
print(' ACTUAL:\n', pformat(iching_db[seq][k]))
print(' EXPECTED:\n', pformat(expected_iching_db[seq][k]))
break
2. divine (10 points)
A divination is done by flipping 3 coins to determine the bottom trigram (bottom up order), flipping other three coins for the upper trigram (again bottom up order), and then the union gives the resulting hexagram. Write a function that PRINTS the process as in the example and RETURNS a string of bits representing the resulting hexagram
HINT: to flip coins use random.randint(0,1)
Example:
>>> divination = divine(iching_db, "Will I pass the exam?")
>>> print("\nRETURNED:", divination)
Dear stranger, welcome to SCIPROG I CHING 易經 DIVINATOR
Tell me your question...
Will I pass the exam?
The coin says 'heads' : we get a yang ---
The coin says 'tails' : we get a yin - -
The coin says 'heads' : we get a yang ---
The sacred bottom trigram is:
Fire
---
- -
---
The coin says 'heads' : we get a yang ---
The coin says 'tails' : we get a yin - -
The coin says 'tails' : we get a yin - -
The sacred upper trigram is:
Thunder
- -
- -
---
The final response hexagram is...
Abounding
- -
- -
---
---
- -
---
RETURNED: 101100
[5]:
import random
def divine(iching, question):
#THE SEED DETERMINES FOLLOWING randint RESULTS
random.seed(109) # Abounding
# Thunder
# Fire
#IMPORTANT: try also this seed to check lines visualization order
#random.seed(1)
#
# Infiltrating 001011
# Air ---
# ---
# - -
# Mountain ---
# - -
# - -
raise Exception('TODO IMPLEMENT ME !')
divination = divine(iching_db, "Will I pass the exam?")
print("\nRETURNED:", divination)
3. plot_divination (8 points)
Given a divination as a string of bits, plot the divination. First draw the lines, then the rest if you have time. Make it fancy with these examples. To center text you can use these parameters: ha='center', va='center'
[6]:
%matplotlib inline
import matplotlib.pyplot as plt
def plot_divination(iching, question, divination):
raise Exception('TODO IMPLEMENT ME !')
plot_divination(iching_db, "Will I pass sciprog exam?", '101100') # Abounding
#plot_divination(iching_db, "Will I pass programming exam?", '111011') # Small Harvest
#plot_divination(iching_db, "Will I pass programming exam?",'001011') # Infiltrating
Part B (32 points)
Open Visual Studio Code and start editing the folder on your desktop
B1.1 Complexity (8 points)
Given a list L
of n elements, please compute the asymptotic computational complexity of the myFun
function, explaining your reasoning.
Write the solution in separate ``theory.txt`` file
def my_fun2(L):
n = len(L)
tmp = []
for i in range(n):
for j in range(n):
tmp.append(L[i]/(1+L[j]))
return tmp
def my_fun(L):
n = len(L)
if n <= 1:
return 1
else:
L1 = L[0:n//4]
L2 = L[n//4:]
a = my_fun(L1) + max(my_fun2(L1))
b = my_fun(L2) + max(my_fun2(L2))
return a - b
B1.2 Graphs (8 points)
Describe the differences between the Depth-First and the Breadth-First Search algorithms for visiting graphs. Then, apply BFS to the graph below (write down the visit order only).
B2. chains (8 points)
def chain(external_list):
""" Takes a list of list of strings and return a list containing all
the strings from external_list in sequence, joined by the ending
and starting strings of the internal lists. See tests for more examples.
INPUT: a list of list of strings , like:
[
['ab', 'c', 'de'],
['gh', 'i'],
['de', 'f', 'gh']
]
OUTPUT: a list of strings, like ['ab', 'c', 'de', 'f', 'gh', 'i']
"""
Assume that:
external_list
always contains at least one internal listinternal lists always contain at least two strings
no string is duplicated among all internal lists
Output sequence is constructed as follows:
it starts will all the items from the first internal list
successive items are taken from an internal list which starts with a string equal to the previous taken internal list last string
sequence must not contain repetitions (so joint strings are taken only once).
all internal lists must be used. If this is not possible (because there are no joint strings), raise
ValueError
MUST RUN IN \(O(m * n)\), where \(m\) is the number of internal lists and \(n\) is the length of the longest internal list (just to calculate complexity think about the scenario where all lists have equal size)
DO NOT use python search methods (so no
.index
,.find
,.count
…) nor regexesHINT: Given the above constraints, whenever you find a string, you cannot start another for loop to check if the string exists elsewhere (that would likely introduce a quadratic \(m^2\) factor)
Testing: python3 -m unittest chains_test.TestChain
B3 linked list pivot (8 points)
def pivot(self):
""" Selects first node data as pivot, and then MOVES before the pivot
all the nodes which have data value STRICTLY LESS (<) than the pivot.
Finally, RETURN the number of moved nodes.
IMPORTANT:
- *DO NOT* create new nodes
- nodes less than pivot must be in the reversed order they were found
- nodes greater or equal than pivot will maintain the original order
- MUST EXECUTE in O(n), where n is the list size
"""
Testing: python3 -m unittest linked_list_test.PivotTest
Example:
[9]:
from linked_list_sol import *
from linked_list_test import to_ll
ll = to_ll([7, 12, 1, 3, 8, 9, 6, 4, 7, 2, 10])
[10]:
print(ll)
LinkedList: 7,12,1,3,8,9,6,4,7,2,10
[11]:
res = ll.pivot()
[12]:
res # there were 5 elements strictly less than pivot 7
[12]:
5
Note elements \(< 7\) are in reverse order in which they were found, elements \(\geq7\) are in the original order
[13]:
print(ll)
LinkedList: 2,4,6,3,1,7,12,8,9,7,10
[ ]:
[ ]: