Exercise 1
Problem Description:
You are given two integers, m
and n
, where:
m
represents the number of repeated units (patterns).n
represents the number of elements (underscores) within each unit.
The goal is to generate a string where:
- Each unit contains
n
underscores (_), separated by|
. - These units are then concatenated together, separated by
*
.
My Solution:
1 | def generate_struct(n): |
My Thought Process:
I think of each unit as a separate structure. So, I decompose the problem by breaking down the smallest unit, which is the structure made of underscores joined by |
.
After constructing each unit, I use join()
to combine them together using *
.
Helper Function generate_struct(n):
This function generates the basic structure.
It creates a list of underscores (_
) of length n and joins them with |
.
Example: If n = 2
, the result will be “_|_
“.
Standard Solution:
1 | def f1(m, n): |
Concise Expression:
The inner join
creates a string of n
underscores joined by |
using a generator expression ('_' for _ in range(n))
.
The outer join
repeats this process m
times and concatenates the units using *
.
In summary, my solution focuses on modularity by breaking down the problem into smaller parts (like creating a structural unit), whereas the standard solution compresses everything into one line using list comprehensions.
Exercise 2
Problem Description:
The goal is to generate a pattern based on the digits of a given number n. Specifically:
If a digit is odd, it should be represented by a black square (⬛).
If a digit is even, it should be represented by a white square (⬜).
The program takes a number n as input and prints a string of squares corresponding to the digits of n.
My Solution:
1 | def f2(n): |
My Thought Process:
Loop through each digit:
Convert the number n to a string to iterate over each digit individually.Check if the digit is even or odd:
Convert each digit back to an integer and use the modulus operator (% 2) to check if the digit is even or odd.Append the corresponding square:
- If the digit is even, append a white square (⬜) to the result string.
- If the digit is odd, append a black square (⬛).
Print the final string:
After processing all the digits, print the final string containing black and white squares.
Standard Solution:
1 | def f2(n): |
Dr.Martin’s solution is:
- More compact and Pythonic.
- Uses a dictionary and list comprehension for brevity and efficiency.
- The Unicode characters for the squares are referenced directly using their escape sequences (
\u2b1c
for white,\u2b1b
for black).
Exercise 3
Problem Description:
In this task, the number n
is treated as a number expressed in different bases (ranging from 2 to 10), and we aim to convert it into its corresponding base 10 value for each of these bases, where the conversion is valid.
For n = 2143:
2143
in base5
is equivalent to298
in base10
.2143
in base6
is equivalent to495
in base10
.- And so on.
The goal is to iterate over different base systems from 2 to 10, treat the input number n
as if it is expressed in each base, and then convert it to base 10.
My Solution:
1 | def f3(n: int): |
My Thought Process:
Iterating over Bases (2 to 10):
- We loop through the base values i ranging from 2 to 10.
Conversion Using
int()
:- For each base
i
, we treat the numbern
as a number in that base. To do this, we first convertn
to a string (str(n)
) and then useint()
to interpret it as a basei
number. - If the digits of
n
are valid for basei
, this conversion succeeds, and the result is the base 10 equivalent ofn
. - If the digits of n are not valid for base i (for example, if base 2 is used and n contains digits greater than 1), a ValueError is raised, and we skip the invalid base.
- For each base
Handling Errors with
try-except
:- The
try-except
block ensures that invalid bases are skipped, allowing us to handle cases where the digits inn
are not valid for a particular base.
- The
Standard Solution:
1 | def f3(n): |
It skips the bases where the digits in n are not valid, and it uses a set comprehension to extract the unique digits from n_as_string. The maximum digit is then used to determine the minimum base to start iterating from.
Exercise 4
Problem Description:
The task is to create a function f4(n, base)
that returns a dictionary D, where:
Keys are integers from 0
to n
.
Values are tuples that represent the base base
representation of each key, converted from base 10.
My Solution:
1 | def convert_to_base(n, base): |
My Thought Process:
Helper Function
convert_to_base(n, base)
:- This function converts a number
n
from base 10 to the specified base. - We use a while loop to repeatedly take the modulus (
n % base
) and append the remainder to the listdigits
. - We then divide
n
bybase
(n //= base)
to reduce it for the next iteration. - After collecting all digits, we reverse the list and return it as a string.
- This function converts a number
Main Function
f4(n, base)
:
We initialize an empty dictionaryD
.
For each numberi
from0
ton
, we converti
to the given base usingconvert_to_base()
.
The converted base digits are then mapped to integers and stored in a tuple as the value for each keyi
in the dictionary.
Explanation of Why map()
is not Pythonic:
In the function f4, the use of map(int, convert_to_base(i, base))
applies the int
function
to each element of the result from convert_to_base
, effectively converting each character to an integer.
However, it’s worth noting that the map()
function, which originates from functional programming,
has largely been superseded by more Pythonic constructs such as list comprehensions.
These comprehensions are generally considered superior for several reasons:
- They are more elegant and concise.
- They tend to be shorter in terms of syntax, making the code easier to read.
- They are easier to understand for most people, especially those who are more familiar with Python’s
standard syntax rather than functional programming constructs. - In many cases, they are also more efficient in terms of performance.
For example, instead of using map(int, ...)
, the same functionality could be achieved with a
list comprehension, like so:
D[i] = tuple([int(digit) for digit in convert_to_base(i, base)])
This list comprehension achieves the same result but follows a more modern Pythonic style.
Standard Solution:
1 | def f4(n, base): |
Both solutions are valid and achieve the same result. My approach uses a helper function for base conversion, which adds modularity,
whereas the standard solution is more concise and directly integrates the conversion logic into the main function.
Exercise 5
At the first, try to run this:
1 | print(0.1 + 0.2) |
What happened? The result is not 0.3, but 0.30000000000000004. Why?
Problem Description:
The approach we are using in this exercise is designed to expose the limitations of floating-point arithmetic in computers. Let’s break down why this approach leads to precision inaccuracies and why other methods might not reveal these issues as clearly.
This problem can seem complex, and it’s designed to highlight the subtleties of floating-point arithmetic. Let’s walk through the logic using the test cases to figure out what the function does.
Key Concepts:
- Floating-point numbers: Computers store floating-point numbers using a binary format, which often introduces precision errors.
- Precision: We’re working with two types of precision in this function—simple precision (same as the length of the fractional part) and double precision (twice that length).
Solution:
1 | def f5(integral_part, fractional_part): |
Our function attempts to check and display this floating point error with simple precision (simple_precision
) and double precision (double_precision
). The error becomes more obvious when we represent floating point numbers with higher precision (double the number of decimal places). So in this way, we show that floating point numbers are not always actually stored as we expect them to be with more precise representation.
Exercise 6
Problem Description:
In this task, we are given:
- A list
L
, which contains multiple sub-lists of integers, and all sub-lists have the same lengthn
. - A list
fields
, which is a permutation of{1, ..., n}
.
We are required to sort the list L by using a multi-key sorting mechanism, where:
- First, the elements in
L
are sorted based on the position given by the first element offields
. - If two sub-lists are equal based on the first field, we move to the second field, and so on.
- Finally, if required, the sorting proceeds up to the last field in
fields
.
Example of Fields Explanation:
If fields = [2, 1]
, it means:
- First, sort based on the second element of each sublist.
- If there are ties (same values in the second position), sort based on the first element.
My Solution:
1 | def f6(L, fields): |
Sorting with sorted():
Thesorted()
function is used to sort the listL
.
The key parameter defines how the sorting will be performed.Lambda Function:
The lambda function defines how the sublists will be sorted. It generates a list of values for each sublist based on the positions specified infields
.
For example, iffields = [2, 1]
, the lambda function will extract the second and first elements from each sublist in that order, and sorting will be done based on this new list.Key Structure:
The key is a list of elements from each sublist, indexed by the positions specified in fields. We usex[i - 1]
because fields is1-based
, and list indexing in Python is0-based
.What is Lambda Function?
For example:
We have:
1 | f = lambda x: x * x |
This is equivalent to:
1 | def f(x): |
And lambda function in a sorted function is used to define a custom sorting key.
1 | L = [(1,2), (3,1), (5,0)] |
The result is: [(5, 0), (3, 1), (1, 2)]
, it sorts the list based on the second element of each tuple.
Standard Solution:
1 | def f6(L, fields): |
Why Use a Tuple?:
- Tuples are often preferred for multi-key sorting because they are immutable, and Python’s built-in sorting functions can efficiently compare tuples.
- Each sublist is transformed into a tuple of its elements based on the order defined by fields. The sorted() function then uses these tuples to sort the list.