Esercizi Python Intermedi: Logica, Ottimizzazione e Strutture Dati (con Soluzioni)

Cerca:

Generic selectors
Exact matches only
Search in title
Search in content
Post Type Selectors
esercizi sulle liste e cicli while in Python

Leggere manuali e guardare ore di tutorial è un ottimo punto di partenza, ma arriva sempre quel momento in cui bisogna mettersi alla prova. Scrivere codice funzionante partendo da una pagina bianca è l’unico vero modo per capire se un concetto è entrato in testa.

In questo articolo abbiamo raccolto tre sfide pratiche in Python.

Non sono i classici esercizi accademici fini a se stessi: li abbiamo pensati per simulare piccoli problemi che si affrontano nello sviluppo di software reali. Dovrai gestire l’imprevedibilità di un utente che inserisce dati sbagliati, ottimizzare un algoritmo per non fondere il processore e gestire lo “stato” di un sistema in tempo reale.

Prendetevi il vostro tempo, aprite il vostro editor preferito e provate a scrivere la vostra soluzione prima di leggere la nostra.

Esercizio 1: Il Gioco dell’Indovinello con Suggerimenti Intelligenti (Medio)

Testo:
Scrivi un gioco in cui il computer pensa a un numero segreto tra 1 e 100 (inclusi). Il giocatore ha un massimo di 7 tentativi per indovinarlo. Dopo ogni tentativo sbagliato, il programma deve fornire un suggerimento:

  • “Troppo alto” o “Troppo basso”.

  • Inoltre, se il tentativo è entro 5 dal numero segreto, deve stampare “Sei molto vicino!”.

  • Il programma deve anche gestire l’input, assicurandosi che sia un numero intero valido tra 1 e 100.
    Al termine, stampare un messaggio di vittoria o sconfitta e rivelare il numero segreto.

Soluzione:

import random

# 1. Generazione del numero casuale
numero_segreto = random.randint(1, 100)
tentativi_massimi = 7
tentativi_effettuati = 0
ha_vinto = False

print("Benvenuto nel gioco dell'indovinello!")
print(f"Ho pensato a un numero tra 1 e 100. Hai {tentativi_massimi} tentativi.")

# 2. Ciclo principale del gioco
while tentativi_effettuati < tentativi_massimi and not ha_vinto:
    try:
        tentativo = int(input("Tenta il numero: "))
        # Validazione del range
        if tentativo < 1 or tentativo > 100:
            print("Per favore, inserisci un numero tra 1 e 100.")
            continue  # Non conta come tentativo
    except ValueError:
        print("Input non valido. Inserisci un numero intero.")
        continue  # Non conta come tentativo

    tentativi_effettuati += 1

    # 3. Logica di confronto e suggerimenti
    if tentativo == numero_segreto:
        ha_vinto = True
        print(f"Congratulazioni! Hai indovinato in {tentativi_effettuati} tentativi!")
    else:
        # Suggerimento "alto/basso"
        if tentativo > numero_segreto:
            print("Troppo alto.")
        else:
            print("Troppo basso.")

        # Suggerimento extra "vicino"
        if abs(tentativo - numero_segreto) <= 5:
            print("Sei molto vicino!")

        print(f"Tentativi rimasti: {tentativi_massimi - tentativi_effettuati}")

# 4. Messaggio di sconfitta se il ciclo termina senza vittoria
if not ha_vinto:
    print(f"\nHai esaurito i tentativi! Il numero segreto era {numero_segreto}.")numero_segreto:
print("Troppo alto.")
else:
print("Troppo basso.")

# Suggerimento extra "vicino"
if abs(tentativo - numero_segreto) <= 5:
print("Sei molto vicino!")

print(f"Tentativi rimasti: {tentativi_massimi - tentativi_effettuati}")

# 4. Messaggio di sconfitta se il ciclo termina senza vittoria
if not ha_vinto:
print(f"\nHai esaurito i tentativi! Il numero segreto era {numero_segreto}.")
Benvenuto nel gioco dell'indovinello!
Ho pensato a un numero tra 1 e 100. Hai 7 tentativi.
Tenta il numero: 56
Troppo alto.
Tentativi rimasti: 6
Tenta il numero: 44
Troppo alto.
Tentativi rimasti: 5
Tenta il numero: 22
Troppo basso.
Tentativi rimasti: 4
Tenta il numero: 34
Troppo basso.
Sei molto vicino!
Tentativi rimasti: 3
Tenta il numero: 32
Troppo basso.
Tentativi rimasti: 2
Tenta il numero: 33
Troppo basso.
Sei molto vicino!
Tentativi rimasti: 1
Tenta il numero: 35
Troppo basso.
Sei molto vicino!
Tentativi rimasti: 0

