📋 PROMEMORIA: TUPLE E METODI DELLE TUPLE IN PYTHON


# ═══════════════════════════════════════════════════════════════════════════
# ═══════════════════════════════════════════════════════════════════════════

# ─────────────────────────────────────────────────────────────────────────────
# 🎬 COSA SONO LE TUPLE
# ─────────────────────────────────────────────────────────────────────────────
# Le tuple sono strutture dati simili alle liste ma con una differenza chiave:
# 👉 SONO IMMUTABILI → non possono essere modificate dopo la creazione
#
# Quando usarle:
# • Dati che devono rimanere costanti (ID prodotti, categorie, configurazioni)
# • Quando l'integrità dei dati è essenziale
# • Per proteggere informazioni da modifiche accidentali


# ─────────────────────────────────────────────────────────────────────────────
# 🧱 CARATTERISTICHE FONDAMENTALI DELLE TUPLE
# ─────────────────────────────────────────────────────────────────────────────
# 1. ORDINATE → mantengono l'ordine originale degli elementi
# 2. IMMUTABILI → NON si possono aggiungere, rimuovere o modificare elementi
# 3. INDICIZZATE → ogni elemento ha una posizione numerica (0, 1, 2...)
# 4. AMMETTONO DUPLICATI → lo stesso valore può apparire più volte
# 5. VERSATILI → possono contenere tipi di dati misti


# ─────────────────────────────────────────────────────────────────────────────
# ✔ CREAZIONE DELLE TUPLE
# ─────────────────────────────────────────────────────────────────────────────

# Metodo 1: Con parentesi tonde ()
grocery_aisles = ("Produce", "Dairy", "Bakery", "Meat", "Frozen Foods")

# Metodo 2: Senza parentesi (tuple packing)
coordinates = 10, 20, 30

# Tupla con un solo elemento (virgola obbligatoria!)
single_item = ("apple",)  # Corretto
not_a_tuple = ("apple")   # Questo è solo una stringa!

# Tupla vuota
empty_tuple = ()

# Tupla con tipi misti
apple_details = ("apple", 34, True, 1.99, "Fuji")


# ─────────────────────────────────────────────────────────────────────────────
# 🔍 ACCESSO AGLI ELEMENTI
# ─────────────────────────────────────────────────────────────────────────────

# Le tuple usano l'indicizzazione come le liste
apple_details = ("apple", 34, True, 1.99, "Fuji")

print(apple_details[0])  # → "apple" (primo elemento)
print(apple_details[4])  # → "Fuji" (ultimo elemento)
print(apple_details[-1])  # → "Fuji" (ultimo elemento, indice negativo)

# Display della tupla completa
grocery_aisles = ("Produce", "Dairy", "Bakery", "Meat", "Frozen Foods")
print("Grocery Aisles:", grocery_aisles)


# ─────────────────────────────────────────────────────────────────────────────
# 🔒 IMMUTABILITÀ DELLE TUPLE
# ─────────────────────────────────────────────────────────────────────────────

# ❌ OPERAZIONI NON PERMESSE:
grocery_aisles = ("Produce", "Dairy", "Bakery")

# grocery_aisles[0] = "Vegetables"  # ❌ ERRORE! Non si può modificare
# grocery_aisles.append("Meat")     # ❌ ERRORE! Non esiste questo metodo
# grocery_aisles.remove("Dairy")    # ❌ ERRORE! Non esiste questo metodo
# del grocery_aisles[1]             # ❌ ERRORE! Non si può rimuovere


# ─────────────────────────────────────────────────────────────────────────────
# ⚠️ TUPLE CON OGGETTI MUTABILI (CASO SPECIALE)
# ─────────────────────────────────────────────────────────────────────────────
# Le tuple sono immutabili, MA gli oggetti mutabili al loro interno
# (come liste) POSSONO essere modificati

# Tupla contenente una lista
apple_details = ("apple", 34, True, 1.99, "Fuji",
                 ["Washington", "California", "Michigan"])

print(apple_details)
# Output: ("apple", 34, True, 1.99, "Fuji", ["Washington", "California", "Michigan"])

# ✅ Possiamo modificare la lista DENTRO la tupla
apple_details[5][2] = "Pennsylvania"

print(apple_details)
# Output: ("apple", 34, True, 1.99, "Fuji", ["Washington", "California", "Pennsylvania"])

# NOTA: La tupla stessa non è cambiata (contiene ancora gli stessi 6 elementi),
#       ma il contenuto della lista al suo interno è stato modificato


# ─────────────────────────────────────────────────────────────────────────────
# 🧰 METODI DELLE TUPLE
# ─────────────────────────────────────────────────────────────────────────────
# Le tuple hanno SOLO 2 metodi (non hanno metodi per modificare il contenuto):

# 1. .count(value) → Conta quante volte un valore appare nella tupla
# 2. .index(value) → Restituisce l'indice della PRIMA occorrenza di un valore


