Vai al contenuto

Modulo CSV

In questo capitolo, per lavorare con il modulo csv, introdurremo prima una struttura dati che completa la nostra conoscenza sulle sequenze: i dizionari!

Questi saranno lo strumento tramite il quale lavoreremo poi con i dati su file e il modulo csv.

Dizionari

I dizionari (dict) sono un tipo Python predefinito, sequenza, mutabile, non ordinato (e non ordinabile) che contiene elementi (items) formati ognuno da una coppia chiave (key): valore (value).

Per dichiarare gli elementi di un dizionario si utilizzano le parentesi graffe e questo ci fa capire che i tipi predefiniti sono finiti, perché sono finiti i tipi di parentesi esistenti 😄

Vediamo con un esempio il concetto:

prefisso = { "IT": 39, "ES": 34, "DE": 49}

In questo semplice esempio abbiamo memorizzato i prefissi internazionali di alcuni stati: data la chiave (il nome dello stato) si può visualizzare il prefisso corrispondente (il "valore"), ricorrendo però alle parentesi quadre:

print( prefisso["IT"] ) # scrive 39

Per completezza, ricordo che è possibile verificare il tipo della variabile con il comando type()

type(prefisso)    # vale <class 'dict'>

e che è possibile creare un dizionario vuoto semplicemente utilizzando le parentesi graffe: essendo i dizionari un tipo mutabile sarà sempre possibile aggiungervi dati successivamente.

dizionarioVuoto = {}

Torniamo all'esempio del dizionario dei prefissi e vediamo come è semplice inserire un nuova coppia chiave: valore nel dizionario. Occhio all'utilizzo delle parentesi, che in questo caso sono le quadre:

prefisso["FR"] = 34
print(prefisso)
{ "IT": 39 , "ES": 34 , "DE": 49 , "FR": 34 }

Come vedete ci sono 2 stati ("ES" e "FR") che hanno lo stesso valore, lo stesso prefisso. Questo è assolutamente possibile nei dizionari. I dizionari però, non ammettono valori duplicati nelle chiavi, quindi non è possibile inserire due chiavi uguali per valori diversi. Da questa considerazione si capisce anche che le chiavi di un dizionario devono essere per forza tipi immutabili (ad esempio numeri, stringhe, tuple, etc… solitamente interi o stringhe) mentre per i valori si può dare spazio alla fantasia.

L'inserimento dei dati dipende dall'esistenza o meno della chiave nel dizionario:

# se la chiave non esiste, viene aggiunta una nuova coppia nel dizionario
prefisso["UK"] = 44
# se la chiave è già presente, viene modificato il valore in essa
prefisso["FR"] = 33
print(prefisso)
{ "IT": 39, "ES": 34, "DE": 49, "FR": 33, "UK": 44 }

I dizionari non possono essere ordinati!!!

Non è possibile ordinare i valori presenti in un dizionario. Per procedere ad una ricerca bisogna fare affidamento solo sulle chiavi!!!

Lavorare con i dizionari

Per le nostre considerazioni, partiremo analizzando il dizionario "prefisso" già definito:

print(prefisso)
{ "IT": 39, "ES": 34, "DE": 49, "FR": 33, "UK": 44 }


Come sappiamo, è possibile interrogare una sequenza per sapere se un elemento è presente in essa. Nei dizionari l'istruzione IN funziona con le chiavi:

"IT" in prefisso    # vale True
"US" in prefisso    # vale False

Quindi, il seguente codice:

if "IT" in prefisso:
    print("il prefisso \"IT\" è presente e vale", prefisso["IT"])
else:
    print("il prefisso \"IT\" non è presente")

scriverà:

il prefisso "IT" è presente e vale 39


L'istruzione FOR...IN che ben conosciamo, scorre le chiavi dei dizionari:

for chiave in prefisso:
    print(chiave)
IT
ES
DE
FR
UK

Questo significa che è semplicissimo usare il FOR...IN per ottenere le chiavi e visualizzare dunque i valori contenuti nel dizionario, con la sintassi dizionario[chiave] = valore:

for chiave in prefisso:
    print( "Prefisso", chiave, ":", prefisso[chiave] )
Prefisso IT : 39
Prefisso ES : 34
Prefisso DE : 49
Prefisso FR : 33
Prefisso UK : 44


