Skip to content
This repository was archived by the owner on Apr 3, 2026. It is now read-only.

Commit 9325c82

Browse files
authored
feat: Exercise #3. Complex Data Structures
BREAKING CHANGE: New exercise added
2 parents 15a785e + 6140d1e commit 9325c82

File tree

4 files changed

+243
-4
lines changed

4 files changed

+243
-4
lines changed

3-data-structures/blockchain.py

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
# Types of iterables
2+
# A [list] is mutable, ordered collection of items that allows duplicates and uses mostly one type of data
3+
# [1, 2, 3, 4, 5]
4+
# A [set] is also mutable, not ordered, no duplicates, and also uses mostly one type of data
5+
# {1, 2, 3, 4, 5}
6+
# A [tuple] is immutable, ordered collection of items that allows duplicates and can use multiple types of data
7+
# (1, 2, 3, "honey", true)
8+
# A [dictionary] is mutable, not ordered, no duplicates, and uses key-value pairs. Is similar to a JavaScript object
9+
# {"name": "Alice", "age": 30, "city": "New York"}
10+
11+
# Other thigs to have in mind when you are using iterables
12+
# If you copy a list from another one and change any of its values inside, it will change the value in both lists because they are pointing to the same memory address
13+
# That is because is copying the reference of the list, not the actual values
14+
# To avoid that, you can use the LIST_NAME[:] function to create a new list with the same values
15+
# copy_list = original_list[:]
16+
# A way to copy a specific range of a list is by using the LIST_NAME[START:END] function
17+
# This will copy the values from index 1 to 2 (3 is not included)
18+
# copy_list = original_list[1:3]
19+
# This feature also works with tuples
20+
# copy_tuple = original_tuple[1:3]
21+
# To copy the list exept the last element, you can use LIST_NAME[:-1]
22+
# copy_list = original_list[:-1]
23+
# In all range copy examples, it gives a new copy of the list, not a reference to the original one
24+
# BUT, it creates a shallow copy. The elements (such as dictionaries or lists) inside the list are still references to the original ones
25+
26+
MINING_REWARD = 10
27+
genesis_block = {
28+
'previous_hash': '',
29+
'index': 0,
30+
'transactions': []
31+
}
32+
my_blockchain = [genesis_block]
33+
open_transactions = []
34+
waiting_for_input = True
35+
owner = 'Nicolas'
36+
# A set can be created without any previous values or with a iterable like a list
37+
participants = set([owner])
38+
39+
def add__line():
40+
print('------------------------------')
41+
42+
def generate_options_menu():
43+
add__line()
44+
print('Please choose an option:')
45+
print('1: Add a new transaction')
46+
print('2: Mine a new block')
47+
print('3: Output all blockchain blocks')
48+
print('4: Output all participants')
49+
print('h: Manipulate blockchain')
50+
print('q: Quit')
51+
add__line()
52+
53+
def get_user_input():
54+
user_input = input('Please enter your selection: ')
55+
add__line()
56+
return user_input
57+
58+
def hash_block(block):
59+
""" Hash a block using its strucutre as base """
60+
# In order to create a new hash for the new block, we will use a list comprehension
61+
# List comprehension creates a new list based on an existing iterable, applying an expression to each item in the iterable, in this case, concatinating each value of the dictionary into a single string
62+
# hashed_block = str([last_block[key] for key in last_block])
63+
# You can use an if for this comprehension to filter items from the original iterable
64+
# hashed_block = str([last_block[key] for key in last_block if key != 'transactions'])
65+
return str(block[key] for key in block)
66+
67+
def mine_block():
68+
last_block = my_blockchain[-1]
69+
hashed_block = hash_block(last_block)
70+
71+
reward_transaction = {
72+
'sender': 'MINING',
73+
'recipient': owner,
74+
'amount': MINING_REWARD
75+
}
76+
77+
block = {
78+
'previous_hash': hashed_block,
79+
'index': len(my_blockchain),
80+
'transactions': [open_transactions, reward_transaction]
81+
}
82+
83+
my_blockchain.append(block)
84+
# Here if you try to reset the open_transactions list, it will create a new local variable instead of modifying the global one
85+
# open_transactions = []
86+
print('Block added!')
87+
add__line()
88+
return True
89+
90+
def get_transaction_value():
91+
""" Returns the input of the user (a new transaction amount and its recipient) as a tuple """
92+
tx_recipient_input = input('Please enter the recipient of the transaction: ')
93+
tx_amount_input = float(input('Please enter your transaction input: '))
94+
add__line()
95+
96+
# When you return a tuple, it does not need to be enclosed in parentheses (is optional i guess)
97+
return tx_recipient_input, tx_amount_input
98+
99+
def take_last_blockchain_value():
100+
if len(my_blockchain) < 1:
101+
return None
102+
103+
return my_blockchain[-1]
104+
105+
def verify_transaction(transaction):
106+
sender_balance = get_balance(transaction['sender'])
107+
108+
if sender_balance >= transaction['amount']:
109+
return True
110+
return False
111+
112+
def add_transaction(sender, recipient, amount=1):
113+
"""
114+
Add a new transaction to the list of open transactions (which will be added to the next mined block)
115+
116+
Arguments:
117+
:sender: The sender of the coins.
118+
:recipient: The recipient of the coins.
119+
:amount: The amount of the transaction.
120+
"""
121+
122+
new_transaction = {
123+
'sender': sender,
124+
'recipient': recipient,
125+
'amount': amount
126+
}
127+
128+
if verify_transaction(new_transaction):
129+
open_transactions.append(new_transaction)
130+
# When you add a new element to a set, if the element already exists, it will not be added again
131+
participants.add(sender)
132+
participants.add(recipient)
133+
else:
134+
print('Transaction failed! Not enough balance!')
135+
add__line()
136+
137+
def return_all_blocks():
138+
print('---Outputting all blocks---')
139+
140+
for block in my_blockchain:
141+
print('Outputting block: ' + str(block))
142+
add__line()
143+
144+
def get_balance(participant):
145+
final_balance = 0
146+
sent_transactions = [[tx['amount'] for tx in block['transactions'] if tx['sender'] == participant] for block in my_blockchain]
147+
recieved_transactions = [[tx['amount'] for tx in block['transactions'] if tx['recipient'] == participant] for block in my_blockchain]
148+
open_sent_transactions = [tx['amount'] for tx in open_transactions if tx['sender'] == participant]
149+
150+
sent_transactions.append(open_sent_transactions)
151+
152+
for sent_amount in sent_transactions:
153+
if len(sent_amount) > 0:
154+
final_balance -= sent_amount
155+
156+
for recieved_amount in recieved_transactions:
157+
if len(recieved_amount) > 0:
158+
final_balance += recieved_amount
159+
160+
return final_balance
161+
162+
def verify_chain():
163+
""" The function helps to verify the integrity of the blockchain by checking if each block's previous hash matches the hash of the previous block. """
164+
# Enumerate is a function that will convert your list in a tuple with the index and the value of each item in the list
165+
# By creating a tuple, we you can unpack its values into different variables
166+
for (index, block) in enumerate(my_blockchain):
167+
if index == 0:
168+
continue
169+
if block['previous_hash'] != hash_block(my_blockchain[index - 1]):
170+
return False
171+
return True
172+
173+
def verify_transactions():
174+
""" The function verifies all open transactions to ensure they are valid. """
175+
# All method checks that all items in an iterable are true
176+
# In this case, we are converting the open_transactions list into a comprehension list that will return True or False depending on the verify_transaction function
177+
# There is another method that checks if at least one item is true, is called any()
178+
# To read more about all() and any() methods, check the following link: https://docs.python.org/3/tutorial/datastructures.html
179+
return all([tx for tx in open_transactions if not verify_transaction(tx)])
180+
181+
while waiting_for_input:
182+
generate_options_menu()
183+
184+
user_choice = get_user_input()
185+
186+
if user_choice == '1':
187+
tx_input_data = get_transaction_value()
188+
# A way to unpack values from a tuple into different variables
189+
recipient, amount = tx_input_data
190+
add_transaction(owner, recipient, amount)
191+
elif user_choice == '2':
192+
if mine_block():
193+
# Here you can reset the open_transactions list after mining a block because is referring to the global variable
194+
open_transactions = []
195+
elif user_choice == '3':
196+
return_all_blocks()
197+
elif user_choice == '4':
198+
print(participants)
199+
elif user_choice == 'q':
200+
waiting_for_input = False
201+
elif user_choice == 'h':
202+
if len(my_blockchain) >= 1:
203+
my_blockchain[0] = [2.0]
204+
else:
205+
print('Invalid input, please choose a valid option')
206+
if not verify_chain():
207+
print('Invalid blockchain!')
208+
waiting_for_input = False
209+
print('Balance: ' + str(get_balance(owner)))
210+
else:
211+
print('User left!')
212+
213+
add__line()
214+
print('Done!')