Hai esaurito i tentativi! Il numero segreto era 38.

💡 Flag di controllo:

La variabile booleana ha_vinto è un flag. Ci permette di uscire elegantemente dal ciclo while se il giocatore indovina, senza dover usare break (che può rendere il codice meno leggibile in cicli complessi).
💡 abs() (valore assoluto):

La funzione abs() è fondamentale per calcolare la distanza tra due numeri senza preoccuparsi del segno.

Mini Quiz:
Perché abbiamo usato and not ha_vinto nella condizione del while? Quale sarebbe la differenza se avessimo messo la condizione di vittoria all’interno del ciclo con un break?

Esercizio 2: Il Cacciatore di Numeri Primi Gemelli (Medio/Difficile)

Testo:
Due numeri primi sono detti “gemelli” se la loro differenza è 2 (es. 3 e 5, 5 e 7, 11 e 13).

Forse potrebbe interessarti anche:  Python: leggere i file delimitati

Scrivi un programma che:

  1. Chieda all’utente un numero intero positivo N.

  2. Trovi e stampi tutte le coppie di numeri primi gemelli dove entrambi i numeri sono minori o uguali a N.

  3. Per determinare se un numero è primo, scrivi una funzione is_prime(numero) che restituisce True se il numero è primo, False altrimenti.

  4. Ottimizza la funzione is_prime per essere efficiente: non è necessario controllare tutti i numeri fino a numero-1.

Soluzione:

import math

# Definizione della funzione per testare la primalità
def is_prime(numero: int) -> bool:
    """Restituisce True se numero è primo, altrimenti False."""
    if numero < 2:
        return False
    # 2 è l'unico numero primo pari
    if numero == 2:
        return True
    # Se è pari e maggiore di 2, non è primo
    if numero % 2 == 0:
        return False
    # Ottimizzazione: controlliamo solo i divisori dispari fino alla radice quadrata
    # Perché se un numero ha un divisore maggiore della sua radice, ne ha uno minore.
    limite = int(math.sqrt(numero)) + 1
    for i in range(3, limite, 2):  # partiamo da 3, step 2 (solo numeri dispari)
        if numero % i == 0:
            return False
    return True

# Programma principale
N = int(input("Inserisci il limite massimo N: "))
print(f"Coppie di primi gemelli <= {N}:")

# Iteriamo su tutti i numeri da 2 a N-2 (perché il secondo gemello è primo+2)
for numero in range(2, N - 1):
    # Se numero e numero+2 sono entrambi primi
    if is_prime(numero) and is_prime(numero + 2):
        print(f"({numero}, {numero + 2})")
Inserisci il limite massimo N: 34
Coppie di primi gemelli <= 34:
(3, 5)
(5, 7)
(11, 13)
(17, 19)
(29, 31)

💡 Modularizzazione (Funzioni):

Incapsulare la logica per testare la primalità in una funzione is_prime rende il codice principale (il loop) estremamente chiaro e leggibile. Inoltre, se in futuro volessimo migliorare l’algoritmo, lo faremo in un solo punto.
💡 Efficienza algoritmica:

Controllare tutti i divisori fino a numero-1 è inefficiente per numeri grandi. Grazie a una proprietà matematica (se n = a*b, allora almeno uno tra a e b è ≤ √n), possiamo limitarci a controllare fino alla radice quadrata. Inoltre, saltare i numeri pari dopo aver verificato il 2 dimezza i controlli.

Mini Quiz:
Perché nella funzione is_prime abbiamo usato int(math.sqrt(numero)) + 1 come limite nel ciclo for? Quale errore concettuale potrebbe accadere se usassimo solo int(math.sqrt(numero))?

Esercizio 3: Il Gestore di Prenotazioni Aeree (Difficile)

Testo:
Un piccolo aeroporto ha un aereo con 20 posti, numerati da 1 a 20. Il sistema di prenotazione deve gestire le richieste degli utenti tramite un menu testuale:

  1. Mostra mappa posti: Visualizza lo stato dei posti, dove [ ] è libero e [X] è occupato.

  2. Prenota posto: Chiede all’utente un numero di posto. Se libero, lo prenota. Se occupato o non valido, avvisa l’utente.

  3. Cancella prenotazione: Chiede il numero di posto e, se occupato, lo libera.

  4. Statistiche: Mostra il numero di posti liberi e occupati e la percentuale di occupazione.

  5. Esci: Termina il programma.