I dizionari supportano inoltre alcuni metodi aggiuntivi tipici della classe dict. Dopo la tabella riassuntiva trovate alcuni esempi esplicativi.

Metodo Descrizione
dic.keys() Restituisce una sequenza speciale (tipo una lista) che contiene tutte le chiavi del dizionario dic, aggiornate in tempo reale.
dic.values() Restituisce una sequenza speciale (tipo una lista) che contiene tutti i valori del dizionario dic, aggiornati in tempo reale.
dic.get(key, default) Restituisce il valore corrispondente a key, oppure default se non esiste.
Se default è nullo, non restituisce nulla.
dic.pop(key, default) Restituisce il valore corrispondente a key e lo elimina dal dizionario. Se non presente restituisce default.
Se non presente e default non impostato, genera un errore chiamato KeyError.
dic.copy() Clona dic
dic.clear() Cancella dic, ovvero rimuove tutti i suoi elementi

Esempio 1: keys

chiavi = prefisso.keys()
print(chiavi)    # scrive dict_keys( ["IT","ES","DE","FR","UK"] )... simile ad una lista, vedete?
for k in chiavi:
    print(k)
IT
ES
DE
FR
UK
prefisso["PR"] = 25 # aggiungo un elemento al dizionario
print(chiavi)
dict_keys( ["IT","ES","DE","FR","UK","PR"] ) # valori aggiornati

Esempio 2: values

valori = prefisso.values()
print(valori)     # scrive dict_values( [39, 34, 49, 33, 44] ) ... simile ad una lista, vedete?
for v in valori:
    print(k)
39
34
49
33
44
prefisso["PR"] = 25 # aggiungo un elemento al dizionario
print(valori)
dict_values( [39, 34, 49, 33, 44, 25] ) # valori aggiornati

Esempio 3: get

# prefisso vale { "IT": 39, "ES": 34, "DE": 49, "FR": 33, "UK": 44 }

prefisso.get("US")      # la chiave "US" non c'è. Non ritorna nulla
prefisso.get("US", 0)   # "US" chiave non presente, ritorna 0
prefisso.get("IT")      # "IT" chiave presente, ritorna il valore 39
prefisso.get("IT", 0)   # essendo "IT" presente, ritorna comunque 39

Esempio 4: pop

# prefisso vale { "IT": 39, "ES": 34, "DE": 49, "FR": 33, "UK": 44 }

prefisso.pop("IT")    # chiave "IT" presente, ritorna il valore 39
print(prefisso)       # scrive { "ES": 34, "DE": 49, "FR": 33, "UK": 44 }
                      # la coppia "IT":39 è stata eliminata dal dizionario

prefisso.pop("US")    # la chiave "US" non c'è. ERRORE
KeyError
prefisso.pop("US", 0) # valore di default impostato a 0, ritorna 0

Tip

con un valore di default impostato pop() non da mai errore!!!

Spero che osservando gli esempi sia tutto chiaro sul funzionamento dei dizionari e sulle funzioni disponibili per essi. Mi raccomando di provare voi stessi a riprodurre gli esempi per meglio capire quello che succede. E poi di provare a fare gli esercizi qui sotto :)

Esercizi sui dizionari


Esercizio 801

Creare un dizionario vuoto, chiamato DatiPersonali. Chiedere all'utente di inserire i propri dati personali (nome, cognome, indirizzo, numero civico, città, provincia, CAP) e inserirli nel dizionario con il tipo di dato come chiave (ad esempio "nome") e il dato inserito dall'utente come valore.

Visualizzare i dati contenuti nel dizionario tramite un ciclo for..in sulle chiavi del dizionario.


Esercizio 802

Definire un dizionario vuoto chiamato "quadrati" e inserirvi all'interno una serie di valori tramite codice: le chiavi saranno i numeri interi da 1 a 100 e i valori saranno i quadrati delle chiavi.

Visualizzare tutto il dizionario scrivendo per ogni riga: "chiave" al quadrato = "valore"


Esercizio 803

Definire un dizionario vuoto chiamato "dispari" e inserirvi all'interno una serie di valori tramite codice: le chiavi saranno i numeri da 0 a 99 e i valori saranno i primi 100 numeri dispari. Mostrare il dizionario così ottenuto, visualizzando per ogni elemento la stringa "chiave: key, numero dispari abbinato: value".

