# Lezione 16 - Le strutture dati (parte 3)

# ---==== TUPLE ====---

# Le tuple sono una struttura dati simile alle liste, ma immutabile. Questo
# significa che, una volta creata, una tupla non può essere modificata. Le
# tuple sono definite tra parentesi tonde e i loro elementi sono separati da
# virgole.

# Esempio:
t = (1, 2, 3, 4, 5)
print(t)

# Output:
# (1, 2, 3, 4, 5)

# Per accedere agli elementi di una tupla, si può utilizzare l'indice dell'elemento
# come per le liste.

# Esempio:
print(t[0])
print(t[2])

# Output:
# 1
# 3

# Le tuple possono contenere elementi di diversi tipi.

# Esempio:
t = (1, 'due', 3.0, [4, 5, 6])

# È possibile creare una tupla con un solo elemento aggiungendo una virgola dopo
# l'elemento.

# Esempio:
t = (1,)

# Per creare una tupla vuota, si possono utilizzare le parentesi tonde senza
# elementi.

# Esempio:
t = ()

# Le tuple possono essere utilizzate per restituire più valori da una funzione.

# Esempio:
def divisione_e_resto(dividendo, divisore):
    quoziente = dividendo // divisore
    resto = dividendo % divisore
    return quoziente, resto

q, r = divisione_e_resto(10, 3)

# Se si assegna il risultato della funzione che restituisce una tupla a una
# sola variabile, questa diventa una tupla.

# Esempio:
risultato = divisione_e_resto(10, 3)
print(risultato)

# Output:
# (3, 1)

print(type(risultato))

# Output:
# <class 'tuple'>

# In cosa differiscono le tuple dalle liste?
# - Le tuple sono immutabili, mentre le liste sono mutabili. Questo significa
#   che non è possibile modificare gli elementi di una tupla dopo che è stata
#   creata.
# - Le tuple sono definite tra parentesi tonde, mentre le liste sono definite
#   tra parentesi quadre.
# - Le tuple sono più veloci delle liste, perché richiedono meno memoria.

# ---==== SET ====---

# Un set è una collezione non ordinata di elementi unici. I set sono definiti
# tra parentesi graffe e i loro elementi sono separati da virgole.

# Esempio:
s = {1, 2, 3, 4, 5}
print(s)

# Output:
# {1, 2, 3, 4, 5}

# I set non possono contenere elementi duplicati.

# Esempio:
s = {1, 2, 3, 4, 5, 1, 2, 3, 4, 5}
print(s)

# Output:
# {1, 2, 3, 4, 5}

# Per accedere agli elementi di un set, si può utilizzare un ciclo for.

# Esempio:
for elemento in s:
    print(elemento)

# Output:
# 1
# 2
# ...

# I set possono contenere elementi di diversi tipi.

# Esempio:
s = {1, 'due', 3.0, (4, 5, 6)}

# È possibile creare un set vuoto con la funzione set().

# Esempio:
s = set()

# I set sono utili per rimuovere i duplicati da una lista.

# Esempio:
l = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]
s = set(l) # Questo è un type casting (come int(), float(), ecc.)
l = list(s)
print(l)
print(s)

# Output:
# [1, 2, 3, 4, 5]
# {1, 2, 3, 4, 5}

# La differenza tra tuple e set è che i set sono mutabili, mentre le tuple sono
# immutabili. Questo significa che è possibile aggiungere o rimuovere elementi
# da un set dopo che è stato creato.

# La differenza tra set e liste è che i set non sono ordinati, mentre le liste
# sono ordinate. Questo significa che non è possibile accedere agli elementi di
# un set utilizzando l'indice dell'elemento.
# Inoltre, i set non possono contenere elementi duplicati, mentre le liste possono.

# ---==== DIZIONARI ====---