3-data-structures/exercise.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# 1) Create a list of “person” dictionaries with a name, age and list of hobbies for each person. Fill in any data you want.
2+
persons_list = [
3+
{ 'name': 'Nicolas', 'age': 22, 'hobbies': ['reading', 'gaming'] },
4+
{ 'name': 'Florencia', 'age': 19, 'hobbies': ['painting', 'cycling'] },
5+
{ 'name': 'Martin', 'age': 25, 'hobbies': ['hiking', 'swimming'] }
6+
]
7+
print(persons_list)
8+
9+
# 2) Use a list comprehension to convert this list of persons into a list of names (of the persons).
10+
person_comprehension = ', '.join([person['name'] for person in persons_list])
11+
print('The persons names are:', person_comprehension)
12+
13+
# 3) Use a list comprehension to check whether all persons are older than 20.
14+
all_older_than_20 = all([person['age'] > 20 for person in persons_list])
15+
print('All persons older than 20:', all_older_than_20)
16+
17+
# 4) Copy the person list such that you can safely edit the name of the first person (without changing the original list).
18+
persons_copy = [person.copy() for person in persons_list]
19+
persons_copy[0]['name'] = 'Matias'
20+
print('Original list:', persons_list)
21+
print('Modified copy:', persons_copy)

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,14 @@ python3 exercise.py # Or any .py file
5252
- Understanding how to use `break` and `continue` words.
5353
- Undesrtanding the usage of `None` value.
5454
- Undesrtanding the `not` keyword.
55+
- Complex Data Structures (`3-data-structures` folder):
56+
- How to use `tuples`.
57+
- How to use `lists`
58+
- How to create modified lists with `comprehension lists`.
59+
- How to make copies from another list and understanding the differece between `copy by reference` and `by value`.
60+
- How to check value validity (if is true) with methods such as `all` or `any`.
61+
- How to use `set`.
62+
- Use built-in converter methods like `enumerate`.
5563

5664
## Other practice repos
5765
| Node | React | Angular | GraphQL | HTML & CSS | Styling | Typescript | NextJs | Docker |

notes.txt

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,3 @@ REPL
1111
- Print > print the result
1212
- Loop > wait for the next line to be executed
1313

14-
Types of data I will be using during this course
15-
- numbers > integers and floats
16-
- strings
17-
- booleans

0 commit comments

Comments
 (0)