Il programma deve continuare a mostrare il menu e gestire le scelte fino a quando l’utente non sceglie di uscire. Gestisci tutti gli input errati (es. scegliere un’opzione non valida dal menu, inserire un numero di posto fuori range).

Soluzione:

# Inizializzazione: una lista di 20 elementi, inizialmente tutti False (libero)
# Useremo una lista di booleani. L'indice 0 rappresenterà il posto 1.
posti = [False] * 20

def mostra_mappa():
    """Stampa una rappresentazione grafica dei posti."""
    print("\n--- Mappa dei posti ---")
    # Scorriamo la lista con enumerate per avere indice e valore
    for i, occupato in enumerate(posti):
        numero_posto = i + 1
        if occupato:
            print(f"[X] ", end="")
        else:
            print(f"[ ] ", end="")
        # Ogni 5 posti, andiamo a capo per una migliore visualizzazione
        if numero_posto % 5 == 0:
            print()
    print("------------------------")

def prenota_posto():
    try:
        numero = int(input("Inserisci il numero del posto da prenotare (1-20): "))
        # Validazione range
        if numero < 1 or numero > 20:
            print("Numero posto non valido.")
            return
        indice = numero - 1
        if posti[indice]:
            print(f"Attenzione: il posto {numero} è già occupato.")
        else:
            posti[indice] = True
            print(f"Posto {numero} prenotato con successo.")
    except ValueError:
        print("Input non valido.")

def cancella_prenotazione():
    try:
        numero = int(input("Inserisci il numero del posto da liberare (1-20): "))
        if numero < 1 or numero > 20:
            print("Numero posto non valido.")
            return
        indice = numero - 1
        if not posti[indice]:
            print(f"Il posto {numero} è già libero.")
        else:
            posti[indice] = False
            print(f"Prenotazione per il posto {numero} cancellata.")
    except ValueError:
        print("Input non valido.")

def mostra_statistiche():
    occupati = sum(posti)  # In una lista di booleani, True=1, False=0
    liberi = len(posti) - occupati
    percentuale_occupazione = (occupati / len(posti)) * 100
    print(f"\n--- Statistiche ---")
    print(f"Posti totali: {len(posti)}")
    print(f"Posti occupati: {occupati}")
    print(f"Posti liberi: {liberi}")
    print(f"Percentuale occupazione: {percentuale_occupazione:.1f}%")
    print("-------------------")

# --- Ciclo principale del programma ---
while True:
    print("\n=== MENU PRINCIPALE ===")
    print("1. Mostra mappa posti")
    print("2. Prenota un posto")
    print("3. Cancella una prenotazione")
    print("4. Statistiche")
    print("5. Esci")

    scelta = input("Scegli un'opzione: ")

    if scelta == "1":
        mostra_mappa()
    elif scelta == "2":
        prenota_posto()
    elif scelta == "3":
        cancella_prenotazione()
    elif scelta == "4":
        mostra_statistiche()
    elif scelta == "5":
        print("Arrivederci!")
        break  # Uscita definitiva dal ciclo while
    else:
        print("Opzione non valida. Scegli un numero tra 1 e 5.")

💡 Struttura Dati:

Forse potrebbe interessarti anche:  Analisi della Fedeltà dei Clienti con Python: Un Approccio Pratico con Pandas

Una lista di booleani è la struttura dati più adatta e compatta per rappresentare lo stato di un insieme di posti.
💡 Menu e while True:

L’uso di while True con un break per uscire è una pratica comune e chiara per implementare menù interattivi. La condizione di uscita è esplicitamente gestita dalla scelta “Esci”.
💡 Modularità:

Ogni funzionalità è stata incapsulata in una funzione (mostra_mappaprenota_posto, ecc.). Questo rende il ciclo while principale una semplice struttura di controllo che richiama le funzioni, migliorando la leggibilità e la manutenibilità.

Mini Quiz:
Nella funzione mostra_statistiche, abbiamo usato occupati = sum(posti). Perché questa operazione funziona correttamente? A cosa corrisponde matematicamente?

Risposte Dettagliate ai Mini Quiz

Esercizio 1:
Perché abbiamo usato and not ha_vinto nella condizione del while? Quale sarebbe la differenza se avessimo messo la condizione di vittoria all’interno del ciclo con un break?

Risposta:

Abbiamo usato and not ha_vinto per avere un’unica e chiara condizione di uscita del ciclo, definita nella sua intestazione. Questo rende immediatamente evidente al lettore quando il ciclo si ferma: o finiscono i tentativi o si vince. Se avessimo usato un break all’interno del if tentativo == numero_segreto, il ciclo si sarebbe comunque fermato, ma la condizione di uscita sarebbe stata “nascosta” nel corpo del ciclo. Entrambi gli approcci sono validi, ma usare un flag nella condizione del while è spesso considerato più elegante e più facile da debuggare in cicli complessi, poiché tutte le condizioni di terminazione sono in un unico punto.

Esercizio 2:
Perché nella funzione is_prime abbiamo usato int(math.sqrt(numero)) + 1 come limite nel ciclo for? Quale errore concettuale potrebbe accadere se usassimo solo int(math.sqrt(numero))?

Risposta:

Forse potrebbe interessarti anche:  Python: La libreria Sympy. Le derivate

La funzione range(start, stop, step) in Python genera numeri fino a stop-1. Se volessimo controllare tutti i divisori fino a √n incluso, dobbiamo impostare stop = int(√n) + 1. Ad esempio, per n=9, √9 = 3. I divisori da controllare sono 3 (perché 3*3=9). Se impostassimo range(3, int(math.sqrt(9)), 2) avremmo range(3, 3, 2), che è vuoto. Non controlleremmo il divisore 3, e is_prime(9) restituirebbe erroneamente True (falso positivo). L’aggiunta di +1 assicura che il limite superiore sia incluso nel controllo.

Esercizio 3:
Nella funzione mostra_statistiche, abbiamo usato occupati = sum(posti). Perché questa operazione funziona correttamente? A cosa corrisponde matematicamente?

Risposta:

In Python, il tipo bool è un sottotipo di intTrue ha valore intero 1 e False ha valore 0. La funzione sum() somma gli elementi di una lista. Quando applicata a una lista di booleani, somma i valori numerici corrispondenti, restituendo così il conteggio totale degli elementi True nella lista. Quindi sum(posti) è un modo estremamente conciso ed efficiente per contare quanti posti sono occupati, senza bisogno di scrivere un ciclo for con un contatore.

Il Dietro le Quinte:

  • Esercizio 1 (L’Indovinello): L’importanza dell’Input Validation

    • A prima vista sembra un gioco di logica elementare, ma la sua vera forza sta nella robustezza. L’uso del blocco try-except ci introduce a uno dei dogmi della programmazione: non fidarsi mai dell’input dell’utente. Se l’utente digita “ciao” invece di un numero, il programma non si schianta (crash), ma gestisce l’errore con grazia. Questa è la base per la creazione di interfacce (CLI o GUI) a prova di bomba.

  • Esercizio 2 (Primi Gemelli): Il costo computazionale

    •  Questo esercizio è un’ottima introduzione all’efficienza algoritmica. L’approccio brute-force (controllare tutti i numeri fino a n-1) funziona per numeri piccoli, ma collassa se chiediamo al programma di controllare numeri nell’ordine dei milioni. Limitare il ciclo fino a √n e saltare i numeri pari è un assaggio di come i software enterprise scalano: la matematica ci viene in aiuto per risparmiare preziosi cicli di CPU.

  • Commento all’Esercizio 3 (L’Aeroporto): Lo Stato e le operazioni CRUD

    • Perché è interessante: Questa è un’applicazione gestionale in miniatura. La lista di booleani posti non è altro che un database primordiale in memoria. Le funzioni che abbiamo scritto coprono i concetti fondamentali delle operazioni CRUD (Create, Read, Update, Delete). Prenotare aggiorna lo stato (“Update”), mostrare la mappa lo legge (“Read”), cancellare lo ripristina. Questo è esattamente il pattern architetturale alla base di qualsiasi sistema di booking online moderno, solo spogliato della complessità dell’infrastruttura di rete.

Python base → strutture dati → gestione input/output

👉 Elementi di base di Python per la scienza dei dati

👉 Python: richiedere input all’utente

👉 Esercizi sulle stringhe in Python

👉 Funzioni e metodi per operare sulle stringhe

👉 Leggere file delimitati in Python

👉 File di testo e codifica Unicode in Python

👉 Liste, tuple e dizionari: guida completa con esercizi

👉 Tuple in Python: immutabilità ed esercizi

👉 Ricercare in un dizionario in Python

👉Python Pratico: 3 Esercizi su Cicli e Condizionali per Sviluppare la Logica di Programmazione

Pubblicità