# ─────────────────────────────────────────────────────────────────────────────
# 📌 METODO 1: .count(value)
# ─────────────────────────────────────────────────────────────────────────────

fruits = ("apple", "banana", "cherry", "apple", "banana", "cherry", "apple")

# Contare quante volte appare "apple"
apple_count = fruits.count("apple")
print("Number of times 'apple' appears:", apple_count)  # → 3

# Contare quante volte appare "banana"
banana_count = fruits.count("banana")
print("Banana count:", banana_count)  # → 2

# Se l'elemento non esiste, restituisce 0
orange_count = fruits.count("orange")
print("Orange count:", orange_count)  # → 0


# ─────────────────────────────────────────────────────────────────────────────
# 📌 METODO 2: .index(value)
# ─────────────────────────────────────────────────────────────────────────────

fruits = ("apple", "banana", "cherry", "apple", "banana", "cherry", "apple")

# Trovare l'indice della PRIMA occorrenza di "cherry"
cherry_index = fruits.index("cherry")
print("The first occurrence of 'cherry' is at index:", cherry_index)  # → 2

# Trovare l'indice di "apple" (prima occorrenza)
apple_index = fruits.index("apple")
print("Apple index:", apple_index)  # → 0

# ⚠️ Se l'elemento non esiste, genera un ValueError
# orange_index = fruits.index("orange")  # ❌ ERRORE! ValueError


# ─────────────────────────────────────────────────────────────────────────────
# 🧪 ESEMPIO PRATICO: DETTAGLI PRODOTTO
# ─────────────────────────────────────────────────────────────────────────────

spinachDetails = ("Fresh", "Organic", "Baby Spinach", 2.99, 50)

# Accedere a elementi specifici
print("Descrizione:", spinachDetails[2])  # → "Baby Spinach"
print("Prezzo:", spinachDetails[3])       # → 2.99
print("Quantità:", spinachDetails[4])     # → 50

# Trovare la posizione di "Baby Spinach"
description_index = spinachDetails.index("Baby Spinach")
print("Index of 'Baby Spinach':", description_index)  # → 2


# ─────────────────────────────────────────────────────────────────────────────
# 🧪 ESEMPIO: VENDITE GIORNALIERE
# ─────────────────────────────────────────────────────────────────────────────

itemsSold = ("milk", "eggs", "milk", "bread", "eggs", "milk")

# Contare quante volte è stato venduto il latte
milk_sales = itemsSold.count("milk")
print("Milk sold:", milk_sales)  # → 3

# Contare le uova vendute
eggs_sales = itemsSold.count("eggs")
print("Eggs sold:", eggs_sales)  # → 2


# ─────────────────────────────────────────────────────────────────────────────
# 💡 CONFRONTO: LISTE VS TUPLE
# ─────────────────────────────────────────────────────────────────────────────

# LISTE (mutabili - parentesi quadre [])
inventory_list = ["apple", "banana", "cherry"]
inventory_list.append("orange")      # ✅ OK
inventory_list.remove("banana")      # ✅ OK
inventory_list[0] = "grape"          # ✅ OK

# TUPLE (immutabili - parentesi tonde ())
store_categories = ("Produce", "Dairy", "Bakery")
# store_categories.append("Meat")    # ❌ ERRORE!
# store_categories.remove("Dairy")   # ❌ ERRORE!
# store_categories[0] = "Vegetables" # ❌ ERRORE!

# Quando usare cosa:
# LISTE → quando i dati devono cambiare (inventario, carrello)
# TUPLE → quando i dati devono rimanere fissi (categorie, ID, coordinate)


# ─────────────────────────────────────────────────────────────────────────────
# ✅ ESERCIZI DI VERIFICA
# ─────────────────────────────────────────────────────────────────────────────

# Esercizio 1: Contare occorrenze
itemsSold = ("milk", "eggs", "milk", "bread", "eggs", "milk")
print(itemsSold.count("milk"))
# Soluzione: 3

# Esercizio 2: Trovare indice
spinachDetails = ("Fresh", "Organic", "Baby Spinach", 2.99, 50)
position = spinachDetails.index("Baby Spinach")
print(position)
# Soluzione: 2


# ─────────────────────────────────────────────────────────────────────────────
# 💡 NOTE IMPORTANTI
# ─────────────────────────────────────────────────────────────────────────────

# • Le tuple sono più efficienti in memoria rispetto alle liste
# • Usare tuple per dati che non devono cambiare (protezione)
# • Le tuple possono essere usate come chiavi nei dizionari (le liste no!)
# • .index() restituisce solo la PRIMA occorrenza
# • .index() genera errore se l'elemento non esiste
# • Una tupla con un solo elemento DEVE avere la virgola: ("item",)
# • Gli oggetti mutabili dentro una tupla possono essere modificati

# ═══════════════════════════════════════════════════════════════════════════
# Fine del promemoria
# ═══════════════════════════════════════════════════════════════════════════