Day 1: KISS principle. Unittest pytest. Poetry.
Hi 👋 I am Hud, a postdoc for engineering data science at the AI Manufacturing Center in Laramie, Wyoming. My funding is running out (AaAaaA !), so while I am actively looking for a new job, instead of doing the 205th coding certificate to prove my worthiness — I thought I’d do design challenges and document how I spend my time upskilling so other engineers can do the same.
Nowadays, certificates are everywhere. Documenting small upskill projects that you can later show off is the best way to get recognition as a professional engineer.
This is day 1 of a 30-day design challenge. Follow along and let me know if you get stuck!
KISS debrief
The KISS principle, which stands for “Keep It Simple, Stupid,” is a design principle that promotes simplicity and avoiding unnecessary complexity. It suggests that systems, products, or solutions should be kept as simple as possible without compromising their functionality or effectiveness.
The principle originated in the field of engineering, but its application extends to various domains, including software development, user interface design, project management, and communication. The core idea behind the KISS principle is that simplicity often leads to better outcomes, improved efficiency, and reduced chances of errors or confusion.
When applied to design and problem-solving processes, the KISS principle encourages practitioners to eliminate unnecessary features, complexities, or steps that do not contribute significantly to the desired outcome. By keeping things simple, the principle aims to enhance usability, maintainability, and overall user experience.
The KISS principle is closely related to other design philosophies, such as Occam’s Razor, which suggests that the simplest explanation or solution is often the most likely to be correct. Embracing simplicity can result in streamlined designs, clearer communication, and easier understanding and implementation of ideas or concepts.
For more information, see here.
KISS challenge tldr
Challenge:
Write a Python function called count_fruits
that counts the frequency of each type of fruit in a given list of strings. Return a dictionary where the keys are fruit names and the values are their respective counts. The code should be simple and straightforward.
Solution:
- Download the provided code for the challenge.
- Write a function named
count_fruits
that takes a list of strings as input. - Initialize an empty dictionary to store the fruit counts.
- Iterate over each fruit in the input list.
- If the fruit is already a key in the dictionary, increment its count by 1.
- If the fruit is not a key in the dictionary, add it as a key with a count of 1.
- Return the resulting dictionary of fruit counts.
Testing:
- Add tests to ensure the function works correctly.
- You can use Python’s built-in
unittest
package or add assertions to the main function.
Reflection:
- Reflect on the simplicity and straightforwardness of your solution.
- Consider if there is a simpler approach to solve the problem.
- Evaluate if you have used appropriate language features and constructs effectively.
STEPS
Download the provided code for the challenge.
poetry config --local virtualenvs.in-project true
2. Access poetry in terminal (installation docs here):
poetry shell
3. We start by editing the main logic in before.py
. Make a copy of before.py
to after.py
. If you are stuck, the solutions are in after_{v0, v1, v2}.py
. We consider two ways of testing. The first adds tests directly using unittest
:
a. Add test code
b. Run the tests:
python after_v0.py # change versions accordingly v0, v1, v2
4. The second way is by using pytest (:
a. Add pytest
to poetry:
poetry add pytest
b. In the root folder, add a tests
folder with an empty __init__.py
in it. Create tests:
c. Run the test in the terminal simply with:
pytest
Now we can work on the main logic. Here are some of the variations we can use. Feel free to add in the comments your own versions.
Example V0 — the ugly
The count_fruits
function takes a list of strings called fruits
as input and returns a dictionary with the count of each fruit in the input list.
The function initializes an empty dictionary called fruits_dict
. It then iterates over the indices of the fruits
list using a for
loop. For each fruit in the list, it checks if the fruit is already in the fruits_dict
dictionary. If it is not, the function initializes a counter variable called count
to 0 and iterates over the remaining elements of the fruits
list using another for
loop. For each element that matches the current fruit, the count
variable is incremented. Finally, the function adds an entry to the fruits_dict
dictionary with the current fruit as the key and the count
variable as the value.
The time complexity of this function is O(n²), where n is the length of the input list. This is because the function iterates over the list twice, once for each fruit, and for each fruit it iterates over the remaining elements of the list to count the number of occurrences.
The space complexity of this function is also O(n), where n is the length of the input list. This is because the function creates a dictionary with one entry for each unique fruit in the input list.
Example V1 — the bad
The function initializes an empty dictionary called fruit_counts
. It then iterates over the elements of the fruits
list using a for
loop. For each fruit in the list, it checks if the fruit is already in the fruit_counts
dictionary. If it is, the function increments the value of the corresponding key by 1. If it is not, the function adds a new key-value pair to the fruit_counts
dictionary with the current fruit as the key and the value 1.
The time complexity of this function is O(n), where n is the length of the input list. This is because the function iterates over the list once and performs a constant-time dictionary lookup and update operation for each element.
The space complexity of this function is also O(n), where n is the length of the input list. This is because the function creates a dictionary with one entry for each unique fruit in the input list.
This implementation follows the KISS (Keep It Simple, Stupid) principle better than the previous implementation. It achieves the same functionality with fewer lines of code and simpler logic. It also has better time and space complexity than the previous implementation.
Example V2— the good
This implementation uses the Counter
class from the built-in collections
module to count the occurrences of each fruit in the input list. The Counter
class returns a dictionary-like object with the count of each element in the input list. The dict
constructor is then used to convert the Counter
object to a regular dictionary.
The time complexity of this function is O(n), where n is the length of the input list. This is because the Counter
class iterates over the input list once to count the occurrences of each element.
The space complexity of this function is also O(n), where n is the length of the input list. This is because the Counter
class creates a dictionary with one entry for each unique element in the input list.
This implementation follows the KISS (Keep It Simple, Stupid) principle even better than the previous implementation. It achieves the same functionality with fewer lines of code and simpler logic.
Conclusion
Congratulations! You finished Day 1 from the 30-day design challenge.
If you have reached this far, you know how to:
- Reflect on KISS and refactor
- Build using Poetry
- Run unit tests and pytest
Check out day 2 challenge!
Also, you can access the full 30-day GitHub repository here.
💡 My goal here is to help engineering data scientists upskill in design. I’d like to hear from you! Was this helpful? Anything I can improve? Connect with me on LinkedIn | Medium