Εργαστήριο 6 στην Python
Θέματα που εξετάζονται στο εργαστήριο: συναρτησιακός προγραμματισμός με την Python, λάμδα συναρτήσεις (lambda functions), συναρτήσεις υψηλότερης τάξης (higher order functions), μερικές συναρτήσεις (partial functions), γεννήτριες (generator functions και generator expressions) και οκνηρή αποτίμηση (lazy evaluation), iterators, list comprehensions, βιβλιοθήκες itertools και functools.
Άσκηση Ε6Α1 (λάμδα συναρτήσεις και συναρτήσεις υψηλότερης τάξης) - Δίνεται η ακόλουθη λίστα από πλειάδες που αναπαριστούν παραγγελίες ενός ηλεκτρονικού καταστήματος:
orders = [
("A101", "laptop", 1200, 1),
("A102", "mouse", 25, 2),
("A103", "keyboard", 75, 1),
("A104", "monitor", 300, 2),
("A105", "laptop", 1100, 1),
("A106", "mouse", 20, 3),
("A107", "monitor", 280, 1)
]
Κάθε πλειάδα έχει τη μορφή: (order_id, product, price_per_unit, quantity).
Να γράψετε πρόγραμμα που χωρίς χρήση for-loops και αξιοποιώντας lambda συναρτήσεις και higher-order functions (συναρτήσεις map, filter, sorted και από το functools η συνάρτηση reduce) να εκτελεί τα ακόλουθα:
- Υπολογισμός συνολικής αξίας κάθε παραγγελίας. Δηλαδή, δημιουργήστε μια νέα λίστα όπου κάθε στοιχείο θα έχει τη μορφή: (order_id, product, total_value), όπου total_value = price_per_unit * quantity
- Φιλτράρισμα παραγγελιών με συνολική αξία πάνω από 100 ευρώ
- Ομαδοποίηση order_id ανά προϊόν
- Ταξινομήστε τα προϊόντα κατά συνολική αξία σε φθίνουσα σειρά
- Εύρεση προϊόντος με τα μεγαλύτερα συνολικά έσοδα από όλες τις παραγγελίες
Λύση άσκησης E6A1
| e6a1.py |
|---|
| from functools import reduce
orders = [
("A101", "laptop", 1200, 1),
("A102", "mouse", 25, 2),
("A103", "keyboard", 75, 1),
("A104", "monitor", 300, 2),
("A105", "laptop", 1100, 1),
("A106", "mouse", 20, 3),
("A107", "monitor", 280, 1)
]
# 1. Συνολική αξία κάθε παραγγελίας
order_totals = list(map(
lambda o: (o[0], o[1], o[2] * o[3]),
orders
))
print("Συνολική αξία κάθε παραγγελίας:")
print(order_totals)
# 2. Παραγγελίες με συνολική αξία πάνω από 100 ευρώ
orders_over_100 = list(filter(
lambda o: o[2] > 100,
order_totals
))
print("\nΠαραγγελίες άνω των 100 ευρώ:")
print(orders_over_100)
# 3. Ομαδοποίηση order_id ανά προϊόν
orders_by_product = reduce(
lambda acc, o: {
**acc,
o[1]: acc.get(o[1], []) + [o[0]]
},
orders,
{}
)
print("\nOrder IDs ανά προϊόν:")
print(orders_by_product)
# 4. Συνολικά έσοδα ανά προϊόν
revenue_by_product = reduce(
lambda acc, o: {
**acc,
o[1]: acc.get(o[1], 0) + o[2] * o[3]
},
orders,
{}
)
sorted_products = sorted(
revenue_by_product.items(),
key=lambda x: x[1],
reverse=True
)
print("\nΠροϊόντα κατά συνολική αξία φθίνουσα:")
print(sorted_products)
# 5. Προϊόν με τα μεγαλύτερα συνολικά έσοδα
top_product = reduce(
lambda a, b: a if a[1] > b[1] else b,
sorted_products
)
print("\nΠροϊόν με τα μεγαλύτερα συνολικά έσοδα:")
print(top_product)
|
Άσκηση Ε6Α2 (μερική αποτίμηση συναρτήσεων) - Δίνεται η παρακάτω συνάρτηση που υπολογίζει την τελική αξία μιας παραγγελίας, λαμβάνοντας υπόψη την τιμή μονάδας price, την ποσότητα quantity, την έκπτωση discount και τον φόρο tax:
def calculate_price(price, quantity, discount, tax):
return price * quantity * (1 - discount) * (1 + tax)
Να γράψετε πρόγραμμα που να χρησιμοποιεί partial function evaluation ώστε:
- Να δημιουργεί μια νέα συνάρτηση calculate_with_tax, στην οποία ο φόρος να είναι σταθερός στο 24%.
- Να δημιουργεί μια νέα συνάρτηση calculate_with_discount, στην οποία η έκπτωση να είναι σταθερή στο 10%.
- Να δημιουργεί μια νέα συνάρτηση calculate_standard_order, στην οποία τόσο ο φόρος όσο και η έκπτωση να είναι σταθερά στο 24% και 10% αντίστοιχα.
- Να καλεί τις νέες συναρτήσεις για διαφορετικές τιμές προϊόντων και ποσοτήτων και να εμφανίζει τα αποτελέσματα με κατάλληλα μηνύματα.
Λύση άσκησης E6A2
| e6a2.py |
|---|
| from functools import partial
def calculate_price(price, quantity, discount, tax):
return price * quantity * (1 - discount) * (1 + tax)
# Σταθερός φόρος 24%
calculate_with_tax = partial(calculate_price, tax=0.24)
# Σταθερή έκπτωση 10%
calculate_with_discount = partial(calculate_price, discount=0.10)
# Σταθερός φόρος 24% και σταθερή έκπτωση 10%
calculate_standard_order = partial(calculate_price, discount=0.10, tax=0.24)
print("Με σταθερό φόρο 24%:")
print(calculate_with_tax(price=100, quantity=2, discount=0.10))
print("\nΜε σταθερή έκπτωση 10%:")
print(calculate_with_discount(price=100, quantity=2, tax=0.24))
print("\nΜε σταθερό φόρο 24% και έκπτωση 10%:")
print(calculate_standard_order(price=100, quantity=2))
print(calculate_standard_order(price=50, quantity=5))
print(calculate_standard_order(price=1200, quantity=1))
|
Άσκηση Ε6Α3 (γεννήτριες) - Να γράψετε μια γεννήτρια συνάρτηση running_totals(numbers) η οποία δέχεται μια λίστα αριθμών και παράγει σταδιακά τα αθροίσματα των στοιχείων της λίστας. Για παράδειγμα, για τη λίστα numbers = [4, 7, 2, 10] η γεννήτρια θα πρέπει να παράγει τις τιμές 4, 11, 13, 23 σταδιακά με κάθε κλήση την εντολής yield. Καλέστε την yield σε μια for.
Λύση άσκησης E6A3
| e6a3.py |
|---|
| def running_totals(numbers):
total = 0
for n in numbers:
total += n
yield total
numbers = [4, 7, 2, 10]
for value in running_totals(numbers):
print(value)
|
Άσκηση Ε6Α4 (memoization)
Ο ακόλουθος κώδικας υπολογίζει αναδρομικά τον n-οστό όρο της ακολουθίας Fibonacci (0,1,1,2,3,5,8,13,...).
| e6a4a.py |
|---|
| def fibonacci(n):
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
x = fibonacci(40)
print(x)
|
Ωστόσο, καθυστερεί υπερβολικά για τιμές του n από 40 και πάνω. Εντοπίστε την τιμή του n για την οποία ο χρόνος υπολογισμού του n-οστού όρου είναι περισσότερο από 10 δευτερόλεπτα.
Άσκηση E6A4 (α΄ μέρος)
| e6a4b.py |
|---|
| def fibonacci(n):
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
import time
n = 0
while True:
start = time.perf_counter()
result = fibonacci(n)
elapsed = time.perf_counter() - start
print(f"n={n}, fib={result}, time={elapsed:.4f} sec")
if elapsed > 10:
break
n += 1
|
Στη συνέχεια προτείνετε λύση με το functools @cache που να λύνει το θέμα της ταχύτητας υπολογισμού.
Άσκηση E6A4 (β΄ μέρος)
| e6a4c.py |
|---|
| import time
from functools import cache
@cache
def fibonacci_cached(n):
if n <= 1:
return n
return fibonacci_cached(n - 1) + fibonacci_cached(n - 2)
n = 50
start = time.perf_counter()
result = fibonacci_cached(n)
elapsed = time.perf_counter() - start
print(f"With cache: n={n}, fib={result}, time={elapsed:.8f} sec")
|
Άσκηση Ε6Α5 itertools
Να υλοποιήσετε τις παρακάτω συναρτήσεις, ώστε να περνάνε τα unittests, χρησιμοποιώντας τις συναρτήσεις του module itertools: count, combinations, product, accumulate, cycle, islice.
| e6a5.py |
|---|
| import unittest
from itertools import (
count,
combinations,
product,
accumulate,
cycle,
islice,
)
# TODO: Υλοποιήστε τις συναρτήσεις
def first_numbers(start, step, n):
pass
def pair_combinations(items):
pass
def cartesian_product(a, b):
pass
def cumulative_sums(numbers):
pass
def repeat_pattern(pattern, n):
pass
class TestItertools(unittest.TestCase):
def test_count(self):
self.assertEqual(
first_numbers(0, 2, 5),
[0, 2, 4, 6, 8]
)
def test_combinations(self):
self.assertEqual(
pair_combinations(["A", "B", "C"]),
[("A", "B"), ("A", "C"), ("B", "C")]
)
def test_product(self):
self.assertEqual(
cartesian_product([1, 2], ["x", "y"]),
[(1, "x"), (1, "y"), (2, "x"), (2, "y")]
)
def test_accumulate(self):
self.assertEqual(
cumulative_sums([1, 2, 3, 4]),
[1, 3, 6, 10]
)
def test_cycle(self):
self.assertEqual(
repeat_pattern(["A", "B"], 5),
["A", "B", "A", "B", "A"]
)
if __name__ == "__main__":
unittest.main()
|
Άσκηση E6A5
| e6a5sol.py |
|---|
| import unittest
from itertools import (
count,
combinations,
product,
accumulate,
cycle,
islice,
)
def first_numbers(start, step, n):
return list(islice(count(start, step), n))
def pair_combinations(items):
return list(combinations(items, 2))
def cartesian_product(a, b):
return list(product(a, b))
def cumulative_sums(numbers):
return list(accumulate(numbers))
def repeat_pattern(pattern, n):
return list(islice(cycle(pattern), n))
class TestItertools(unittest.TestCase):
def test_count(self):
self.assertEqual(
first_numbers(0, 2, 5),
[0, 2, 4, 6, 8]
)
def test_combinations(self):
self.assertEqual(
pair_combinations(["A", "B", "C"]),
[("A", "B"), ("A", "C"), ("B", "C")]
)
def test_product(self):
self.assertEqual(
cartesian_product([1, 2], ["x", "y"]),
[(1, "x"), (1, "y"), (2, "x"), (2, "y")]
)
def test_accumulate(self):
self.assertEqual(
cumulative_sums([1, 2, 3, 4]),
[1, 3, 6, 10]
)
def test_cycle(self):
self.assertEqual(
repeat_pattern(["A", "B"], 5),
["A", "B", "A", "B", "A"]
)
if __name__ == "__main__":
unittest.main()
|
Άσκηση Ε6Α6 (all και any)
Δίνεται μια λίστα από φοιτητές, όπου κάθε φοιτητής αναπαρίσταται από ένα tuple της μορφής (name, passed) όπου name είναι το όνομα του φοιτητή και το passed που είναι True αν ο φοιτητής πέρασε ένα μάθημα διαφορετικά είναι False. Δίνεται ο ακόλουθος κώδικας που ελέγχει α) αν όλοι οι φοιτητές έχουν περάσει το μάθημα και β) αν υπάρχει τουλάχιστον ένας φοιτητής που δεν πέρασε το μάθημα.
| e6a6.py |
|---|
| #fmt:off
students = [
("Anna", True),
("Nikos", True),
("Maria", False),
("Giorgos", True)
]
#fmt:on
all_passed = True
someone_failed = False
for name, passed in students:
if not passed:
all_passed = False
someone_failed = True
print("All students passed:", all_passed)
print("At least one student failed:", someone_failed)
|
Υλοποιήστε την ίδια λειτουργικότητα χρησιμοποιώντας τις συναρτήσεις all() και any().
Άσκηση E6A6
| e6a6sol.py |
|---|
| #fmt:off
students = [
("Anna", True),
("Nikos", True),
("Maria", False),
("Giorgos", True)
]
#fmt:on
all_passed = all(passed for name, passed in students)
someone_failed = any(not passed for name, passed in students)
print("All students passed:", all_passed)
print("At least one student failed:", someone_failed)
|