Chiedere all'utente di inserire un numero intero e se esso è una chiave del dizionario, visualizzare il numero corrispondente. Altrimenti visualizzare la scritta "chiave non presente nel dizionario".


Esercizio 804

Definire il dizionario vuoto chiamato "pari" e inserirvi all'interno una serie di valori tramite codice: le chiavi saranno i numeri da 1 a 50 e i valori saranno i primi 50 numeri pari. Mostrare il dizionario così ottenuto, visualizzando per ogni elemento la stringa "chiave: key, numero pari abbinato: value".

Chiedere all'utente di inserire un numero intero e se esso è una chiave del dizionario, eliminare la coppia chiave:valore corrispondente. Altrimenti visualizzare la scritta "chiave non presente nel dizionario".


Esercizio 805

Definire un dizionario, inizialmente vuoto, chiamato "studentiDaInterrogare". All'inizio va riempito con una serie di coppie dove la chiave sono i numeri interi che vanno da 1 in avanti (la posizione sul registro) e il valore è il cognome dello studente.

Definire la lista "interrogati", inizialmente vuota. Estrarre 5 numeri casuali fra 1 e il numero di studenti da interrogare. Se il numero estratto è presente come chiave del dizionario, eliminare da esso l'elemento che corrisponde a quella chiave e inserire quel valore nella lista "interrogati".

Al termine delle 5 estrazioni visualizzare la lista "interrogati" e il dizionario "studentiDaInterrogare".


Esercizio 806

Definire un dizionario vuoto chiamato "Magazzino", che sarà riempito con elementi tali che le chiavi rappresentano il prodotto presente in esso e i valori il numero di pezzi dello stesso. Inserire al suo interno i seguenti prodotti: 25 bastoni, 138 scaffali, 2000 scatole, 50 attaccapanni.

Visualizzare il contenuto del magazzino.

Chiedere all'utente di selezionare un prodotto; se il prodotto è presente, visualizzare il numero di elementi di quel prodotto presenti nel magazzino e permettere all'utente di indicare quanti ne desidera. Il magazzino deve essere aggiornato togliendo dal magazzino i prodotti acquistati dall'utente.
Ad esempio, se l'utente seleziona scaffali, si visualizzi una scritta tipo: "Presenti 25 scaffali. Quanti vuole acquistarne? Se l'utente digita 30, ovviamente il programma insulta l'utente. Se digita, ad esempio, 5, si visualizza una frase tipo "venduti 5 scaffali" e il magazzino viene aggiornato al nuovo numero di scaffali presenti (25 erano, 5 sono stati venduti... 20!!!)

Visualizzare il magazzino, elencando i prodotti rimasti. Per ognuno dei prodotti rimasti, togliere 1 pezzo. Visualizzare di nuovo il magazzino.


Esercizio 807

Definire un dizionario chiamato "cartellaTombola" e riempirlo con 5 numeri interi random fra 1 a 90 diversi fra loro. Essi saranno la chiave dell'elemento inserito, mentre il valore sarà per tutti il booleano False. Visualizzare le chiavi del dizionario "cartellaTombola".

Ad esempio potrebbe essere generato un dizionario tipo { 23: False, 45: False, 56: False, 78: False, 90:False}.

Ripetere 10 volte l'estrazione casuale di un numero compreso fra 1 e 90. Se il numero è una chiave della cartella, modificare a True il valore corrispondente. Alla fine delle 10 estrazioni verificare quanti e quali numeri della cartella sono stati estratti.


Esercizio 808

Permettere all'utente di inserire una parola e creare con questa il dizionario delle frequenze delle lettere. Ad esempio, data una stringa "ababcc", otterremo in risultato {"a": 2, "b": 2, "c": 2, "d": 0…...}.

Visualizzare alla fine la parola e tutti gli elementi del dizionario. Analizzando il dizionario trovare la lettera più frequente nella parola inserita. Visualizzare infine solo gli elementi del dizionario che hanno frequenza positiva.


Esercizio 809

Creare un dizionario ItalianoInglese e inserire una serie di termini con la parola italiana come chiave e la corrispondente traduzione inglese come valore. Ad esempio alla chiave "cane" viene abbinato il valore "dog".

Data una qualunque stringa da parte dell'utente procedere a visualizzare la stringa "tradotta" sostituendo tutte le parole trovate presenti nel dizionario con la corrispondente parola inglese.