# I dizionari sono effettivamente l'altra importante struttura dati in Python.
# Le tuple e i set, invece, non vengono utilizzati così spesso. I dizionari
# sono simili alle liste, ma invece di avere indici numerici, hanno delle chiavi
# che possono essere di qualsiasi tipo. I dizionari sono definiti tra parentesi
# graffe e le coppie chiave-valore sono separate da due punti.

# Esempio:
dizionario = {'ambrogio': 1,
     'biero': 2,
     'carlone': 3}
print(dizionario)

# Output:
# {'ambrogio': 1, 'biero': 2, 'carlone': 3}

# Per accedere ai valori di un dizionario, si può utilizzare la
# chiave dell'elemento.

# Esempio:
print(dizionario['ambrogio'])
print(dizionario['carlone'])

# Output:
# 1
# 3

# print(dizionario['drugo'])
# Output: ERRORE (KeyError)

# È possibile aggiungere nuove coppie chiave-valore a un dizionario.

# Esempio:
dizionario['drugo'] = ["drugo", "il", "grande", "laureato"]
print(dizionario)

# Output:
# {'ambrogio': 1, 'biero': 2, 'carlone': 3, 'drugo': ["drugo", "il", "grande", "laureato"]}

# È possibile rimuovere una coppia chiave-valore da un dizionario utilizzando
# la parola chiave "del".

# Esempio:
del dizionario['ambrogio']
print(dizionario)

# Output:
# {'biero': 2, 'carlone': 3, 'drugo': ["drugo", "il", "grande", "laureato"]}

# È possibile utilizzare un ciclo for per accedere alle chiavi e ai valori di un
# dizionario.

# Esempio:
for var in dizionario:
    print(var, dizionario[var])

# Output:
# biero 2
# carlone 3
# drugo ["drugo", "il", "grande", "laureato"]

# È possibile utilizzare il metodo "items" per ottenere una lista di tuple che
# contengono le coppie chiave-valore di un dizionario.

# Esempio:
print(dizionario.items())

# Output:
# dict_items([('biero', 2), ('carlone', 3), ('drugo', ["drugo", "il", "grande", "laureato"])])

for chiave, valore in dizionario.items():
    print(chiave, valore)

# È possibile utilizzare il metodo "keys" per ottenere una lista delle chiavi di
# un dizionario.

# Esempio:
print(dizionario.keys())

# Output:
# dict_keys(['biero', 'carlone', 'drugo'])

# È possibile utilizzare il metodo "values" per ottenere una lista dei valori di
# un dizionario.

# Esempio:
print(dizionario.values())

# Output:
# dict_values([2, 3, ["drugo", "il", "grande", "laureato"]])

# I dizionari sono utili per memorizzare informazioni strutturate. Ad esempio,
# si possono utilizzare i dizionari per memorizzare informazioni sugli studenti
# di una scuola.

# Esempio:
studenti = {
    'Filibertozzo': {
        'età': 15,
        'voti': [8, 7, 6, 9]
    },
    'Giangiovanni': {
        'età': 16,
        'voti': [7, 6, 8, 9]
    },
    'T': {
        'età': 15,
        'voti': [9, 9, 9, 9]
    }
}

for studente in studenti:
    print(f"Lo studente {studente} ha {studenti[studente]['età']} anni e "
          f"ha preso i seguenti voti:\n{studenti[studente]['voti']}")

# In questo esempio, il dizionario "studenti" contiene tre coppie chiave-valore,
# dove la chiave è il nome dello studente e il valore è un altro dizionario che
# contiene l'età e i voti dello studente.

# Uso della funzione dict()

# È possibile creare un dizionario utilizzando la funzione dict(). Questa funzione
# accetta una lista di tuple come argomento, dove ogni tupla contiene una coppia
# chiave-valore.

# Esempio:
d = dict([('a', 1), ('b', 2), ('c', 3)])
print(d)

lista = ["ciao", "drugo", 5, [1, 2, 3]]
# Corrisponde al dizionario:
dizionario = {
    0: "ciao",
    1: "drugo",
    2: 5,
    3: [1, 2, 3]
}