Esercizio 810

Definire un dizionario, chiamato "voti", inizialmente vuoto. In esso dovranno essere aggiunti i voti di tutte le vostre materie, in modo tale che il nome della materia sia la chiave ed il voto corrispondente il valore. Visualizzare il dizionario con un ciclo, visualizzando in ogni riga la chiave (il nome della materia) e il valore corrispondente (il voto).

  • Calcolare la media aritmetica dei voti e visualizzarla.
  • Visualizzare la materia con il voto più alto (la materia preferita).
  • Visualizzare i nomi di tutte le materie insufficienti (ahi ahi ahi...)
  • Dato un nome di materia da parte dell'utente, visualizzare il voto se la materia è presente nel dizionario, oppure scrivere "materia non presente".

Esercizio 811

Definire un dizionario chiamato "ortofrutta" inizialmente vuoto. In esso dovranno essere inserite una serie di informazioni, che rappresentano ognuna un prodotto per un negozio di frutta e verdura, organizzate in questo modo:

  • la chiave deve essere il nome del prodotto (es: "pere", "patate", ecc…)
  • il valore sarà una tupla di 2 informazioni:
    • un numero per la quantità presente (in chilogrammi)
    • un numero per il costo al chilo del prodotto

Inserire tramite codice una serie di almeno 6 prodotti da vendere all'ortofrutta e procedere ad una visualizzazione come indicato:

prodotto: NOME
quantità: VALORE kg
costo: VALORE euro al chilo
(riga vuota)

Chiedere all'utente di inserire un prodotto. Se esso è presente nel dizionario, chiedere quanti chili di esso se ne vogliono acquistare. Se il numero inserito è minore o uguale alla quantità presente, diminuire la quantità attuale dei chili venduti e visualizzare all'utente il costo totale del prodotto acquistato.


Esercizio 812

Definire un dizionario chiamato "squadra" inizialmente vuoto. In esso dovranno essere inserite una serie di informazioni, che rappresentano ognuna un giocatore della squadra, organizzate in questo modo:

  • la chiave deve essere il numero di maglia del giocatore
  • il valore sarà una tupla di 2 informazioni:
    • una stringa per il nome del giocatore
    • un carattere per il ruolo ('P per portiere, 'D' per difensore, 'C' per centrocampista, 'A' per attaccante)

Inserire tramite codice una serie di almeno 6 giocatori nella squadra e procedere ad una visualizzazione come indicato:

numero: NUMERO
nome: NOME
ruolo: RUOLO
(riga vuota)

Definire un nuovo dizionario chiamato "ruoli" ove vi saranno inseriti 4 elementi: le chiavi saranno i ruoli "P", "D", "C", "A" possibili per i giocatori della squadra, i valori saranno il numero di giocatori per quel ruolo presenti nel dizionario "squadra".

Visualizzare il dizionario così completato.


Esercizio 813

Definire un dizionario chiamato "rubrica" inizialmente vuoto. In esso dovranno essere inserite una serie di informazioni, che rappresentano ognuna un contatto della rubrica, organizzate in questo modo:

  • la chiave deve essere il nickname del contatto (ad esempio: "mamma", "prof", etc...)
  • il valore sarà una tupla di 3 informazioni:
    • una stringa per il numero di telefono del contatto
    • una stringa per l'indirizzo di residenza
    • un intero per l'anno di nascita

Inserire tramite codice una serie di almeno 5 contatti nella rubrica e procedere ad una visualizzazione come indicato:

nome: NOME
telefono: NUMERO
indirizzo: INDIRIZZO
anno di nascita: ANNO
(riga vuota)

Riempire una lista con i nickname dei contatti minorenni, visualizzare l'elenco e dire quanti sono in totale. Permettere all'utente di inserire un nick e se presente visualizzare il suo numero di telefono, altrimenti visualizzare la scritta "CONTATTO NON PRESENTE".


Esercizio 814

Definire un dizionario chiamato "pazienti" inizialmente vuoto. In esso dovranno essere inserite una serie di informazioni, che rappresentano ognuna una persona in cura da un dietologo, organizzate in questo modo:

  • la chiave deve essere il nome della persona
  • il valore sarà una tupla di 4 informazioni:
    • peso (in kg)
    • altezza (in cm)
    • età (in anni)
    • sesso (M' oppureF')

Inserire tramite codice una serie di almeno 5 persone e procedere ad una visualizzazione come indicato:

nome: NOME
peso: VALORE kg
altezza: VALORE cm
maschio di TOT anni (oppure femmina di... a seconda del valore)
(riga vuota)
  1. Visualizzare i nomi di tutte le femmine.
  2. Contare il numero di minorenni più alti di 180 cm e visualizzare i loro nomi.
  3. Visualizzare il nome del maschio e della femmina che pesano di più.

Modulo CSV

Il modulo csv è un modulo della libreria predefinita che serve per trattare i file in formato CSV (Comma Separated Value).

Il CSV è un formato di testo utilizzato per l'importazione ed esportazione di una tabella di dati da fogli elettronici o da basi di dati. Essere in grado di manipolarlo ci permette dunque di migliorare di molto (ma con poco sforzo) le nostre attuali capacità di programmatori!

Per fare un esempio semplice con il formato CSV, immaginate di voler memorizzare la seguente tabella dati:

Nome Ruolo Squadra
Maradona Centrocampista Napoli
Gullit Attaccante Milan
Brehme Difensore Inter
Falcao Centrocampista Roma

Ogni riga della tabella nel formato CSV termina quando si va a capo. Ogni colonna della tabella termina con una virgola. Da cui si ottiene:

Esempio 0: il file "giocatori.csv"

Nome,Ruolo,Squadra
Maradona,Centrocampista,Napoli
Gullit,Attaccante,Milan
Brehme,Difensore,Inter
Falcao,Centrocampista,Roma

Mi sembra molto semplice da capire :)

Note

Il modulo CSV permette di caricare in maniera automatica i dati estratti in strutture dati che noi ben conosciamo: i dizionari!

Da tutte queste considerazioni insieme capite la necessità di studiare questo modulo. Per caricare i dati da un file csv si può semplicemente utilizzare la funzione DictReader() del modulo CSV, che carica ogni riga del file (successiva alla prima) in un dizionario, con la prima riga che diventa la chiave di ogni voce.

Facciamo alcuni esempi caricando i dati dal file "giocatori.csv" di cui conosciamo già la struttura.

Esempio 1: lettura del file CSV e caricamento dati in una LISTA di DIZIONARI
import csv

# la lista su cui caricheremo i dati
dati = []

file = open("giocatori.csv", "r")
lettore = csv.DictReader(file)

for riga in lettore:
    dati.append(riga)

file.close()

# da adesso in poi basta lavorare sulla variabile dati
# ...

# visualizzazione dati
for giocatore in dati:
    print(giocatore)

L'output di questo codice diventa questo:

{'Nome': 'Maradona', 'Ruolo': 'Centrocampista', 'Squadra': 'Napoli'}
{'Nome': 'Gullit', 'Ruolo': 'Attaccante', 'Squadra': 'Milan'}
{'Nome': 'Brehme', 'Ruolo': 'Difensore', 'Squadra': 'Inter'}
{'Nome': 'Falcao', 'Ruolo': 'Centrocampista', 'Squadra': 'Roma'}

In maniera analoga è possibile scrivere in un file CSV partendo da una serie di dizionari (una... lista di dizionari!!!) utilizzando la funzione DictWriter.

Esempio 2: salvataggio su file dei dati presenti in una lista di dizionari
import csv

dati = []
dati.append( {"nome":"Gino","cognome":"Panino"} )
dati.append( {"nome":"Pino","cognome":"Pinguino"} )

campi = ["nome","cognome"]

# il parametro newline così impostato assicura che il codice funzioni uguale su Win,Mac,Linux
file = open("esempio.csv", "w", newline="") 
scrittore = csv.DictWriter(file, campi)

# inserisce l’intestazione nel file
scrittore.writeheader()

for riga in dati:
    scrittore.writerow(riga)

file.close()

Questo codice produce un file CSV chiamato "esempio.csv" contenente i seguenti dati:

nome, cognome
Gino, Panino
Pino, Pinguino

Spero sia tutto chiaro!

ATTENZIONE!!!

Se create il file CSV con Microsoft Excel questo imporrà come separatore il punto e virgola ; invece della virgola ,.

Invece di mettersi a cambiare tutti i file, è più semplice indicare a Python quale è il simbolo delimitatore dei campi, con questa sintassi:

file = open("giocatori.csv", "r")
lettore = csv.DictReader(file, delimiter=";")

Arrivati qui dovete semplicemente riprovare il codice proposto in questi esempi e poi mettervi alla prova con i seguenti esercizi!

Esercizi

Nota

Gli esercizi che seguono si basano tutti sui file CSV reperibili su https://www.adjam.org/csv/.

Ogni esercizio indica il file da scaricare per poter eseguire l'esercizio correttamente.


Esercizio 851

Si basa sulla mappa dei cinema italiani.

Step 0
Caricare i dati dal file CSV in una opportuna lista dati.
Step 1
Visualizzare, per ogni cinema, il "Nome" (del cinema) e il "Comune", ove esso è locato.
Step 2
Come lo step precedente, ma limitandosi solo a quelli della regione "Marche". La regione si trova nel campo "Regione".
Step 3
Estrapolare i cinema inseriti dopo il 2015 per la regione "Lombardia". Con i suddetti dati creare un nuovo file CSV chiamato "LombardiaPost2015.csv" con i dati delle colonne "Nome", "Comune", "Provincia", "Longitudine", "Latitudine".

Esercizio 852

Si basa sull'anagrafica delle scuole italiane.

Step 0
Caricare i dati dal file CSV in una opportuna lista dati.
Step 1
Dato un codice scuola, digitato dall'utente, visualizzare il nome, l'indirizzo, la città, il numero di telefono, la mail e il sito web della scuola corrispondente. Oppure la scritta "Codice Scuola non esistente".
Step 2
Contare le tipologie di scuole, dividendo tra scuola dell'infanzia, prima, e secondaria di primo e secondo grado e fra scuola statale e paritaria. Il risultato dovrà essere visualizzato in un formato tipo:
Scuola dell'infanzia (statale): 98
Scuola dell'infanzia (paritarie): 34
...
Step 3
Inserita una città da parte dell'utente, visualizzare tutte le scuole (nome e via) divise per tipologia (es: prima quelle dell'infanzia, poi le scuole primarie, etc...)
Step 4
Selezionato un CAP da parte dell'utente, visualizzare i nomi e i codici scuola di tutte le scuole presenti in quel CAP.

Esercizio 853

Si basa sulla mappa dei monumenti italiani.

Step 0
Caricare i dati dal file CSV in una opportuna lista dati.
Step 1
Creare una tupla con i nomi delle regioni italiane. Visualizzare i nomi delle regioni e permettere all'utente di selezionarne una. Visualizzare tutti i nomi dei monumenti di quella regione (tra virgolette) con accanto il nome della città in cui si trova (separata da virgola) e tra parentesi il "tipo" di monumento.
Ad esempio, selezionata "Puglia", uno dei monumenti potrebbe essere:
"Eraclio", Barletta (Monumento)
Step 2
Creare nella home dell'utente il file "MonumentiPuglia.txt" (se la regione selezionata è la Puglia) con i monumenti elencati uno per riga (ripeto, nella stessa visualizzazione dello step precedente).
Step 3
Inserito un anno a scelta a dell'utente visualizzare il numero di monumenti inseriti nel file in quell'anno.
Step 4
Per ognuno degli anni dal 2010 al 2019, visualizzare il numero di monumenti inseriti in quell'anno.
Step 5
Inserito un ID OpenStreetMap da parte dell'utente, visualizzare tutti i dati relativi al monumento selezionato oppure visualizzare la scritta: "nessun monumento esistente con ID:" e visualizzare l'ID inserito.

Esercizio 854

Si basa sulla mappa delle strutture ricettive marchigiane.

Step 0
Caricare i dati dal file CSV in una opportuna lista dati.
Step 1
Inserito dall'utente un numero S compreso fra 1 e 5, visualizzare tutte le strutture ricettive classificate come "Alberghi S stelle" dove S è il numero inserito dall'utente
Step 2
Come il precedente ma permettendo all'utente di selezionare anche una provincia (da scegliere in un elenco opportuno). Saranno dunque visualizzate tutte le strutture classificate in un certo modo di quella provincia.
Step 3
Visualizzare il numero totale di strutture ricettive per provincia.
Step 4
Selezionata una provincia, visualizzare i nomi di tutte le città della provincia che presentano strutture ricettive.