Laboratorio di Informatica

Il seguente materiale didattico è stato scritto dal Dr. Stefano Teso, che mi ha gentilmente concesso di utilizzarlo per questa edizione del corso (grazie!).

La pagina del corso si trova qui:

mentre quella delle esercitazioni si trova qui:


Hint

Se avete dubbi o domande potete rivolgervi al vostro tutor:

  • Guendalina Bergonzoni g dot bergonzoni at studenti dot unitn dot it

Siete pregati di usare il tag [INF CIBIO] nel titolo per visibilita’.


Hint

Per gli utenti Mac OS X: il comando tac non e’ presente nella shell di default, usate in alternativa tail -r.

Il seguente script vi permette di aggiungere un alias a tac. Per farlo, scaricatelo ed eseguite sh tac_alias.sh, chiudete e riaprite il terminale.


Bibliografia


Dati

I dati usati nelle esercitazioni sono disponibili qui:


Note Introduttive

  • Assumeremo di lavorare esclusivamente con file di testo, indipendentemente dall’estensione dei file.
  • La shell e Python non capiscono le lettere accentate, non usatele.
  • Il comportamento di alcuni comandi puo’ variare tra varianti diverse di Unix, ad esempio GNU/Linux e MacOS X.


Shell: Fondamentali

Scorciatoie

Alcune dritte per lavorare con la shell:

combinazione funzione
Control-c uccide il comando/script in esecuzione
Tab autocompleta comandi e percorsi
, scorrono tra i comandi dati in precedenza
history stampa a schermo la storia dei comandi
Control-←, Control-→ scorrono tra le parole della riga
Home, End saltano all’inizio e alla fine della riga
Control-l pulisce il terminale

Trovare gli errori

Quando costruite una pipeline complessa (con |), e’ conveniente procedere in modo incrementale: partendo dalla prima, aggiungete una componente alla volta, verificando ad ogni passaggio che il risultato sia corretto.

Se la lunga sequenza di comandi che avete appena scritto non funziona, come trovare il colpevole?

  • Controllate di trovarvi nella directory giusta con pwd.

  • Controllate che i file e le directory che impiegate esistano e che il loro contenuto sia quello che vi aspettate (ad esempio che non siano vuoti).

  • Controllate la sintassi dei comandi sospetti. Potete farlo usando man:

    man comando
    
  • Controllate che le redirezioni (<, >, >>, |) siano giuste. Sono nel verso giusto? I comandi con cui le usate supportano la redirezione?

  • Controllate che minuscole e maiuscole siano corrette (ad esempio, /home/stefano/Informatica e /home/stefano/informatica sono diversi).


Invocare comandi

Per invocare un comando cmd e’ sufficiente scrivere:

cmd

nella shell e dare invio.

Alcuni comandi supportano argomenti ed opzioni aggiuntivi:

  • Gli argomenti indicano tipicamente percorsi a file, ad esempio da dove prendere l’input del comando e/o dove mettere il risultato. Ad esempio:

    scanformotifs protein.fasta known_motifs.txt motifs_found_in_protein.txt
    
  • Le opzioni alterano il comportamento del comando, spesso sono facoltative. Ad esempio:

    cmd -x -y -z --long-option
    

Esempio. Il comando ls stampa a schermo la lista dei file contenuti in una directory. Accetta uno o piu’ argomenti, che devono essere dei percorsi (a file o directory), e zero o piu’ opzioni che ne alterano il comportamento.

La sintassi completa e’:

ls [opzioni] <percorso_1> ... <percorso_n>

Per visualizzare una lista dettagliata dei contenuti della mia come con ls, ordinati per tempo di modifica, scrivo:

ls -l -t /home/stefano

Per delucidazioni, potete scrivere:

man ls

o alternativamente:

ls --help

Esempio. Per invocare il manuale di cmd, scrivo:

man cmd

Cosi’ ottengo la sintassi di cmd, incluse le opzioni accettate.

Per uscire dal manuale, premete q (quit). Per spostarvi potete usare le frecce. Per cercare un termine (ad esempio, l’opzione -t) premete / e scrivete -t; potete usare n (next) e p (previous) per spostarvi tra le varie apparizioni del testo -t nel manuale.


Lista dei Comandi

Ecco una lista completa dei comandi che useremo:

comando funzione
pwd stampa la directory corrente
ls (<percorso>) elenca i file in una directory
cd <percorso> cambia la directory corrente
mv <cosa> <dove> sposta o rinomina un file/directory
cp <cosa> <dove> copia un file/directory
rm <cosa> rimuove un file/directory
mkdir <percorso> crea una o piu’ directory
echo <testo> stampa l’eco
cat <percorso> stampa il contenuto di un file
tac <percorso> stampa il contenuto sottosopra
head <percorso> stampa l’inzio di un file
tail <percorso> stampa la fine di un file
wc <percorso> conta caratteri, parole e righe
sort <percorso> ordina righe
uniq <percorso> rimuove righe consecutive identiche
cut <percorso> stampa una o piu’ colonne
tr <percorso> sostituisce un carattere con un altro
grep <regex> <percorso> seleziona righe in base ad una regex

Percorsi

Nei sistemi Unix (quindi Linux, MacOS X, Android, etc.) il filesystem puo’ essere visto come un albero, simile a questo:

Struttura ad albero del filesystem.

La posizione di ogni file e directory nel filesystem e’ specificata da un percorso (o path).

Per usare un comando sara’ (talvolta) necessario dirgli da quali file prendere gli input e dove mettere il risultato. Per farlo, gli passeremo come argomenti i percorsi a questi file.

Ogni volta che aprite una shell, il vostro alter-ego virtuale sara’ posizionato nella directory home, che si trova qui:

/home/login.utente/

Potete cambiare posizione all’interno del filesystem usando cd e manipolare sia la struttura del filesystem (“cosa si trova dove”) sia i contenuti dei file (i dati veri e propri).

In ogni momento, potete usare pwd per visualizzare il percorso della directory dove vi trovate.

Un percorso e’ assoluto se include tutte le directory a partire dalla radice, ad esempio:

/home/login.utente/informatica/esercizi/data

oppure relativo se parte dalla directory in cui vi trovate ora, ad esempio:

informatica/esercizi/data

Esempio. Se mi trovo nella mia home (posso verificarlo con pwd), i due path:

informatica

e:

/home/stefano/informatica

sono equivalenti: indicano la stesso directory.

Alcune utili scorciatoie:

  • / e’ il percorso della radice (o root) del filesystem.
  • . e’ il percorso alla directory in cui vi trovate, qualunque essa sia.
  • .. e’ il percorso alla directory che contiene la directory in cui vi trovate, qualunque essa sia.
  • ~ e’ il percorso alla vostra home; e’ identico a /home/login.utente/.

Esempio. Per spostarmi nella radice scrivo:

cd /

Per spostarmi nella directory che contiene la directory in cui mi trovo, scrivo:

cd ..

Esercizi

  1. Cosa significa l’opzione -l di ls?
  2. Cosa significa l’opzione -n di cat?
  3. I comandi cat e tac accettano le stesse opzioni?
  4. Come si invoca il manuale di man?
  5. Verificare (a mente) se i seguenti percorsi sono validi, assoluti o relativi:
    1. .
    2. ../bio/luca
    3. /home/luca/data
    4. ../..
    5. ...
    6. ././here/there
    7. ..// (verificare con la shell usando cd)
    8. /../home
  6. Qual’e’ il percorso assoluto della tua home?
  7. Qual’e’ il percorso relativo di x se la directory in cui ti trovi e’ y?
    1. x = ~/data, y = ~
    2. x = ~/foo/bar, y = ~/bee/muu
  8. Qual’e’ il percorso assoluto di x se la directory in cui ti trovi e’ y?
    1. x = ., y = ~/data
    2. x = .., y = ~/personal
    3. x = .././maria/.., y = ~/informatica
    4. x = ./../nicola/., y = ~/informatica
    5. x = .., y = /
  9. Qual’e’ il percorso assoluto di ../proteins? Questa domanda ha senso?
  10. Il percorso relativo di una directory e’ unico?

  1. Usando man ls e cercando -l si scopre che l’opzione istruisce ls a stampare la lista dei file in modo piu’ dettagliato.

  2. Usando man cat e cercando -n si scopre che l’opzione istruisce cat a stampare, per ogni riga contenuta nel file, anche il numero di linea.

  3. No, man cat e man tac mostrano opzioni diverse.

  4. Cosi’: man man

  5. Soluzione:
    1. relativo.
    2. relativo.
    3. assoluto.
    4. relativo.
    5. invalido.
    6. relativo.
    7. relativo, equivale a ../.
    8. assoluto, equivale a /home.
  6. /home/login.utente. (posso verificarlo con cd ~; pwd.)

  7. Soluzione:
    1. data.
    2. ../../foo/bar.
  8. Soluzione:
    1. /home/login.utente/data
    2. /home/login.utente
    3. /home/login.utente
    4. /home/login.utente/nicola.
  9. La domanda non ha senso: non so dove mi trovo, quindi non posso sapere quale sia il path assoluto di ..!

  10. No. Ad esempio se mi trovo in ~, posso immaginarmi due percorsi relativi a ~/data:

    data
    

    e:

    data/../data
    

    Ce ne sono molti, molti altri.



Shell: Parte 1

Spostarsi nel Filesystem

comando funzione opzioni principali
pwd stampa la directory corrente -l, -h, -t, -S
ls elenca i file in una directory -l
cd cambia la directory corrente -
mv sposta o rinomina un file/directory -i, -f
cp copia un file/directory -i, -f, -r
rm rimuove un file/directory -i, -f, -r
mkdir crea una o piu’ directory -p

I comandi qui sopra spostano o copiano file, ma non modificano i contenuti degli stessi.

Esempio. Se non sapete dove vi trovate, potete usare cd senza argomenti per tornare alla vostra home.


Wildcards, Parte 1

La shell esegue quella che si chiama wildcard expansion: ogni volta che incontra l’asterisco * lo sostituisce con la lista dei file/directory che “fanno match”.

Esempio. Se eseguo:

ls *

la shell sostituisce * con la lista di tutti i file e le directory nella directory attuale (perche’ tutti fanno match con *). Invece:

ls ~/informatica/*

sostituisce * con la lista dei file in ~/informatica.

Supponendo che in ~/informatica ci siano solo tre file, chiamati test1, test2 e results, il comando precedente sarebbe equivalente a:

ls ~/informatica/test1 ~/informatica/test2 ~/informatica/results

Se avessi eseguito:

ls ~/informatica/test*

la wildcard test* avrebbe fatto match solo con test1 e test2, ed il comando sarebbe stato equivalente a:

ls ~/informatica/test1 ~/informatica/test2

Qui results non fa match, quindi non viene incluso.


Esercizi

  1. Cosa fa ls data/prot-fasta data/prot-pdb? Confrontalo con ls data/prot-fasta e ls data/prot-pdb per conferma.
  2. Cosa fa ls se gli si passa un path ad un file piuttosto che ad una directory? E ls -l?
  3. Cosa fa ls se gli si passa path misti a file e directory?
  4. Che differenza c’e’ tra:
    1. cd . e cd ..?
    2. cd .. e cd..?
    3. ls e ls .?
    4. ls e ls *? Perche’?
    5. ls -l -h e ls -lh?
    6. ls -l e ls - l?
  5. Il risultato di ls ../* “include” il risultato di ls .?
  6. Che differenza c’e’ tra ls /home/login.utente/bio e ls ../login.utente/bio se pwd e’ ~?
  7. Il file data/empty1 e’ vuoto? E il file data/empty2?
  8. Cosa fa il comando (alcuni dei comandi seguenti possono non funzionare; scopo dell’esercizio e’ scoprire perche’; partite sempre da una directory vuota):
    1. mkdir muu/bee?
    2. mkdir -p muu/bee/grr; rm muu/bee/grr?
    3. mkdir -p muu/bee/grr; rm -r muu/bee?
    4. mkdir muu; cd .; mkdir muu?
    5. mkdir muu; cd .; mkdir -p muu?
    6. Il comando rm -r muu/bee rimuove anche la directory muu?
  9. Stampare la lista dei file contenute nelle directory data/deep0, data/deep1, …, data/deep4.
  10. Stampare la lista di tutti i file, inclusi quelli nelle sotto-directory.

  1. Stampa a schermo prima ls lista dei contenuti della directory data/prot-fasta, poi quelli della directory data/prot-pdb. Quindi e’ identico a lanciare:

    ls data/prot-fasta
    ls data/prot-pdb
    

    separatamente.

  2. Stampa il nome del file. Con -l, stampa anche informazioni aggiuntive (proprietario, permessi, etc.)

  3. Stampa i nomi di tutti i file, ed i contenuti di tutte le directory.

  4. Soluzioni:
    1. Il primo non fa niente, il secondo ci sposta nella directory che contiene la directory in cui ci troviamo.
    2. Il primo funziona, il secondo no – manca uno spazio.
    3. Nessuna.
    4. Il primo stampa la lista dei contenuti della directory corrente. Il secondo invece subisce una wildcard expansion: * viene sostituito con la lista di tutti i file nella directory corrente. Quindi ls * stampa la lista dei file nella directory corrente, piu’ i contenuti di tutte le directory.
    5. Nessuna.
    6. Il primo funziona, il secondo no – - l ha uno spazio di troppo, non e’ un’opzione.
  5. Si’. Nella wildcard expansion ../* c’e’ anche la directory corrente, ..

  6. Nessuna.

  7. ls -l data/empty1 mostra chiaramente che il file non e’ vuoto, mentre ls -l data/empty2 mostra che lo e’.

  8. Soluzioni:
    1. Da’ errore. Creare directory innestate richiede l’opzione -p.
    2. Da’ errore. Rimuovere una directory (in questo caso muu/bee/grr) richiede l’opzione -r.
    3. Funziona. Crea le directory innestate muu/bee/grr, poi cancella muu/bee e la directory contenuta in essa, muu/bee/grr.
    4. Da’ errore. La directory muu esiste gia’ prima della seconda invocazione a mkdir.
    5. Funziona. Crea la directory muu, poi prova a crearla di nuovo. mkdir non da’ errore perche’ con -p gli chiediamo di ignorare il fatto che muu esiste gia’.
    6. No.
  9. ls data/deep0; ls data/deep1; ... ;ls data/deep4, oppure ls data/deep*.

  10. ls -R data/deep*.



Shell: Parte 2

Redirezione

I comandi standard:

  • prendono l’input da stdin, standard input; di default stdin e’ l’input utente dato via terminale.
  • scrivono l’output in stdout, standard output; di default stdout e’ il terminale.
  • scrivono eventuali messaggi di errore in stderr, standard error; di default stderr e’ il terminale.

Possiamo modificare questi nomi simbolici usando gli operatori di redirezione:

  • cmd < file costringe cmd ad usare i contenuti di file come stdin.
  • cmd > file costringe cmd a scrivere l’output nel file file. Se file non esiste viene creato, se esiste viene sovrascirtto.
  • cmd >> file costringe cmd a scrivere l’output nel file file. Se file non esiste viene crato, se esiste l’output viene aggiunto alla fine del file.

Warning

Non tutti i comandi permettono di redirigere lo stdin. Tra questi ci sono ls e cat.

Esempio. Lo stdout di ls puo’ comunque essere rediretto verso un file arbitrario:

ls > lista_dei_file.txt

Esempio. Prendiamo il comando echo: di default prende l’input (stdin) dal terminale (qui indicato come “keyboard”) e scrive (stdout) su terminale. Se lanciamo:

echo foo

quello che succede e’ che echo replica (fa l’eco) di tutto quello che scriviamo su terminale e lo stampa sul terminale. In immagini:

Redirezione.

Redirigendo lo stdout di echo a file invece lo costringiamo a redirigere il proprio output nel file:

echo foo > temp.txt

In immagini:

Redirezione.

Esempio. Redirigere l’input di un comando funziona allo stesso modo. Prendiamo il comando wc. Di default prende l’input da schermo:

$ wc -w
foo
bar
baz
Control-d

qui Control-d dice a wc che l’input finisce li’. L’output sara’ 3. Pero’ possiamo costringere wc a leggere l’input da file:

wc -w < temp.txt

In immagini:

Redirezione.

Pipelines

Per eseguire piu’ comandi in sequenza, possiamo usare ;:

cmd1 ; cmd2 ; ... ; cmdN

In questo modo la shell esegue cmd1, poi cmd2, …, ed infine cmdN.

Si possono combinare piu’ comandi in sequenza con l’operatore di concatetenamento | (pipe). La sintassi e’:

cmd1 | cmd2 | ... | cmdN

L’operatore | collega lo stdout di un comando allo stdin del comando che lo segue.

Una pipeline.

Note

Il simbolo | rappresenta un tubo!

Esempio. Anticipando un po’ i tempi, se lancio:

ls | tac

ottengo che le righe stampata da ls vengano prese da tac, che le stampa sottosopra.

Esempio. Per stampare su stdout la lista dei file nella mia home con ls e poi contare le righe con wc, scrivo:

ls -l ~ | wc -l

Posso passare argomenti ai vari comandi esattamente come se la pipeline non ci fosse.

Esempio. Per navigare comodamente l’output di una pipeline complessa:

cmd1 | ... | cmdN | less

Il comando less permette di vedere l’output pagina per pagina. Potete spostarvi nell’output come fareste nel manuale: vi spostate con le frecce, potete cercare con /, etc.

Modificare Files

I comandi di base sono:

comando funzione opzioni principali
echo stampa l’eco -n
cat stampa il contenuto di un file -n
tac stampa il contenuto sottosopra  
head stampa l’inzio di un file -n
tail stampa la fine di un file -n

Esempio. Come gia’ visto sopra, il comando echo puo’ essere usato per creare file di testo, ad esempio:

echo 'I am the last line *ever*!' > output.txt
echo 'You are not, you /fool/!' >> output.txt

Qui le virgolette singole ' servono per evitare che la shell reagisca ad eventuali caratteri speciali, nel nostro caso gli asterischi * e gli slash /.

Esercizi

  1. Partendo da ~, controllare il risultato dopo ogni passaggio:
    1. Creare una directory informatica. E’ vuota?
    2. Creare una directory esercizi.
    3. Spostarsi in informatica.
    4. Sempre da dentro informatica, rinominare esercizi in esercizi-shell.
    5. Creare una copia di esercizi-shell in informatica.
    6. Rimuovere la copia originale di esercizi-shell.
    7. Creare in informatica/esercizi-shell un file README, il cui testo deve leggere: “esercitazioni di informatica”.
    8. Aggiungere a README una seconda riga: “Parte 1, introduzione alla shell”.
    9. Tornare alla propria home.
  2. Partendo da ~, controllare il risultato dopo ogni passaggio:
    1. Creare un file di testo a in una nuova directory original. Il file deve contenere la stringa “*”.

    2. Prima di ciascuno dei punti successivi, creare una copia di original chiamata temp.

      1. Creare in temp due copie di a, chiamate b e c.
      2. Che differenza c’e’ tra echo a, ls a e cat a?
      3. Che differenza c’e’ tra mv a b e cp a b; rm a?
      4. Che differenza c’e’ tra cp a b; cp a c e mv a b; mv b c?
      5. Che differenza c’e’ tra mv a z e mkdir z; mv a z?
      6. Che differenza c’e’ tra echo a z e mkdir z; echo a z?
      7. Creare dieci file a1, …, a10, poi cancellarli con una sola invocazione di rm.
  3. Cosa fa il comando (alcuni dei seguenti comandi sono errati):
    1. ls -e | head -n + 25?
    2. cat | head | tail?
    3. cat .?
    4. echo cat?
    5. cat a > b?
    6. cat << a?
    7. head > a | tail > b?
    8. ls > a; rm < a?
    9. echo KrustyIlKlown > a?
    10. tac < FILE1 | tac > FILE2
  4. Che differenza c’e’ tra (alcuni dei seguenti comandi sono errati):
    1. head < a > b
    2. cat a | head > b
    3. tac a | tac | head > b
    4. tac < a | head | tac > b
  5. Che differenza c’e’ tra:
    1. tac a | head -n 25 > b
    2. cat a | tail -n 25 > b
  6. Che differenza c’e’ tra:
    1. head a | tail
    2. head a > temp; tail a
  7. Che differenza c’e’ tra:
    1. cat file | head
    2. cat | head file
  8. Come faccio a:
    1. stampare l’ennesima riga di un file?
    2. stampare le righe dalla n alla n+m di un file?
  9. Eseguire in ordine:
    1. Creare un file data/b che contenga le stesse righe di data/a, ordinate dalla 26 alla 50, dalla 1 alla 25, e dalla 51 alla 100, in quest’ordine.
    2. Creare un file data/c che contenga le stesse righe di data/a, ordinate dalla 26 alla 50, dalla 25 alla 1, e dalla 51 alla 100, in quest’ordine.

Shell: Parte 2 (Soluzioni)

  1. Soluzioni:
    1. mkdir informatica. Verifico che sia vuota con ls informatica.
    2. mkdir esercizi.
    3. cd informatica. Verifico con pwd.
    4. mv ../esercizi ../esercizi-shell. Verifico con ls ...
    5. cp -r ../esercizi-shell .. Verifico con ls.
    6. rm -r ../esercizi-shell.
    7. echo 'esercitazioni di informatica' > esercizi-shell/README. Verifico con cat esercizi-shell/README.
    8. echo 'Parte 1, introduzione alla shell' >> esercizi-shell/README. Idem.
    9. cd.
  2. Soluzioni:
    1. mkdir original, seguito da echo '*' > original/a.

    2. Per comodita’, entro in temp con cd temp. Dopo ogni passaggio, faccio:

      cd ..
      rm -r temp
      cp -r original temp
      cd temp
      

      Soluzioni:

      1. cp a b; cp a c. Verifico con ls; cat b; cat c.

      2. echo a stampa la stringa “a” a schermo; ls a stampa il nome del file a (che e’ a…); cat a stampa il contenuto del file a, cioe’ le due righe che abbiamo scritto prima.

      3. Nessuna. mv a b rinomina a in b. cp a b; rm aa prima fa prima una copia di a chiamata b, poi rimuove a.

      4. Nel primo caso mi ritrovo con tre copie di a, nel second con un solo file: c.

      5. mv a z rinomina a in z; quindi z e’ un file. mkdir z; mv a z prima crea una directory z, poi ci mette dentro a.

      6. echo a z stampa a schermo la stringa a z. mkdir z; echo a z fa la stessa cosa, ma crea anche una directory di nome z.

      7. Cosi’:

        echo qualcosa > a1
        ...
        echo qualcosa > a10
        rm a*
        
  3. Soluzioni:
    1. Da’ errore. -e non e’ un’opzione valida per ls.
    2. Si pianta. cat si aspetta un file di input: noi non glielo diamo, e lui resta in attesa. Dato che cat e’ fermo, il resto della pipeline resta in attesa. Siamo costretti ad uccidere la pipeline con Control-c.
    3. Da’ errore. . e’ una directory, cat si aspetta un file.
    4. Stampa la stringa “cat” a schermo. (Non esegue il comando cat…)
    5. Stampa il contenuto del file a (se esiste), ma visto che lo stdout e’ rediretto, mette l’output di cat nel file b. E’ equivalente a cp a b.
    6. Da’ errore: << non e’ un operatore di redirezione valido.
    7. Non ha senso: stiamo cercando di redirigere lo stdout di head sia verso il file a che, con la pipe |, verso lo stdin di tail.
    8. ls mette in a la lista dei file nella directory corrente; fin qui tutto ok. Mai rm non supporta la redirezione, si aspetta invece il nome di un file! Quindi finisce per dare errore.
    9. Scrive il testo “KrustyIlKlown” nel file a.
    10. Il primo tac legge FILE1 lo stampa a schermo rovesciato; pero’ la pipe ne redirige lo stdout al secondo tac: l’effetto complessivo e’ che FILE1 viene stampato a schermo nel verso giusto. Infine, l’ultimo > mette il contenuto di FILE1 in FILE2. Il tutto e’ equivalente a cp FILE1 FILE2.
  4. Soluzioni:
    1. Stampa le prime dieci righe di a in b.
    2. Idem.
    3. Idem.
    4. Stampa le ultime dieci righe di a in b.
  5. Soluzioni:
    1. Stampa a sottosopra, prende le prime venticinque righe, le mette in b. Quindi b contiene le ultime venticinque righe di a sottosopra.
    2. Stampa a, prende le ultime venticinque righe, le mette in b. Quindi b contiene ultime venticinque righe di a ma non sottosopra.
  6. Soluzioni:
    1. Stampa le prime dieci righe di a.
    2. Mette le prime dieci righe di a nel file temp, poi stampa le ultime dieci righe di a.
  7. Soluzioni:
    1. Stampa file e ne tiene solo le prime dieci righe.
    2. Si pianta. Come sopra, cat si aspetta un file in input; non non glielo diamo e lui resta in attesa.
  8. Soluzioni:
    1. Stampo le prime n del file, di queste tengo solo l’ultima: head -n n file | tail -n 1.
    2. Assumiamo che tot=n+m. Stampo le ultime tot righe del file, di queste tengo solo le ultime m: head -n tot file | tail -n m.
  9. Soluzioni:
    1. Eseguo:

      head -n 50 data/a | tail -n 25 > data/b
      head -n 25 data/a >> data/b
      tail -n 50 data/a >> data/b
      
    2. Come sopra, ma il secondo comando diventa:

      head -n 25 data/a | tac >> data/b
      


Shell: Parte 3

Wildcards, Parte 2

Le wildcard piu’ importanti sono:

wildcard fa match con
akz il testo “akz
* una stringa qualunque (anche vuota)
? un carattere qualunque
[akz] un carattere solo tra a, k e z
[a-z] un carattere alfabetico qualunque
[0-9] una cifra qualunque
[!1b] un carattere qualunque che non sia 1 o b
[!a-e] un carattere qualunque che non sia a, b, …, e

Quando la shell incontra un comando dove uno (o piu’) degli argomenti contiene delle wildacrds, esegue la wildcard expansion: sostituisce all’argomento incriminato tutti i file che fanno match con la wildcard.

Warning

Le wildcards sono simili alle regex, ma non sono la stessa cosa:

  1. Le wildcards sono usate dalla shell per fare il match di percorsi.
  2. Le regex sono usate da grep per fare il match di righe di testo contenute in un file.
  3. Le regole che determinano il match di wildcards e regex sono diverse.

Esempio. La wildcard:

le rose sono *se

fa match con:

le rose sono rosse

ma anche con:

le rose sono costose

e:

le rose sono grosse

ma non con:

i maneggi abitano in montagna

Le wildcard possono essere combinate, ad esempio:

test?[a-z][!0-9]

fa il match con tutti i percorsi che cominciano con test, proseguono con un carattere qualunque, poi con un carattere alfabetico ed infine con un carattere non numerico.


Esempio. Un esempio piu’ realistico. Il comando:

cat data/dna-fasta/*.[12]

fa match con tutti i file nella directory data/dna-fasta il cui filename e’ composto di una-stringa-qualunque, seguita da un punto, seguito da 1 o 2 e nient’altro. Nel nostro caso i soli file a fare match sono:

data/dna-fasta/fasta.1
data/dna-fasta/fasta.2

Dopo la wildcard expansion il comando precedente diventa:

cat data/dna-fasta/fasta.1 data/dna-fasta/fasta.2

Esempio. Per stampare a schermo i contenuti della directory data, scrivo:

ls data

Per stampare i contenuti delle directory che stanno in data:

ls data/*

qui la wildcard * viene espansa in:

aatable deep0 ... deep4 dna-fasta empty1 empty2 prot-fasta prot-pdb simple1

Per stampare a schermo solo il contenuto delle directory deep0, …, deep4:

ls data/deep*

Mentre per restringere la wildcard alle directory deep0 e deep3:

ls data/deep[03]

e solo per le directory deep0, …, deep4 ma non deep2:

ls data/deep[!2]

Esercizi

  1. Cosa fa il comando:
    1. echo *?
    2. echo '*'?
    3. cat data/simple1/*.txt?
  2. Stampare il contenuto dei file .txt in data/simple1.
  3. Stampare il contenuto dei file .abc in data/simple1.
  4. Concatenare il contenuto dei file .txt in data/simple1 in un nuovo file temp.
  5. Concatenare il contenuto dei file .abc in data/simple1 ed aggiungerlo in coda a temp.
  6. Tra i file in /usr/bin, trovare con ls quelli che:
    1. Iniziano per una cifra.
    2. Iniziano e finiscono per x.
    3. Iniziano o finiscono per x.


Filtri

La shell mette a disposizione un numero di comandi che agiscono da filtri: il loro scopo e’ permettervi di estrarre righe/colonne da file di testo, ordinare i dati, sostituire caratteri, ed in generale calcolare statistiche sui dati.

comando funzione opzioni principali
wc conta caratteri, parole e righe -m, -w, -l
sort ordina righe -f, -n`, ``-r
uniq rimuove righe consecutive identiche -c, -d
cut stampa una o piu’ colonne -d, -f
tr traduce caratteri -d, -s
grep seleziona righe in base ad una regex -E, -i, -v

Vediamoli all’opera.



Esempio. Creo un file con due righe di testo:

echo uno > file; echo due >> file

Controllo quante righe ho scritto:

wc -l file

Esempio. Per contare il numero di righe di data/numers.1:

wc -l data/numbers.1

Per contare quanti file e directory ci sono nella home:

ls ~ | wc -l

Esempio. Concateniamo i file in data/dna-fasta:

cat data/dna-fasta/* > all_fastas

Vogliamo stampare le righe di all_fastas in ordine alfanumerico:

sort all_fastas

Ed ora vogliamo copiarle in un file:

sort all_fastas > sorted_fastas

Possiamo ottenere lo stesso effetto con una pipeline:

cat data/dna-fasta/* | sort > sorted_fastas

Esempio. I file data/numbers.1 e data/numbers.2 contengono liste di numeri. Vogliamo controllare se ci sono doppioni usando uniq.

C’e’ un problema: uniq trova solo doppioni sequenziali. Se lo applico a questo testo:

aaaa
bbbb
bbbb
aaaa

uniq riesce si’ a capire che bbbb e’ ripetuto, ma non ci riesce con aaaa.

Quindi se voglio trovare tutti i doppioni indipendentemente dall’ordine in cui si trovano nel file che mi interessa, devo prima usare sort per avvicinare le righe identiche. Nel nostro caso, faccio:

sort data/numbers.1 > temp1
sort data/numbers.2 > temp2
uniq -d temp1
uniq -d temp2

Non ci sono ripetizioni nei singoli file. Ci sono forse doppioni nei file presi assieme?

Siamo tentati di usare:

cat temp[12] | uniq -d

Pero’ non e’ detto che il concatenamento di due file ordinati produca un file ordinato. Quindi usamo di nuovo sort:

cat temp[12] | sort | uniq -d

Il numero 3 appare piu’ volte! Quante? Verifichiamo:

cat temp[12] | sort | uniq -d -c

Due volte. In alternativa:

cat temp[12] | sort | uniq -d | wc -l

Provate a ripetere il codice costruendo incrementalemente la pipeline.


Esempio. Il comando tr permette di sostituire (tradurre) caratteri con altri caratteri. Data una sequenza nucleotidica voglio sostituire tutte le timine T con uracile U, scrivo:

echo TATAAA | tr 'T' 'E'

Tra gli usi piu’ comuni di tr:

  • Tradurre da maisucolo a minuscolo (e viceversa):

    echo 'voglio diventare grande!' | tr 'a-z' 'A-Z'
    
  • Sostituire i caratteri (nascosti) “a capo”, \n, con altri. Confrontate:

    ls data
    

    e:

    ls data | tr '\n' ','
    
  • Rimuovere ripetizioni di caratteri, con l’opzione -s (squeeze):

    echo 'voglio     uno     spazio     solo!' | tr -s ' '
    
  • Rimuovere caratteri estranei con -d, ad esempio da una sequenza proteica:

    echo 'xixxxox sxxxonxxo uxxxxnaxx xxxxproxxxtxexxinxa' | tr -d ' '
    

Esempio. Il comando cut estrae colonne (non righe!) dallo stdin. Ha due opzioni fondamentali (e poco opzionali):

  • -d specifica il separatore: il carattere che separa le colonne.
  • -f specifica quali colonne (fields, campi) estrarre.

Ad esempio, assumete di avere un file con questo testo:

nome cognome anno-di-nascita
Marco Rossi 1989
Luisa Bianchi 1981
Dante Alighieri 1265

Qui le colonne sono separate da semplici spazi. Posso estrarre la colonna dei nomi con:

cut -d' ' -f1 file

e quella delle date con:

cut -d' ' -f3 file

Se volessi estrarre solo i nomi saltando la prima riga:

tail -n +2 file | cut -d' ' -f1

oppure:

cut -d' ' -f1 file | tail -n +2

o ancora:

cat file | cut ... | tail ...

Assumete che il file contenga:

nome,cognome,anno-di-nasciata,impatto-sui-posteri
Dante,Alighieri,1265,9
Marcel,Proust,1871,7
Homer,Simpson,1989,10

Qui la virgola funge da separatore. Per estrarre i cognomi, scrivo:

tail -n +2 file | cut -d',' -f2

Possiamo anche estrarre piu’ di una colonna, ad esempio nome, cognome e data di nascita:

tail -n +2 file | cut -d',' -f1,2,3

oppure:

tail -n +2 file | cut -d',' -f1-3

Per estrarre nome, cognome e impatto:

tail -n +2 file | cut -d',' -f1,2,4

Per estrarre tutte le colonne dall’anno in poi:

tail -n +2 file | cut -d',' -f3-

Esercizi

  1. Confronta wc A e cat A | wc.
  2. Confronta wc -l A e cat A | tr '\n' ' ' | wc -w.
  3. Quanti file sono contenuti in /usr/bin/?
  4. Stampare i file in /usr/bin ordinati per dimensione, sia con ls da solo che con ls | sort ....
  5. Stampare solo il file piu’ piccolo in /usr/bin.
  6. Stampare i numeri in data/numbers.1 e data/numbers.2 ordinati dal piu’ piccolo al piu’ grande.
  7. Stampare i numeri in data/numbers.1 e data/numbers.2 ordinati dal piu’ grande al piu’ piccolo.
  8. Ci sono file doppi in /usr/bin?
  9. Scrivere in listaN.txt la lista di tutti i file in data/deepN, per N=1,2,3.
  10. Scrivere in dataN.txt i contenuti di tutti i file in data/deepN, per N=1,2,3.
  11. Quante repliche dispari di KrustyIlKlown ci sono in data/deep1?
  12. Cosa fa echo ACAB | cut -dC -f2? E echo BACA | cut -dA -f1,2?
  13. Compara wc -m A e cat A | wc | tr -s ' ' | cut -d' ' -f4
  14. Stampa i file in /usr/bin ordinati per proprietario. (Si veda -k nel manuale di sort).
  15. Come sopra, ma in ordine inverso. E’ necessario tac?
  16. Stampare solo la dimensione del file piu’ piccolo in /usr/bin.
  17. Stampare solo il nome del file piu’ grande in /usr/bin.
  18. Ci sono file di dimensioni identiche in /usr/bin? Quanti?


Espressioni regolari e grep

grep serve per estrarre righe che combaciano con una data espressione regolare: viene usato spesso per filtrare le righe di un file (o dello stdin) alle quali siamo interessati, scartando tutte le altre.

La sintassi e’:

grep regex file

oppure:

cat file | grep regex

Una lista (non esaustiva) delle regex estese:

Simbolo Fa match con
testo La stringa testo
. Un carattere qualunque
[abc] Un carattere qualunque tra a, b e c
[a-z] Un carattere nell’intervallo a, …, z
[^abc] Un carattere che non sia a, b, o c
$ La fine della riga (il carattere \n)
^ L’inizio della riga (il carattere dopo \n)
regex* almeno zero ripetizioni di regex
regex+ almeno una ripetizione di regex
regex{n} n ripetizioni di regex
regex{n,m} tra le n e le m ripetizioni di regex
(regex1|regex2) regex1 oppure regex2

Warning

Spesso useremo regex estese. Per fare in modo che grep le riconosca, useremo l’opzione -E.

Warning

Le regex hanno alcuni caratteri speciali in comune con le wildcards.

Percio’ quando invochiamo grep, e’ necessario inibire i caratteri speciali della regex usata, in modo che la shell non esegua la wildcard expansion.

Il modo piu’ semplice di farlo e’ usare virgolette:

grep 'regex' file

oppure:

cat file | grep 'regex'

Opzioni principali di grep:

opzione funzione
-v matching inverso
-o mostra soltanto il pattern trovato
-i ignora distinzione maiuscole/minuscole

Esempio. Le seguenti regex combaciano con:

  • .*, tutte le stringhe (anche la stringa vuota).
  • .+, tutte le stringhe (ma non quella vuota).
  • abc, tutte le stringhe che contengono abc.
  • [abc], tutte le stringhe che contengono almeno una tra a, b, e c.
  • ^abc, tutte le stringhe che iniziano per abc.
  • abc$, tutte le stringhe che finiscono per abc.
  • ^abc$, la sola stringa abc.
  • ^.*$, tutte le stringhe terminate da un carattere di a capo \n.
  • [a-z], tutte le stringhe che contengono almeno un carattere alfabetico minuscolo.
  • ^[A-Z ]$, tutte le stringhe che contengono solo caratteri alfabetici maiuscoli e spazi.
  • ^[01 ]{3+}$, tutte le stringhe di almeno tre caratteri che rappresentano parole binarie.
  • ant(onio|idiluviano), tutte stringhe che contengono antonio o antidiluviano (o entrambi).
  • ^[ ,](X{10}|Y{10})[ ,], tutte le stringhe che iniziano con uno spazio o una virgola, seguito da dieci X o dieci Y, seguiti da uno spazio o una virgola.

Esempio. Per costruire regex che facciano match con caratteri speciali (ad esempio il punto .), posso usare l’escaping o inserirli tra parentesi quadre. Ad esempio:

grep '.' data/aatable

estrae “tutte le righe che contengono almeno un carattere”, mentre:

grep '\.' data/aatable
grep '[.]' data/aatable

estraggono “tutte le righe che contengono un punto”.

Note

L’opzione --color chiede a grep di colorare il match.


Esempio. Prendiamo il file 1A34.fasta da qui. Contiene la sequenza aminoacidica delle tre catene della proteina 1A34:

>1A34:A|PDBID|CHAIN|SEQUENCE
MGRGKVKPNRKSTGDNSNVVTMIRAGSYPKVNPTPT
WVRAIPFEVSVQSGIAFKVPVGSLFSANFRTDSFTS
VTVMSVRAWTQLTPPVNEYSFVRLKPLFKTGDSTEE
FEGRASNINTRASVGYRIPTNLRQNTVAADNVCEVR
SNCRQVALVISCCFN
>1A34:B|PDBID|CHAIN|SEQUENCE
AAAAAAAAAA
>1A34:C|PDBID|CHAIN|SEQUENCE
UUUUUUUUUU

Ogni catena compare come un’intestazione (o header) che comincia col carattere >, seguita dalla sequenza vera e propria.

Per estrarre le intestazioni, sfrutto il fatto che cominciano per >:

grep '>' 1A34.fasta

Il risultato e’:

>1A34:A|PDBID|CHAIN|SEQUENCE
>1A34:B|PDBID|CHAIN|SEQUENCE
>1A34:C|PDBID|CHAIN|SEQUENCE

Ancora meglio, costringo grep a cercare > all’inizio della riga (e non, ad esempio, nel bel mezzo di una sequenza proteica):

grep '^>' 1A34.fasta

Per estrarre invece le sequenze:

grep -v '^[^>]' 1A34.fasta

Esempio. Se siamo interessati a scoprire se, tra le catene in data/prot-fasta/ quali contengono la sequenza “DP” (leggi: acido aspartico seguito da prolina):

grep DP data/prot-fasta/*.fasta

Il risultato e’:

data/prot-fasta/3J00.fasta:FVIDADHEHIAIKEANNLGIPV...
data/prot-fasta/3J00.fasta:PRRRVIGQRKILPDPKFGSELL...
data/prot-fasta/3J00.fasta:SMQDPIADMLTRIRNGQAANKA...
data/prot-fasta/3J01.fasta:AKGIREKIKLVSSAGTGHFYTT...
data/prot-fasta/3J01.fasta:EYDPNRSANIALVLYKDGERRY...
data/prot-fasta/3J01.fasta:ARNLHKVDVRDATGIDPVSLIA...

Quindi si’, abbiamo risposto alla nostra domanda: ci sono ben due proteine (3F00 e 3J01) che includono il pattern “DP”.

Per controllare di non avere mai fatto match con le intestazioni, scrivo:

grep DP data/prot-fasta/*.fasta | grep '^>'

grep non stampa niente, percio’ non abbiamo mai tenuto intestazioni. Bene. Per evitare sorprese, possiamo filtrare via le intestazioni a priori con:

grep -v DP data/prot-fasta/*.fasta | grep -v '^>'

Esempio. Costruiamo una pipeline complessa.

Dato l’output di grep DP ..., voglio stampare il nome delle proteine che contengono la sequenza DP. L’output di grep era:

data/prot-fasta/3J00.fasta:FVIDADHEHIAIKEANNLGIPV...
data/prot-fasta/3J00.fasta:PRRRVIGQRKILPDPKFGSELL...
data/prot-fasta/3J00.fasta:SMQDPIADMLTRIRNGQAANKA...
data/prot-fasta/3J01.fasta:AKGIREKIKLVSSAGTGHFYTT...
data/prot-fasta/3J01.fasta:EYDPNRSANIALVLYKDGERRY...
data/prot-fasta/3J01.fasta:ARNLHKVDVRDATGIDPVSLIA...

Usiamo cut per tagliare la colonna dei nomi dei file (che contiene 3J00 e 3J01). Come prima cosa, uniformiamo i delimitatori con tr:

grep DP data/prot-fasta/*.fasta | tr '/.' ' '

ottenendo:

data prot-fasta 3J00 fasta:FVIDADHEHIAIKEANNLGIPV...
data prot-fasta 3J00 fasta:PRRRVIGQRKILPDPKFGSELL...
data prot-fasta 3J00 fasta:SMQDPIADMLTRIRNGQAANKA...
data prot-fasta 3J01 fasta:AKGIREKIKLVSSAGTGHFYTT...
data prot-fasta 3J01 fasta:EYDPNRSANIALVLYKDGERRY...
data prot-fasta 3J01 fasta:ARNLHKVDVRDATGIDPVSLIA...

Ora uso cut per tenere solo la colonna giusta:

grep DP data/prot-fasta/*.fasta | tr './' ' ' | cut -d' ' -f3

ottenendo:

3J00
3J00
3J00
3J01
3J01
3J01

A questo punto uso sort e uniq per rimuovere le ripetizioni:

grep ... | tr '/.' ' ' | cut -d' ' -f3 | sort | uniq

ottenendo:

3J00
3J01

In alterantiva posso invocare cut due volte:

grep DP ... | cut -d'/' -f3 | cut -d'.' -f1 | ...

Note

Ci sono molti comandi utilissimi che non fanno parte del corso. Per chi fosse interessato, una lista dei piu’ importanti:

  • paste, il contrario di cut: concatena colonne orizzontalmente.
  • rev, stampa una riga dalla fine all’inizio.
  • sed, permette di rimuovere o sostituire intere stringhe – un tr molto piu’ potente
  • awk, permette manipolazioni arbitrariamente complesse, ad esempio di fare aritmetica
  • bc, un calcolatore
  • e molti, molti altri ancora…

Esercizi

  1. Cosa significano le seguenti regex (se valide)?
    1. .
    2. .*
    3. [09]{2}
    4. [0-9]{2}
    5. *
    6. [
    7. [[]
    8. ^.3
    9. ^.{3}
    10. .{3}$
    11. ^>
    12. AA
    13. ^AA$
    14. aA
    15. [aA]
    16. word
    17. w..d
    18. ^$
    19. [}{]
    20. [0-9]+
  2. Scrivere una regex che facciano match con:
    1. Tutti i caratteri alfanumerici, maiuscoli e minuscoli
    2. Le righe contenenti solo spazi
    3. Le righe che contengono punti esclamativi o punti interrogativi
    4. I giorni della settimana (nel modo piu’ compatto possibile) Senza lettere accentate. La shell non le digerisce.
    5. Le parole di radice frazion-, ad esempio: frazione, frazionario, etc.
    6. I multipli di 10, i multipli di 5, i numeri dispari
    7. I numeri razionali come frazione, ad esempio: p/q
    8. I numeri razionali in notazione decimale, ad esempio: 1.34, .99, 17., 3
    9. I numeri razionali in notazione scientifica, ad esempio: 1.34e10, 1.34e-10
    10. Le somme (esempio: a+b+c+d, a+b, etc.) di lunghezza arbitraria, dove a, b, c, … sono numeri interi
    11. Le somme di due moltiplicazioni, cose come: (2 * 3 * 2) + (5 * 7), (6 * 2) + (4 * 3), etc.
  3. Sequenze fasta:

    Note

    Nei prossimi esercizi faremo uso di questo file sequences.fasta:

    Gli esercizi chiedono di contare quante sequenze nel file FASTA contengono (o meno) contengono un motivo data. Il motivo puo’ essere naturalmente espresso da una (o piu’) regex.

    • Quando scrivo “residuo1; residuo2; residuo3” intendo “residuo1; seguito nella sequenza da residuo2; seguito da residuo3”. Ometto i vari “seguito da” per compattezza.
    • Quando scrivo “un aminoacido qualunque”, intendo un carattere qualunque, per semplicita’.

    Per contare quante sequenze, si consiglia l’uso di wc in pipe con grep.

    1. Calcolare quante sequenze contengono i seguenti motivi:

      • Una fenilalanina (F); due aminoacidi arbitrari; un’altra fenilalanina. Il motivo deve apparire alla fine della sequenza.
      • Una arginina (R); una fenilalanina; un aminoacido che non e’ una prolina (P); una isoleucina (I) oppure una valina (V).
      • Calcolare inoltre quante sequenze includono almeno uno dei due motivi.
    2. Calcolare quante sequenze contengono i seguenti motivi:

      • Tre tirosine (Y); al piu’ tre amino acidi qualunque; una istidina (H).
      • Un aminoacido non standard o un residuo sconosciuto X.
      • Ci sono sequenze che soddisfano entrambe le condizioni?

      Note

      Gli aminoacidi standard sono: A R N D C E Q G H I L K M F P S T W Y V.

    3. Calcolare quante sequenze contengono i seguenti motivi:

      • Un arginina (R); una lisina (K). Il motivo non deve apparire all’inizio della sequenza.
      • Due arginine seguite da un amino acido che non sia ne’ una arginina, ne’ una lisina.
      • Nessuno dei due motivi precedenti.
    4. Clacolare quante sequenze contengono i seguenti motivi:

      • Una fenilalanina (F); un aminoacido qualunque; una fenilalanina o una tirosina (Y); una prolina (P).
      • Una prolina; una treonina (T) o una serina (S); una alanina (A); un’altra prolina. Il motivo non deve apparire ne’ all’inizio, ne’ alla fine della sequenza.
      • Il primo motivo seguito dal secondo, oppure il secondo seguito dal primo.

Shell: Parte 3 (Soluzioni)

Wildcards, Parte 2

  1. Soluzioni:
    1. echo * contiene una wildcard; la shell effettua la wildcard expansion sostituendo * con la lista dei path che fanno il match con la wildcard. In questo caso * fa il match con i file e le directory contenuti nella directory corrente. Quindi echo stampa a schermo i path che hanno fatto match.
    2. echo '*': qui la wildcard e’ protetta dalle virgolette, niente wildcard expansion. echo stampa il carattere * a schermo.
    3. cat data/simple1/*.txt?
  2. cat data/simple1/*.txt.
  3. cat data/simple1/*.abc.
  4. cat data/simple1/*.txt > temp.
  5. cat data/simple1/*.abc >> temp. Usiamo >> per non sovrascrivere temp.
  6. Soluzioni:
    1. ls /usr/bin/[0-9]*
    2. ls /usr/bin/x*x
    3. ls /usr/bin/x* /usr/bin/*x

Filtri

  1. Soluzioni:

    1. wc A stampa il numero di righe, parole e caratteri nel file A.
    2. wc in cat A | wc stampa il numero di righe, parole e caratteri nello stdin, ch in questo caso combacia con il contenuto di A.

    Quindi in principio fanno la stessa cosa. Attenzione pero’ che l’output nei due casi e’ leggermente diverso.

  2. Soluzioni:

    1. wc -l A stampa il numero di righe nel file A.
    2. cat A | tr '\n' ' ' | wc -w passa i contenuti di A alla pipe, che li passa a tr, che sostituisce tutti i caratteri di a capo \n con spazi (cioe’ mette tutto il testo di A in una riga sola, senza \n alla fine); poi wc conta le parole nel risultato.

    Quindi i due comandi fanno cose completamente diverse: il primo conta le righe, il secondo le parole.

  3. ls /usr/bin | wc -l

  4. ls -S /usr/bin, oppure:

    ls -l /usr/bin | tr -s ' ' | cut -d' ' -f5,9 | sort -n -k1
    
  5. ls -S /usr/bin | tail -n 1, oppure:

    ls -l /usr/bin | tr -s ' ' | cut -d' ' -f5,9 | sort -n -k1 | head -n 1
    
  6. Soluzioni:

    cat data/numbers.[12] | sort -n
    
  7. Soluzioni:

        cat data/numbers.[12] | sort -n -r
    
    oppure::
    
        cat data/numbers.[12] | sort -n | tac
    
  8. Controllo:

        ls /usr/bin | sort | uniq -d
    
    Non stampa nessun duplicato: la risposta e' no. (Basta pensare che una
    directory non puo' contenere due file con lo stesso nome.)
    
  9. Soluzioni:

    ls data/deep1/*/* > lista1.txt
    ls data/deep2/*/*/* > lista2.txt
    ls data/deep3/*/*/*/* > lista3.txt
    
  10. Soluzioni:

    cat data/deep1/*/* > lista1.txt
    cat data/deep2/*/*/* > lista2.txt
    cat data/deep3/*/*/*/* > lista3.txt
    
  11. Soluzioni:

        cat data/deep1/*/* | grep '[13579]$' | wc -l
    
    Risposta: 50.
    
  12. Soluzioni:
    • echo ACAB | cut -dC -f2 stampa la scritta ACAB, la passa a
      cut, che usando C come delimitatore stampa la seconda colonna: AB.
    • echo BACA | cut -dA -f1,2 stampa la scritta BACA, la passa a
      cut, che usando A come delimitatore stampa la prima e seconda colonna: BAC.
  13. Soluzioni:
    • wc -m A stampa il numero di caratteri nel file A.
    • cat A | wc | tr -s ' ' | cut -d' ' -f4 stampa i contenuti di
      A a schermo; poi wc stampa il numero di righe, parole e caratteri a schermo su una sola riga di output; poi tr riduce spazi multipli ad uno solo; infine cut, usando lo spazio `` `` come delimitatore, stampa la quarta colonna – che corrisponde al numero di caratteri nel file. Quindi il risultato e’ come sopra.
  14. Nell’output di ls -l il proprietario si trova nella terza colonna. Quindi e’ sufficiente fare:

    ls -l /usr/bin | tr -s ' ' | sort -k 3
    
  15. Ci sono almeno due possibilita’:

    ls -l /usr/bin | tr -s ' ' | sort -k 3 | tac
    

    oppure:

    ls -l /usr/bin | tr -s ' ' | sort -k 3 -r
    
  16. Per stampare la lista dei file ed ordinarli dal

    ls /usr/bin/ -l | tr -s ' ' | sort -n -k 5
    

    L’output sara’ simile a questo:

    $ ls /usr/bin/ -l | tr -s ' ' | sort -n -k 5
    total 260336
    lrwxrwxrwx 1 root root 1 May 6 2013 X11 -> .
    lrwxrwxrwx 1 root root 2 Aug 1 19:50 ghostscript -> gs
    lrwxrwxrwx 1 root root 2 Aug 23 12:49 inimf -> mf
    lrwxrwxrwx 1 root root 2 Dec 13 2014 mcdiff -> mc
    lrwxrwxrwx 1 root root 2 Dec 13 2014 mcedit -> mc
    lrwxrwxrwx 1 root root 2 Dec 13 2014 mcview -> mc
    lrwxrwxrwx 1 root root 2 Jul 3 21:44 unxz -> xz
    lrwxrwxrwx 1 root root 2 Jul 3 21:44 xzcat -> xz
    ...
    

    I file sono ordinati correttamente; ora dobbiamo estrarre la seconda riga (la prima e’ un sommario stampato da ls, e dobbiamo scartarla). Per estrarre la seconda riga posso aggiungere head e tail:

    ... | head -n 2 | tail -n 1
    

    oppure usare grep:

    ... | grep -v '^total' | head -n 1
    

    In entrambi i casi ottengo una riga sola, simile a questa:

    lrwxrwxrwx 1 root root 1 May 6 2013 X11 -> .
    

    A questo punto e’ sufficiente estrarre la dimension (nel mio caso 1) aggiungendo cut:

    ... | cut -d' ' -f5
    

    Ci sono un numero di alternative altrettanto valide.

  17. Simile a prima:

    ls /usr/bin -l | tr -s ' ' | sort -n -k 5 | tail -n 1 | cut -d' ' -f9
    
  18. Stampo la lista dei file, estraggo le dimensioni, e uso sort | uniq:

        ls /usr/bin -l | tr -s ' ' | sort -n -k 5 | cut -d' ' -f5 | sort | uniq -d | wc -l
    
    Il ``wc -l`` alla fine serve per contare il numero di duplicati trovati da
    ``uniq -d``. Nel mio caso ce ne sono 166. In breve, la risposta e' si'.
    

Espressioni regolari e grep

  1. Soluzioni:
    1. .: valida, un carattere qualunque, in qualunque posizione. Leggi: una stringa di almeno un carattere.
    2. .*: valida, un numero arbitrario di caratteri qualunque, anche zero. Leggi: una stringa qualunque.
    3. [09]{2}: valida, esattamente due caratteri 0 oppure 9. Leggi: fa match con 00, 09, 90 e 99.
    4. [0-9]{2}: valida, esattamente due caratteri numerici qualunque. Leggi: stringhe di due caratteri tra 0 e 9.
    5. *: valida, il carattere asterisco *. (Si noti la differenza con le wildcards!)
    6. [: non valida.
    7. [[]: valida, fa match con la parentesi quadra aperta [ in qualunque posizione.
    8. ^.3: valida, fa match con stringhe che iniziano con un carattere qualunque e proseguono con 3.
    9. ^.{3}: valida, fa il match con stringhe lunghe almeno tre caratteri.
    10. .{3}$: valida, come sopra.
    11. ^>: valida, fa match con stringhe che iniziano per >.
    12. AA: valida, fa match con stringhe che contengono la sotto-stringa AA, in qualunque posizione.
    13. ^AA$: valida, fa match on la sola stringa AA.
    14. aA: valida, fa match con stringhe che contengono la sotto-stringa aA, in qualunque posizione.
    15. [aA]: valida, fa match con stringhe che contengono una a o una A, o entrambe, in qualunque posizione.
    16. word: valida, fa match con stringhe che contengono la parola word, in qualunque posizione.
    17. w..d: valida, fa match con stringhe che contengono parole che cominciano con w, finiscono per d, e sono lunghe quattro caratteri.
    18. ^$: valida, fa match con righe vuote.
    19. [}{]: valida, fa match con righe che contengono una alemno parentesi graffa.
    20. [0-9]+: valida, fa match con righe che contengono una sotto-stringa numerica lunga almeno un carattere.
  2. Soluzioni:
    1. Tutti i caratteri alfanumerici, maiuscoli e minuscoli:

      [a-zA-Z]
      
    2. Le righe contenenti solo spazi:

      ^[ ]*$
      
    3. Le righe che contengono punti esclamativi o punti interrogativi:

      [!?]
      
    4. I giorni della settimana (nel modo piu’ compatto possibile) Senza lettere accentate:

          (lune|marte|mercole|giove|vener)di|sabato
      
      (Non uso lettere accentate per comodita'.)
      
    5. Le parole di radice frazion-, ad esempio: frazione, frazionario, etc.:

      frazion.*
      
    6. I multipli di 10, i multipli di 5, i numeri dispari:

      [0-9]*0
      [0-9]*[05]
      [0-9]*[13579]
      
    7. I numeri razionali come frazione:

      [0-9]*/[0-9]*
      
    8. I numeri razionali in notazione decimale, ad esempio: 1.34, .99, 17., 3:

      [0-9]*(\.[0-9]*)?
      
    9. I numeri razionali in notazione scientifica, ad esempio: 1.34e10, 1.34e-10:

      [0-9]*(\.[0-9]*|[0-9]+e[+-]?[0-9]+)?
      
    10. Le somme (esempio: a+b+c+d, a+b, etc.) di lunghezza arbitraria, dove a, b, c, … sono numeri interi:

      [0-9]+(\+[0-9]+)+
      
    11. Le somme di due moltiplicazioni, cose come: (2 * 3 * 2) + (5 * 7), (6 * 2) + (4 * 3), etc.: E’ sufficiente generalizzare la soluzione all’esercizio precedente.

  3. Sequenze fasta:

    Eseguendo un:

    cat sequences.fasta
    

    ci si puo’ accorgere che nel file si alternano righe di intestazione (che cominciano con ‘>’) e relative sequenze. Negli esercizi seguenti, per filtrare le intestazioni uso:

    cat sequences.fasta | grep -Ev ">"
    

    L’opzione ‘-v’ di grep fa il matching inverso, ovvero tiene le righe che NON fanno match con la relativa regex. In alternativa posso usare:

    cat sequences.fasta | grep -E "^[^>]"
    

    Per fare match con tutte le righe che non cominciano con ‘>’.

    1. Soluzioni:

      • Comando:

        cat sequences.fasta | grep -Ev ">" | grep -E 'F..F$' | wc -l
        

        Numero di match: 1

      • Comando:

        cat sequences.fasta | grep -Ev ">" | grep -E 'RF[^P][IV]' | wc -l
        

        Numero di match: 64

      • Comando:

        cat sequences.fasta | grep -Ev ">" | grep -E '(F..F$|RF[^P][IV])' | wc -l
        

        Numero di match: 65

    2. Soluzioni:

      • Comando:

        cat sequences.fasta | grep -Ev ">" | grep -E 'YYY.{0,3}H' | wc -l
        

        Numero di match: 3

      • Comando:

        cat sequences.fasta | grep -Ev ">" | grep -E '[^ARNDCEQGHILKMFPSTWYV]' | wc -l
        

        Numero di match: 8

      • Comando:

        cat sequences.fasta | grep -Ev ">" | grep -E '[^ARNDCEQGHILKMFPSTWYV]' | grep -E
        'YYY.{0,3}H' | wc -l
        

        Numero di match: 0

    3. Soluzioni:

      • Comando:

        cat sequences.fasta | grep -Ev ">"  | grep -E '^[^R][^K].*RK' | wc -l
        

        Numero di match: 713

      • Comando:

        cat sequences.fasta | grep -Ev ">"  | grep -E 'RR[^RK]' | wc -l
        

        Numero di match: 604

      • Comando:

        cat sequences.fasta | grep -Ev ">"  | grep -vE '^[^R][^K].*RK' |
        grep -vE 'RR[^RK]' | wc -l
        

        Numero di match: 49

    4. Soluzioni:

      • Comando:

        cat sequences.fasta | grep -Ev ">"  | grep -E 'F.[FY]P' | wc -l
        

        Numero di match: 44

      • Comando:

        cat sequences.fasta | grep -Ev ">"  | grep -E '^.+P[TS]AP.+$' | wc -l
        

        Numero di match: 19

      • Comando:

        cat sequences.fasta | grep -Ev ">"  | grep -E '(^.+P[TS]AP.*F.[FY]P|F.[FY]P.*P[TS]AP.+$)' | wc -l
        

        Numero di match: 6



Python: Fondamentali

Interprete

Python si riferisce a:

  • il linguaggio Python, un insieme di regole sintattiche e semantiche che definiscono il comportamento di un programma scritto in Python.
  • l’interprete python, un comando eseguibile dalla shell che permette di eseguire codice scritto nel linguaggio Python.

Per far partire l’interprete, da una shell scrivete:

python

Warning

In alcune distribuzioni, la versione di default e’ ancora la 2.x. Tipicamente e’ possibile avviare Python 3 tramite il comando:

python3

Vi apparira’ una schermata di testo simile a questa:

Python 3.5.2 (default, Sep 14 2017, 22:51:06)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

Per eseguire codice python, scrivetelo nell’interprete e date invio, ad esempio:

print("hello, world!")

Per chiudere l’interprete, premete Control-d in una riga vuota.

Hint

Oltre all’interpreter python, che e’ il default, esistono altri interpreti Python piu’ usabili, ad esempio ipython:

L’interprete ipython funziona esattamente come python, ma dispone di funzionalita’ avanzate, come ad esempio la colorazione della sintassi (syntax highlighting), che possono facilitare la stesura del codice.


Moduli

Un’alternativa all’eseguire codice nell’interprete e’ scrivere un modulo: un file di testo con estensione .py in cui scrivete il codice da eseguire.

Warning

L’estensione .py e’ obbligatoria!

Per eseguire un modulo, diciamo il file eseguibile.py, scrivo dalla shell:

python eseguibile.py

Per utilizzare le funzioni definite in un modulo all’interno di un altro, uso import. Ad esempio, se in eseguibile.py voglio usare la funzione ordina_proteine() precedentemente definita nel modulo util.py, scrivo all’inizio di eseguibile.py:

import util

Warning

Quando importate un modulo, omettete l’estensione .py.

A questo punto posso usare ordina_proteine() cosi’:

util.ordina_proteine()

Warning

I contenuti di un modulo importato vengono prefissati col nome del modulo: qui abbiamo invocato util.ordina_proteine, non ordina_protein liscio.


Oggetti

Un oggetto rappresenta dei dati di qualche tipo (un intero, una stringa, una lista, etc.) sui cui operiamo in qualche modo.

Ogni oggetto ha:

  • un tipo, che specifica che cosa l’oggetto rappresenta.
  • un valore.

I tipi fondamentali sono:

Tipo Significato Valori Mutabile
bool Condizioni True, False No
int Interi \{-2^{-31},\ldots,2^{31}-1\} No
float Razionali \mathbb{Q} No
str Testo Testo No
list Sequenze Eterogenei Si’
tuple Sequenze Eterogenei No
dict Mappe Eterogenei Si’
set Insiemi Eterogenei Si’

Variabili

Le variabili sono contenitori di riferimenti ad oggetti. Possono essere viste come nomi per gli oggetti a cui si riferiscono.

Un oggetto viene assegnato ad una variabile con =:

pi = 3.1415926536

Qui la variabile di nome pi si riferisce all’oggetto 3.1415926536 di tipo float. In sostanza abbiamo deciso che pi e’ un nome per l’oggetto 3.1415926536.

Warning

Il nome della variabile e’ arbitrario!

Lo decidiamo noi in modo che sia conveniente: breve, descrittivo degli oggetti a cui si riferisce, indicativo del ruolo che la variabile svolge nel nostro codice, etc.

Il tipo di una variabile e’ il tipo dell’oggetto a cui si riferisce. Ne segue che il tipo di pi e’ float.

Warning

La variabile non contiene l’oggetto, ma un riferimento a quell’oggetto.

Per stampare il valore di una variabile, uso la funzione print:

variabile = "sono una esempio"
print(variabile)

Per stampare il tipo di una variabile, uso la funzione type:

variabile = "sono un esempio"
print(type(variabile))

Esempio. Creo una nuova variabile var:

var = 123

Il nome di var e’ var, il suo valore e’ 123, il suo tipo e’ int.

Esempio. Una variabile puo’ essere assignata piu’ volte:

var = 1
var = "MANLFKLGAENIFLGRKAATKEEAIRF"
var = 3.1415926536

Il nome della variabile resta sempre lo stesso, ma tipo e valore cambiano ad ogni passaggio: controlla con print(var) e print(type(var)).

Esempio. L’assegnamento funziona anche tra variabili:

a = "sono un esempio"
print(a)
b = a
print(b)

Qui l’oggetto riferito da a viene assegnato a b, quindi sia a che b si riferiscono allo stesso oggetto.

Posso anche scrivere:

a = "sono un esempio"
b = "sono un esempio"

L’effetto e’ diverso: qui a e b si riferiscono a due oggetti diversi con lo stesso valore!


Funzioni e Metodi

Python mette a disposizione un’ampia gamma di funzioni e metodi, ad esempio print, che stampa il valore di una espressione a schermo, e help, che visualizza l’aiuto.

  • Una funzione esegue un’operazione sui suoi argomenti, e puo’ restituire un risultato.

    Una funzione e’ invocata cosi’:

    risultato = funzione(argomento1, ..., argomentoN)
    

    Qui risultato e’ una variabile arbitraria.

  • Un metodo e’ una funzione associata ad un tipo: ci sono i metodi degli interi, delle stringhe, delle liste, etc.

    L’invocazione dei metodi somiglia a quella delle funzioni:

    risultato = oggetto_o_variabile.metodo(argomento1, ..., argomentoN)
    

C’e’ un’analogia con le funzioni matematiche:

y = f(x_1, \ldots, x_n)

Con delle importanti differenze:

  • Non tutte le funzioni (e metodi) Python richiedono argomenti, o ritornano un risultato.
  • Le funzioni matematiche possono operare solo sui loro inputs x_1,\ldots,x_n e dall’esterno possiamo osservare solo il loro risultato y. Al contrario, le funzioni Python possono avere side-effects, ad esempio stampare informazioni a schermo, o modificare variabili che non sono i loro argomenti.
  • La variabile risultato e’ una variabile Python (di nome arbitrario, lo decidiamo noi!) in cui viene messo il risultato della funzione.

Warning

Non dimenticate le parentesi attorno agli argomenti!


Documentazione

Per visualizzare l’aiuto di un tipo/oggetto/variabile, usa la funzione help, ad esempio:

help(123)

visualizza l’aiuto degli interi, cioe’ del tipo dell’oggetto 123. Avrei ottenuto lo stesso risultato con:

x = 123
help(x)

o:

help(int)

L’aiuto si usa come il manuale della shell: posso navigare l’aiuto con le frecce, cercare un termine con /, e chiudere l’aiuto con q.

Posso visualizzare l’aiuto di un metodo di interesse, ad esempio il metodo split delle stringhe, cosi’:

help("una stringa".split)

Esercizi

  1. Dati l’intero 1 ed il razionale 1.0, quali sono i metodi messi a disposizione dai tipi dei due oggetti?
  2. Data la stringa x = 'testo', visualizza l’aiuto del metodo replace delle stringhe.
  3. Data la lista x = [], visualizza l’aiuto del metodo remove delle liste.
  4. Dato il dizionario x = {}, visualizza l’aiuto del metodo items dei dizionari.
  5. Visualizza l’aiuto di dir e di help.
  6. Usando un editor di testo a tua scelta (ad esempio nano o gedit), scrivi un modulo che stampi a schermo, usando print, la stringa 'Hello, world!'. Il modulo deve chiamarsi hello.py. Esegui il modulo dal terminale per verificare che funzioni.

Python: Fondamentali (Soluzioni)

  1. Uso help:

    help(1)
    help(1.0)
    

    In alternativa, uso dir:

    dir(1)
    dir(1.0)
    
  2. Prima assegno l’oggetto stringa 'testo' alla variabile x, poi chiamo help per visualizzare l’aiuto del metodo replace delle stringhe:

    x = 'testo'
    help(x.replace)
    

    Il codice funziona perche’ x contiene un riferimento ad un oggetto stringa, come posso verificare con:

    type(x)
    

    In alternativa, posso usare:

    help('testo'.replace)
    
  3. Come sopra, assegno l’oggetto lista [] a x, poi uso help:

    x = []
    type(x) # per verificare che x si riferisca ad una lista
    help(x.remove)
    
  4. Come sopra:

    x = {}
    type(x) # per verificare che x si riferisca ad un dizionario
    help(x.items)
    
  5. Eseguo:

    help(dir)
    help(help)
    
  6. Eseguo l’esercizio con echo. Da un terminale (non dall’interprete Python!), scrivo:

    echo 'print("Hello, world!")' > hello.py
    

    Cosi’ ho creato un file hello.py che contiene il testo:

    print("Hello, world!")
    

    come posso verificare usando cat:

    cat hello.py
    

    Visto che il testo e’ codice Python, posso eseguire il modulo scrivendo, sempre dalla shell (non dall’interprete Python!):

    python hello.py
    

    Riassumendo, l’intera procedura e’:

    $ echo 'print("Hello, world!")' > hello.py
    $ python hello.py
    Hello, world!
    $
    

    (Qui $ indica che il comando viene eseguito nella shell.)



Python: Numeri

Numeri: Tipi Fondamentali

Ci sono tre tipi numerici fondamentali:

Tipo Significato
int Rappresenta numeri interi (!)
float Rappresenta numeri razionali a virgola mobile (float ing-point)
bool Rappresenta condizioni, puo’ essere True o False

Note

I razionali float hanno precisione limitata: la maggior parte dei razionali puo’ essere rappresentata solo approssimatamente con un float.

Ai fini di questo corso, i dettagli non sono importanti.


Esempio. Creo quattro variabili, una per tipo, poi le stampo a schermo con print:

n = 10
x = 3.14
cond = False

# Stampo le quattro variabili
print(n, x, cond)

# Idem, inframezzando testo
print("n =", n, "x =", x, "e la condizione cond vale", cond)

Questa sintassi di print vale per tutti i tipi di variabili, non solo quelli numerici.


Numeri: Aritmetica

Tutti i tipi numerici mettono a disposizione le stesse operazioni aritmetiche:

Operazione Significato
a + b somma
a - b differenza
a * b prodotto
a / b divisione
a // b divisione intera
a % b resto della divisione (o modulo)
a ** b elevamento a potenza

Il tipo del risultato di n operazione m e’ automaticamente il tipo piu’ “complesso” tra i tipi di n e m – per questo si parla di conversione automatica.

La scala di complessita’ dei tipi numerici e’:

bool < int < float

Esempio. Ad esempio, se sommo un int ed un float, otterro’ un float:

risultato = 1.2 + 1             # float * int
print(risultato)                # 1.2
print(type(risultato))          # float

Questo perche’ e’ necessario un float per rappresentare il valore 1.2: un int non basterebbe!

Warning

Per evitare errori, e’ necessario scegliere il tipo delle variabili in modo che il tipo del risultato sia sufficientemente “complesso” da riuscire a rappresentarne il valore.


Numeri: Confronti

Tutti i tipi numerici (e in generale tutto i tipi Python che vedremo durante il corso) supportano le operazioni di comparazione:

Operazione Significato
a == b uguale
a != b diverso
a < b minore
a <= b minore o uguale
a > b maggiore
a >= b maggiore o uguale

Il risultato di un’espressione di confronto e’ sempre un bool: vale True se la condizione e’ soddisfatta, e False altrimenti.


Esempio. Aritmetica e confronti possono essere combinati per verificare condizioni “complesse”, come questa:

na, nc, ng, nt = 2, 6, 50, 4

risultato = (na + nt) > (nc + ng)
print(risultato)
print(type(risultato))

I valori Booleani bool (es. i risultati delle operazioni di confronto) possono essere combinati attraverso le operazioni logiche:

Operazione Significato
a and b congiunzione: True se e solo se a e b sono True
a or b disgiunzione: True se almeno una tra a e b e’ True
not a negazione: True se a e’ False e viceversa

Qui sia a che b sono dei bool.

Warning

In generale, fare aritmetica (es. somme) con valori Booleani e costruire espressioni logiche con valori interi o razionali e’ sconsigliato.

In questi casi, Python si comporta in modo (deterministico e spiegabile, ma decisamente) bizzarro.


Esempio. x > 12 e x < 34 danno come risultato dei bool, quindi le posso combinare per ottenere:

#     int          int
#      |            |
print((x > 12) and (x < 34))
#     \______/     \______/
#       bool         bool
#     \___________________/
#             bool

oppure:

#          int          int
#           |            |
print((not (x > 12)) or (x < 34))
#          \______/
#            bool
#     \____________/    \______/
#          bool           bool
#     \________________________/
#                bool

Esempi

Esempio. Calcolo gli zeri dell’equazione quadratica x^2 - 1 = 0:

a, b, c = 1.0, 0.0, -1.0

delta = b**2 - 4*a*c

zero1 = (-b + delta**0.5) / (2 * a)
zero2 = (-b - delta**0.5) / (2 * a)

print(zero1, zero2)

Qui uso x**0.5 per calcolare la radice quadrata: \sqrt{x} = x^\frac{1}{2}.


Esempio. Voglio calcolare il GC-content di un gene. So che il gene:

  • E’ lungo 1521 basi.
  • Contiene 316 citosine.
  • Contiene 235 guanine.

Simbolicamente, il GC-content e’ definito come (g + c) / n. Per calcolarlo posso scrivere:

n, c, g = 1521, 316, 235

gc_content = (c + g) / n
print(gc_content)

Esempio. Per controllare che x (il cui valore e’ “fuori dal mio controllo”, ma nell’esempio sotto fisso per convenienza) cada nell’intervallo A = [10,50] scrivo:

x = 17 # ad esempio

minimo_a, massimo_a, x = 10, 50

dentro_a = (minimo_a <= x <= massimo_a)
print(dentro_a)

oppure:

dentro_a = ((x >= minimo_a) and (x <= massimo_a))

Assumendo che dentro_a, dentro_b e dentro_c indichino che x e’ nell’intervallo A, B o C, rispettivamente, posso comporre condizioni piu’ complesse:

# x e' in almeno uno dei tre intervalli
dentro_almeno_uno = dentro_a or dentro_b or dentro_c

# x e' sia in A e B, ma non in C
dentro_a_e_b_ma_non_c = dentro_a and dentro_b and (not dentro_c)

Esercizi

  1. Creare alcune variabili, controllando ad ogni passaggio che valore e tipo siano corretti (usando print e type):

    1. a e b con valore 12 e 23 come interi.
    2. x e y con valore 21 e 14 come razionali.
  2. Usando print (una sola volta), stampare:

    1. Tutte le variabili di cui sopra sulla stessa riga.
    2. Tutte le variabili di cui sopra, separate da ;, sulla stessa riga.
    3. Il testo “il prodotto di a e b e’ a * b”, sostituendo ad a, b e a * b i valori delle variabili.
  3. Determinare valore e tipo di:

    1. Il prodotto di a e b.
    2. Il quoziente di x e y.
    3. Il quoziente intero di a e b.
    4. Il quoziente intero di x e y.
    5. Il prodotto di b e y.
    6. 2 elevato a 0.
    7. 2 elevato a 1.2.
    8. 2 elevato a -2.
    9. La radice quadrata di 4.
    10. La radice quadrata di 2.
  4. Che differenza c’e’ tra:

    1. 10 / 12
    2. 10 / 12.0
    3. 10 // 12
    4. 10 // 12.0
  5. Che differenza c’e’ tra:

    1. 10 % 3
    2. 10 % 3.0
  6. Usando pi = 3.141592 e dato r = 2.5, calcolare:

    1. La circonferenza di raggio r: 2 \pi r.
    2. L’area di un cerchio di raggio r: \pi r^2.
    3. Il volume di una sfera di raggio r: \frac{4}{3} \pi r^3.
  7. Creare due variabili a = 100 e b = True. Usando un numero opportuno di variabili ausiliarie (chiamatele come volete!), fate in modo che il valore di a finisca in b e che quello di b finisca in a.

    (Scrivere a = True e b = 100 non vale!)

    Si puo’ fare con una sola variabile ausiliaria?

  8. Sullo stesso strand di DNA si trovano due geni. Il primo include i nucelotidi dalla posizione 10 alla posizione 20, il secondo quelli dalla posizione 30 alla posizione 40. Scriviamo queste informazioni cosi’:

    gene1_inizio, gene1_fine = 10, 20
    gene2_inizio, gene2_fine = 30, 40
    

    Data una variabile pos che rappresenta una posizione arbitraria sullo strand, scrivere dei confronti per verificare se:

    1. pos si trova nel primo gene.
    2. pos si trova nel secondo gene.
    3. pos si trova tra l’inizio del primo gene e la fine del secondo.
    4. pos si trova tra l’inizio del primo gene e la fine del secondo, ma in nessuno dei due geni.
    5. pos si trova prima dell’inizio del primo gene o dopo la fine del secondo.
    6. pos cade in uno dei due geni.
    7. pos non dista piu’ di 10 dall’inizio del primo gene.
  9. Date le tre variabili Booleane t, u, e v, scrivere delle espressioni che valgono True se e solo se:

    1. t, u, v tutte e tre vere.
    2. t e’ vera oppure u e’ vera, ma non entrambe.
    3. Esattamente una delle tre variabili e’ falsa.
    4. Esattamente una delle tre variabili e’ vera.

Python: Numeri (Soluzioni)

  1. Soluzioni:

    a = 12
    b = 23
    print(a, b)
    print(type(a), type(b))          # int, int
    
    x = 21.0
    y = 14.
    print(x, y)
    print(type(x), type(y))          # float, float
    
  2. Soluzioni:

    print(a, b, x, y)
    
    print(a, ";", b, ";", x, ";", ...)
    
  3. Soluzioni:

    # casi semplici:
    
    prodotto = a * b                # int * int
    print(prodotto)
    print(type(prodotto))           # int
    
    # divisione e divisione intera tra vari
    # tipi di numeri:
    
    quoziente = x / y               # float / float
    print(quoziente)
    print(type(quoziente))          # float
    
    risultato = a // b              # int // int
    print(risultato)
    print(type(risultato))          # int
    
    risultato = x // y              # float // float
    print(risultato)
    print(type(risultato))          # float
    
    risultato = b * y               # int * float
    print(risultato)
    print(type(risultato))          # float
    
    # qui il tipo e' determinato automaticamente
    # in base alla magnitudine del risultato:
    
    risultato = 2**0                # int**int
    print(risultato)
    print(type(risultato))          # int
    
    risultato = 2**1.2              # int*float
    print(risultato)
    print(type(risultato))          # float
    
    risultato = 2**-2               # int*int
    print(risultato)
    print(type(risultato))          # *** float!!! ***
    
    risultato = 4**0.5              # int*float
    print(risultato)
    print(type(risultato))          # float
    
    risultato = 2**0.5              # int*float
    print(risultato)
    print(type(risultato))          # float
    
  4. Soluzioni:

    >>> print(10 / 12)
    0.8333333333333334
    >>> print(10 / 12.0)
    0.8333333333333334
    >>> print(10 // 12)
    0
    >>> print(10 // 12.0)
    0.0
    

    Come si vede la divisione intera si comporta normalmente rispetto ai tipi: quando la applico ai due float il risultato e’ quello della divisione normale, ma troncato all’intero 0.

  5. Soluzioni:

    >>> 10 % 3
    1
    >>> 10 % 3.0
    1.0
    

    Come si puo’ vedere, % ritorna il resto di 10 / 3:

    10 = 3*3 + 1
    #          ^
    #       il resto
    

    Il tipo degli operandi non influenza il valore del risultato, solo il suo tipo.

  6. Soluzione:

    pi = 3.141592
    r = 2.5
    
    circonferenza = 2 * pi * r
    print(circonferenza)
    
    area = 2 * pi * r**2
    print(area)
    
    area = 2 * pi * r * r
    print(area)
    
    volume = (4.0 / 3.0) * pi * r**3
    print(volume)
    
  7. Soluzione:

    a, b = 100, True
    
    a2 = a
    b2 = b
    b = a2
    a = b2
    
    print(a, b)
    

    oppure:

    a, b = 100, True
    
    x = a
    a = b
    b = x
    
    print(a, b)
    
  8. Soluzione:

    gene1_inizio, gene1_fine = 10, 20
    gene2_inizio, gene2_fine = 30, 40
    
    # disegnino:
    #
    # 5'                            3'
    # ~~~~~xxxxxxxx~~~~~xxxxxxx~~~~~>
    #     10      20   30     40
    #      \______/     \_____/
    #       gene_1       gene_2
    
    
    # due alternative
    condizione_1 = (10 <= pos <= 20)
    condizione_1 = (pos >= 10 and pos <= 20)
    
    
    condizione_2 = (30 <= pos <= 40)
    
    
    condizione_3 = (10 <= pos <= 40)
    
    
    # due alternative
    condizione_4 = condizione_3 and not (condizione_1 or condizione_2)
    condizione_4 = (20 <= pos <= 40)
    
    
    condizione_5 = pos < 10 or pos > 40
    # occhio che:
    #
    #   pos < 10 and pos > 40
    #
    # non ha senso: e' sempre False!
    
    
    condizione_6 = condizione_1 or condizione_2
    
    
    condizione_7 = (0 <= pos <= 20)
    

    Il codice va testato con diversi valori di posizione, in modo da controllare che le condizioni si comportino come vogliamo: che siano True quando la posizione soddisfa i requisiti della domanda, e False altrimenti.

  9. Soluzione:

    tutte_e_tre = t and u and v
    
    t_oppure_u_ma_non_tutte_e_due = (t or u) and not (t and u)
    
    # NOTA: qui i backslash alla fine delle righe servono
    # per andare "a capo", potete ignorarli.
    una_delle_tre_falsa = \
        (t and u and not v) or \
        (t and not u and v) or \
        (not t and u and v)
    
    una_delle_tre_vera = \
        (t and not u and not v) or \
        (not t and u and not v) or \
        (not t and not u and v)
    

    Di nuovo, il codice va testato usando diversi valori per t, u e v. Ci sono 8 combinazioni in tutto:

    t, u, v = False, False, False
    t, u, v = False, False, True
    t, u, v = False, True, False
    t, u, v = False, True, True
    # ...
    


Python: Stringhe

Le stringhe sono oggetti immutabili che rappresentano testo.

Per definire una stringa, ho due alternative equivalenti:

var = "testo"
var = 'testo'

Per inserire caratteri speciali, devo fare l’escaping con un backslash \:

percorso = "data\\fasta"

oppure usare il prefisso r (raw, grezzo):

percorso = r"data\fasta"

Per creare una stringa multilinea posso inserire manualmente i carattere di a capo \n in ogni riga:

sad_joke = "Time flies like an arrow.\nFruit flies like a banana."

print(sad_joke)

oppure usare le triple virgolette:

sad_joke = """Time flies like an arrow.
Fruit flies like a banana."""

print(sad_joke)

Warning

print interpreta i caratteri speciali, mentre l’eco del terminale no. Provate a scrivere:

print(percorso)

e (dall’interprete):

percorso

Nel primo caso lo slash appare una volta (quello di escaping viene interpretato automaticamente da print) mentre nel secondo due (quello di escaping non viene interpretato affatto).

Lo stesso vale se stampo sad_joke.


Conversioni Stringa-Numero

Posso convertire un numero in una stringa usando str():

n = 10
print(n, type(n))

s = str(n)
print(s, type(s))

int() o float() fanno l’esatto opposto:

n = int("123")
print(n, type(n))

q = float("1.23")
print(q, type(q))

Warning

Se la stringa non descrive un numero del tipo giusto, Python da’ errore:

int("3.14")             # Non e' un int
float("giardinaggio")   # Non e' un numero
int("1 2 3")            # Non e' un numero
int("fifteen")          # Non e' un numero

Operazioni

Ritorna Operatore Significato
int len(str) Restituisce la lunghezza della stringa
str str + str Concatena le due stringhe
str str * int Replica la stringa
bool str in str Controlla se una stringa appare in un’altra
str str[int:int] Estrae una sotto-stringa

Esempio. Concateno due stringhe:

stringa = "una" + " " + "stringa"
lunghezza = len(stringa)
print("la stringa:", stringa, "e' lunga", lunghezza)

Un altro esempio:

stringa = "basta Python!" * 1000
print("la stringa e' lunga", len(stringa), "caratteri")

Warning

Non posso concatenare stringhe con altri tipi. Ad esempio:

var = 123
print("il valore di var e'" + var)

da’ errore. Due alternative funzionanti:

print("il valore di var e'" + str(var))

oppure:

print("il valore di var e'", var)

(Nel secondo caso manca uno spazio tra e' e 123.)


Esempio. L’operatore sottostringa in stringa controlla se sottostringa appare una o piu’ volte in stringa, ad esempio:

stringa = "A beautiful journey"

print("A" in stringa)            # True
print("beautiful" in stringa)    # True
print("BEAUTIFUL" in stringa)    # False
print("ul jour" in stringa)      # True
print("Gengis Khan" in stringa)  # False
print(" " in stringa)            # True
print("     " in stringa)        # False

Il risultato e’ sempre True o False.


Esempio. Per estrarre una sottostringa si usa l’indicizzazione:

#           0                       -1
#           |1                     -2|
#           ||2                   -3||
#           |||        ...         |||
alfabeto = "abcdefghijklmnopqrstuvwxyz"

print(alfabeto[0])               # "a"
print(alfabeto[1])               # "b"
print(alfabeto[len(alfabeto)-1]) # "z"
print(alfabeto[len(alfabeto)])   # Errore
print(alfabeto[10000])           # Errore

print(alfabeto[-1])              # "z"
print(alfabeto[-2])              # "y"

print(alfabeto[0:1])             # "a"
print(alfabeto[0:2])             # "ab"
print(alfabeto[0:5])             # "abcde"
print(alfabeto[:5])              # "abcde"

print(alfabeto[-5:-1])           # "vwxy"
print(alfabeto[-5:])             # "vwxyz"

print(alfabeto[10:-10])          # "klmnop"

Warning

L’estrazione e’ inclusiva rispetto al primo indice, ma esclusiva rispetto al secondo. In altre parole alfabeto[i:j] equivale a:

alfabeto[i] + alfabeto[i+1] + ... + alfabeto[j-1]

Notate che alfabeto[j] e’ escluso.

Warning

Occhio che l’estrazione restituisce una nuova stringa, lasciando l’originale invariata:

alfabeto = "abcdefghijklmnopqrstuvwxyz"

sottostringa = alfabeto[2:-2]
print(sottostringa)
print(alfabeto)                  # Resta invariato

Metodi

Ritorna Metodo Significato
str str.upper() Restituisce la stringa in maiuscolo
str str.lower() Restituisce la stringa in minuscolo
str str.strip(str) Rimuove stringhe ai lati
str str.lstrip(str) Rimuove stringhe a sinistra
str str.rstrip(str) Rimuove stringhe a destra
bool str.startswith(str) Controlla se la stringa comincia per un’altra
bool str.endswith(str) Controlla se la stringa finisce per un’altra
int str.find(str) Restituisce la posizione di una sotto-stringa
int str.count(str) Conta il numero di ripetizioni di una sotto-stringa
str str.replace(str, str) Rimpiazza sotto-stringhe

Warning

Proprio come l’estrazione, i metodi restituiscono una nuova stringa, lasciando l’originale invariata:

alfabeto = "abcdefghijklmnopqrstuvwxyz"

alfabeto_maiuscolo = alfabeto.upper()
print(alfabeto_maiuscolo)
print(alfabeto)                  # Resta invariato

Esempio. upper() e lower() sono molto semplici:

testo = "no yelling"

risultato = testo.upper()
print(risultato)

risultato = risultato.lower()
print(risultato)

Esempio. Le varianti di strip() lo sono altrattanto:

testo = "    un esempio    "

print(testo.strip())         # equivale a testo.strip(" ")
print(testo.lstrip())        # equivale a testo.lstrip(" ")
print(testo.rstrip())        # equivale a testo.rstrip(" ")

print(testo)                 # testo e' invariato

Notate che lo spazio tra "un" ed "esempio" non viene mai rimosso. Posso passare piu’ di un carattere da rimuovere:

"AAAA un esempio BBBB".strip("AB")

Esempio. Lo stesso vale per startswith() e endswith():

testo = "123456789"

print(testo.startswith("1"))     # True
print(testo.startswith("a"))     # False

print(testo.endswith("56789"))   # True
print(testo.endswith("5ABC9"))   # False

Esempio. find() restituisce la posizione della prima occorrenza di una sottostringa, oppure -1 se la sottostringa non appare mai:

testo = "123456789"

print(testo.find("1"))           # 0
print(testo.find("56789"))       # 6

print(testo.find("Q"))           # -1

Esempio. replace() restituisce una copia della stringa dove una sottostringa viene rimpiazzata con un’altra:

testo = "se le rose sono rosse allora"

print(testo.replace("ro", "gro"))

Esempio. Data la stringa “sporca” di aminoacidi:

sequenza = ">MAnlFKLgaENIFLGrKW    "

voglio sbarazzarmi del carattere ">", degli spazi, e poi convertire il tutto in maiuscolo per uniformita’:

s1 = sequenza.lstrip(">")
s2 = s2.rstrip(" ")
s3 = s2.upper()

print(s3)

In alternativa, tutto in un passaggio:

print(sequenza.lstrip(">").rstrip(" ").upper())

Perche’ funziona? Riscriviamolo con le parentesi:

print(( ( sequenza.lstrip(">") ).rstrip(" ") ).upper())
        \_____________________/
                  str
      \_____________________________________/
                         str
      \_____________________________________________/
                             str

Come vedere, il risultato di ciascuno metodo e’ una stringa, proprio come sopra lo erano s1, s2 e s3; e su queste posso invocare i metodi delle stringhe.


Esercizi

  1. Come faccio a:

    1. Creare una stringa che abbia, come testo, cinque spazi.

    2. Controllare che una stringa contenga almeno uno spazio.

    3. Controllare che una stringa contenga cinque caratteri.

    4. Creare una stringa vuota, e controllare che sia vuota.

    5. Creare una stringa che contenga cento ripetizioni di Python e' bello tra la la.

    6. Date le stringhe "ma biologia", "molecolare" e "e' meglio", creare una stringa composta "ma biologia molecolare e' meglio" e poi replicarla mille volte.

    7. Controllare se la stringa "12345" comincia con il carattere 1.

    8. Creare una stringa che contenga il solo carattere \. Controllate con print, l’eco dell’interprete, e len()!

    9. Controllare se la stringa "\\" contiene due backslash \ consecutivi.

    10. Controllare che una stringa inizi o finisca per \. Ci sono almeno due modi.

    11. Controllare che contenga il carattere x appaia almeno tre volte all’inizio o alla fine di una stringa. Ad esempio, questo e’ vero per:

      "x....xx"           # 1 + 2 >= 3
      "xx....x"           # 2 + 1 >= 3
      "xxxx..."           # 4 + 0 >= 3
      

      Ma non per:

      "x.....x"           # 1 + 1 < 3
      "...x..."           # 0 + 0 < 3
      "......."           # 0 + 0 < 3
      
  2. Data la stringa:

    s = "0123456789"
    

    Quali delle seguenti estrazioni sono corrette?

    1. s[9]
    2. s[10]
    3. s[:10]
    4. s[1000]
    5. s[0]
    6. s[-1]
    7. s[1:5]
    8. s[-1:-5]
    9. s[-5:-1]
    10. s[-1000]
  3. Creare una stringa che contenga letteralmente le seguenti due righe di testo, inclusi apici e virgolette:

    urlo’: “non farti vedere mai piu’!”

    “d’accordo”, rispose il bassotto.

    Ci sono almeno due modi per farlo.

  4. Calcolare il valore di \frac{1}{7} in Python, ottenendo un float; mettere il risultato ottenuto nella variabile valore. Controllare se:

    1. Vi appare la cifra 9.
    2. I primi sei decimali sono uguali ai secondi sei?

    Hint: si puo’ risolvere facilmente l’esercizio convertendo valore da float a str.

  5. Date le stringhe:

    stringa = "a 1 b 2 c 3"
    
    digit = "DIGIT"
    character = "CHARACTER"
    

    rimpiazzare tutte le cifre con il testo della variabile digit, e tutti i caratteri alfabetici con quello di character.

    Opzionalmente, fare tutto in una sola riga di codice.

  6. Data la sequenza primaria della catena A della Tumor Suppressor Protein TP53, riportata qui sotto:

    chain_a = """SSSVPSQKTYQGSYGFRLGFLHSGTAKSVTCTYSPALNKM
    FCQLAKTCPVQLWVDSTPPPGTRVRAMAIYKQSQHMTEVV
    RRCPHHERCSDSDGLAPPQHLIRVEGNLRVEYLDDRNTFR
    HSVVVPYEPPEVGSDCTTIHYNYMCNSSCMGGMNRRPILT
    IITLEDSSGNLLGRNSFEVRVCACPGRDRRTEEENLRKKG
    EPHHELPPGSTKRALPNNT"""
    
    1. Di quante righe e’ composta la sequenza? (Hint: e’ sufficiente contare quanti caratteri di a capo ci sono, e poi …)
    2. Quanto e’ lunga la sequenza? (Non l’intera stringa: tenete conto dell’esercizio precedente.)
    3. Rimuovere i caratteri di a capo e mettere il risultato in una nuova variabile sequenza. Controllare se le risposte ai punti precedenti sono corrette.
    4. Quante cisteine "C" ci sono nella sequenza? Quante istidine "H"?
    5. La catena contiene la sotto-sequenza "NLRVEYLDDRN"? In che posizione?
    6. Come posso usare find() e l’estrazione [i:j] per estrarre la prima riga della stringa chain_a?
  7. Data (una piccola parte) della sequenza terziaria della catena A di TP53:

    structure_chain_a = """SER A 96 77.253 20.522 75.007
    VAL A 97 76.066 22.304 71.921
    PRO A 98 77.731 23.371 68.681
    SER A 99 80.136 26.246 68.973
    GLN A 100 79.039 29.534 67.364
    LYS A 101 81.787 32.022 68.157"""
    

    Ogni riga rappresenta un atomo C_\alpha del backbone della struttura. Di quell’atomo sono riportati, in ordine: il codice del residuo cui appartiene, la catena a cui appartiene (sempre "A" nel nostro caso), la posizione del residuo nella sequenza primaria, e le coordinate x,y,z del residuo nello spazio tridimensionale.

    1. Estrarre la seconda riga usando find() e l’estrazione [i:j], e metterla in una nuova variabile riga.

    2. Estrarre le coordinate del residuo, e metterle in tre variabili x, y, e z.

    3. Ripetere il tutto per la terza riga, e mettere le coordinate in x_prime, y_prime, z_prime.

    4. Calcolare la distanza Euclidea tra i due residui:

      d((x,y,z),(x',y',z')) = \sqrt{(x-x')^2 + (y-y')^2 + (z-z')^2}

      Hint: per calcolare la distanza e’ necessario usare dei float.

  8. Il comando:

    dna = open("data/dna-fasta/fasta.1").readlines()[2]
    print(dna)
    

    legge le sequenze di nucleotidi contenute nel file data/dna-fasta/fasta.1 (a patto che python sia stato lanciato nella directory giusta) e restituisce una stringa, che noi mettiamo nella variabile dna.

    1. La stringa in dna e’ vuota? Quanto e’ lunga? Contiene dei caratteri di a capo? (In caso affermativo, rimuoverli.)
    2. I primi tre caratteri sono identici agli ultimi tre?
    3. I primi tre caratteri sono palindromi rispetto agli ultimi tre?
    4. Sostituire A con Ade, C con Cyt, etc. facendo in modo che i singoli residui siano separati da spazi " ". Mettere il risultato in una nuova variabile dna_espanso.

Python: Stringhe (Soluzioni)

Note

In alcune soluzioni uso il carattere \ alla fine di una riga di codice.

Usato in questo modo, \ spiega a Python che il comando continua alla riga successiva. Se non usassi \, Python potrebbe pensare che il comando finisca li’ e quindi che sia sintatticamente sbagliato – dando errore.

Potete tranquillamente ignorare questi \.

  1. Soluzioni:

    1. Soluzione:

      #        12345
      testo = "     "
      print(testo)
      print(len(testo))
      
    2. Soluzione:

      almeno_uno_spazio = " " in testo
      
      # controllo che funzioni
      print(" " in "nonc'e'alcunospazio")
      print(" " in "c'e'unsolospazioqui--> <--")
      print(" " in "ci sono parecchi spazi")
      
    3. Soluzione:

      esattamente_cinque_caratteri = len(testo) == 5
      
      # controllo che funzioni
      print(len("1234") == 5)
      print(len("12345") == 5)
      print(len("123456") == 5)
      
    4. Soluzione:

      stringa_vuota = ""
      print(len(stringa_vuota) == 0)
      
    5. Soluzione:

      base = "Python e' bello tra la la"
      ripetizioni = base * 100
      
      # mi assicuro che almeno la lunghezza sia giusta
      print(len(ripetizioni) == len(base) * 100)
      
    6. Soluzione:

      parte_1 = "ma biologia"
      parte_2 = "molecolare"
      parte_3 = "e' meglio"
      
      testo = (parte_1 + " " + parte_2 + " " + parte_3) * 1000
      
    7. Provo cosi’:

      comincia_con_1 = "12345".startswith(1)
      

      ma Python mi da’ errore:

      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      TypeError: startswith first arg must be str or a tuple of str, not int
      #                     ^^^^^^^^^^^^^^^^^^^^^                    ^^^^^^^
      

      L’errore ci dice (vedi parte evidenziata) che startswith() richiede che l’argomento sia una stringa, non un intero come nel nostro caso: noi invece le abbiamo passato 1, che e’ un intero.

      La soluzione quindi e’:

      comincia_con_1 = "12345".startswith("1")
      print(comincia_con_1)
      

      che vale True, come mi aspettavo.

    8. Soluzione:

      stringa = "\\"
      stringa
      print(stringa)
      print(len(stringa))                  # 1
      
    9. Gia’ controllato nell’esercizio sopra, la risposta e’ no. Verifichiamo comunque:

      backslash = "\\"
      
      print(backslash*2 in "\\")           # False
      
    10. Primo metodo:

      backslash = "\\"
      
      condizione = testo.startswith(backslash) or \
                   testo.endswith(backslash)
      

      Secondo metodo:

      condizione = (testo[0] == backslash) or \
                   (testo[-1] == backslash)
      
    11. Soluzione:

      condizione = \
           testo.startswith("xxx") or \
          (testo.startswith("xx") and testo.endswith("x")) or \
          (testo.startswith("x")  and testo.endswith("xx")) or \
                                      testo.endswith("xxx")
      

      Vale la pena di controllare usando gli esempi nell’esercizio.

  2. Soluzione:

    s = "0123456789"
    print(len(s))                        # 10
    

    Quali delle seguenti estrazioni sono corrette?

    1. s[9]: corretta, estrae l’ultimo carattere.
    2. s[10]: invalida.
    3. s[:10]: corretta, estrae tutti i caratteri (ricordate che in secondo indice, 10 in questo caso, e’ esclusivo.)
    4. s[1000]: invalida.
    5. s[0]: corretta, estrae il primo carattere.
    6. s[-1]: corretta, estrae l’ultimo carattere.
    7. s[1:5]: corretta, estrae dal secondo al sesto carattere.
    8. s[-1:-5]: corretta, ma non estrae niente (gli indici sono invertiti!)
    9. s[-5:-1]: corretta, estrae dal sesto al penultimo carattere.
    10. s[-1000]: invalida.
  3. Soluzione (una di due):

    testo = """urlo': \"non farti vedere mai piu'!\"
    \"d'accordo\", rispose il bassotto."""
    
  4. Soluzione:

    valore = 1.0 / 7.0
    print(valore)                        # 0.14285714285714285
    
    valore_come_stringa = str(valore)
    print(valore_come_stringa)           # "0.14285714285714285"
    
    print("9" in valore_come_stringa)    # False
    
    indice_punto = valore_come_stringa.find(".")
    primi_sei_decimali = valore_come_stringa[indice_punto + 1 : indice_punto + 1 + 6]
    secondi_sei_decimali = valore_come_stringa[indice_punt + 1 + 6 : indice_punti + 1 + 6 + 6]
    print(primi_sei_decimali == secondi_sei_decimali)    # True
    
  5. Soluzione:

    stringa = "a 1 b 2 c 3"
    
    digit = "DIGIT"
    character = "CHARACTER"
    
    risultato = stringa.replace("1", digit)
    risultato = risultato.replace("2", digit)
    risultato = risultato.replace("3", digit)
    risultato = risultato.replace("a", character)
    risultato = risultato.replace("b", character)
    risultato = risultato.replace("c", character)
    
    print(risultato)                     # "CHARACTER DIGIT CHARACTER ..."
    

    In una sola riga:

    print(stringa.replace("1", digit).replace("2", digit) ...)
    
  6. Soluzione:

    chain_a = """SSSVPSQKTYQGSYGFRLGFLHSGTAKSVTCTYSPALNKM
    FCQLAKTCPVQLWVDSTPPPGTRVRAMAIYKQSQHMTEVV
    RRCPHHERCSDSDGLAPPQHLIRVEGNLRVEYLDDRNTFR
    HSVVVPYEPPEVGSDCTTIHYNYMCNSSCMGGMNRRPILT
    IITLEDSSGNLLGRNSFEVRVCACPGRDRRTEEENLRKKG
    EPHHELPPGSTKRALPNNT"""
    
    
    numero_righe = chain_a.count("\n") + 1
    print(numero_righe)                          # 6
    
    
    # NOTA: qui voglio la lunghezza della *sequenza*, non della *stringa*
    lunghezza_sequenza = len(chain_a) - chain_a.count("\n")
    print(lunghezza_sequenza)                    # 219
    
    
    sequenza = chain_a.replace("\n", "")
    print(len(chain_a) - len(sequenza))          # 5 (giusto)
    print(len(sequenza))                         # 219
    
    
    num_cisteine = sequenza.count("C")
    num_istidine = sequenza.count("H")
    print(num_cisteine, num_istidine)            # 10, 9
    
    
    print("NLRVEYLDDRN" in sequenza)             # True
    print(sequenza.find("NLRVEYLDDRN"))          # 106
    # controllo
    print(sequenza[106 : 106 + len("NLRVEYLDDRN")])  # "NLRVEYLDDRN"
    
    
    indice_primo_acapo = chain_a.find("\n")
    prima_riga = chain_a[:indice_primo_acapo]
    print(prima_riga)
    
  7. Soluzione:

    structure_chain_a = """SER A 96 77.253 20.522 75.007
    VAL A 97 76.066 22.304 71.921
    PRO A 98 77.731 23.371 68.681
    SER A 99 80.136 26.246 68.973
    GLN A 100 79.039 29.534 67.364
    LYS A 101 81.787 32.022 68.157"""
    
    # uso una variabile di nome piu' corto per comodita'
    chain = structure_chain_a
    
    
    indice_primo_a_capo = chain.find("\n")
    indice_secondo_a_capo = chain[indice_primo_a_capo + 1:].find("\n") + len(chain[:indice_primo_a_capo + 1])
    indice_terzo_a_capo = chain[indice_secondo_a_capo + 1:].find("\n") + len(chain[:indice_secondo_a_capo + 1])
    print(indice_primo_a_capo, indice_secondo_a_capo, indice_terzo_a_capo)
    
    seconda_riga = chain[indice_primo_a_capo + 1 : indice_secondo_a_capo]
    print(seconda_riga)                      # "VAL A 97 76.066 22.304 71.921"
                                             #           |    | |    | |    |
                                             #  01234567890123456789012345678
                                             #  0         1         2
    
    x = seconda_riga[9:15]
    y = seconda_riga[16:22]
    z = seconda_riga[23:]
    print(x, y, z)
    # NOTA: sono tutte stringhe
    
    
    terza_riga = chain[indice_secondo_a_capo + 1 : indice_terzo_a_capo]
    print(terza_riga)                        # "PRO A 98 77.731 23.371 68.681"
                                             #           |    | |    | |    |
                                             #  01234567890123456789012345678
                                             #  0         1         2
    
    x_prime = terza_riga[9:15]
    y_prime = terza_riga[16:22]
    z_prime = terza_riga[23:]
    print(x_prime, y_prime, z_prime)
    # NOTA: sono tutte stringhe
    
    
    # converto tutte le variabili a float, altrimenti non posso calcolare i
    # quadrati e la radice quadrata (ne' tantomeno le differenze)
    x, y, z = float(x), float(y), float(z)
    x_prime, y_prime, z_prime = float(x_prime), float(y_prime), float(z_prime)
    
    diff_x = x - x_prime
    diff_y = y - y_prime
    diff_z = z - z_prime
    
    distanza = (diff_x**2 + diff_y*82 + diff_z**2)**0.5
    print(distanza)
    

    La soluzione si semplifica moltissimo potendo usare split():

    righe = chain.split("\n")
    seconda_riga = righe[1]
    terza_riga = righe[2]
    
    parole = seconda_riga.split()
    x, y, z = float(parole[-3]), float(parole[-2]), float(parole[-1])
    
    parole = terza_riga.split()
    x_prime, y_prime, z_prime = float(parole[-3]), float(parole[-2]), float(parole[-1])
    
    distanza = ((x - x_prime)**2 + (y - y_prime)**2 + (z - z_prime)**2)**0.5
    
  8. Soluzione:

    dna = open("data/dna-fasta/fasta.1").readlines()[2]
    
    print(len(dna) > 0)                      # False
    print(len(dna))                          # 61
    print("\n" in dna)                       # True
    # rimuovo gli 'a capo'
    dna = dna.strip()
    
    
    print(dna[:3])                           # "CAT"
    print(dna[-3:])                          # "CTT"
    print(dna[:3] == dna[-3:])               # False
    
    
    print((dna[0] == dna[-1] and \
           dna[1] == dna[-2] and \
           dna[2] == dna[-3]))               # False
    
    
    risultato = dna.replace("A", "Ade ")
    risultato = risultato.replace("C", "Cyt ")
    risultato = risultato.replace("G", "Gua ")
    risultato = risultato.replace("T", "Tym ")
    print(risultato)                         # "Cyt Ade Tym ..."
    


Python: Liste

Le liste rappresentano sequenze ordinate di oggetti arbitrari.

Warning

Le liste sono mutabili!

Per definire una lista uso le parentesi quadre:

# Una lista di interi (anche ripetuti)
alcuni_interi = [1, 2, 1, 1, 9]

# Una lista di stringhe
proteine_uniprot = ["Y08501", "Q95747"]

# Una lista mista
cose = ["Y08501", 0.13, "Q95747", 0.96]

# Una lista di liste
lista_di_liste = [
    ["Y08501", 120, 520],
    ["Q95747", 550, 920],
]

# La lista vuota
una_lista_vuota = []

Warning

Le liste possono contenere elementi ripetuti:

[3, 3, 3, "a", "a", "a"] != [3, "a"]

e l’ordine degli elementi conta:

[1, 2, 3] != [3, 2, 1]

Operazioni

Ritorna Operatore Significato
range range(int, [int]) Restituisce un intervallo di interi
int len(list) Restituisce la lunghezza della lista
list list + list Concatena le due liste
list list * int Replica la lista
bool object in list Contolla se un oggetto arbitrario appare nella lista
list list[int:int] Estrae una sotto-lista
list list[int] = object Sostituisce un elemento della lista

Esempio. Uso range() per costruire una lista di interi:

>>> intervallo = range(0, 5)
>>> lista = list(intervallo)
>>> print(lista)
[0, 1, 2, 3, 4]

range(5) fa la stessa cosa.


Esempio. La sostituzione di un elemento funziona solo se l’indice corrisponde ad un elemento gia’ esistente:

lista = [0, 1, 2, 3, 4]

lista[0] = "primo"
print(lista)                         # ["primo", 1, 2, 3, 4]

lista[-1] = "ultimo"
print(lista)                         # ["primo", 1, 2, 3, "ultimo"]

lista[100] = "oltre l'ultimo"        # Errore!

Esercizi

  1. Creare una lista vuota. Controllare che sia vuota con len().

  2. Creare una lista con i primi cinque interi non-negativi: 0, 1, etc. usando range().

  3. Creare una lista con cento elementi 0.

    Hint: replicate una lista con un solo elemento.

  4. Date:

    lista_1 = list(range(10))
    lista_2 = list(range(10, 20))
    

    concatenare le due liste e mettere il risultato in una nuova lista lista_completa. Quanto vale? E’ uguale al risultato di list(range(20))?

  5. Creare una lista con tre stringhe: "sono", "una", "lista". Poi stampare a schermo tipo e lunghezza dei tre elementi, uno per uno.

  6. Data:

    lista = [0.0, "b", [3], [4, 5]]
    
    1. Quanto e’ lunga lista?

    2. Di che tipo e’ il primo elemento di lista?

    3. Quanto e’ lungo il secondo elemento di lista?

    4. Quanto e’ lungo il terzo elemento di lista?

    5. Quanto vale l’ultimo elemento di lista? Quanto e’ lungo?

    6. La lista ha un elemento di valore "b"?

    7. La lista ha un elemento di valore 4?

      Hint: usate in per controllare.

  7. Che differenza c’e’ tra le seguenti “liste”?:

    lista_1 = [1, 2, 3]
    lista_2 = ["1", "2", "3"]
    lista_3 = "[1, 2, 3]"
    

    Hint: la terza e’ una lista?

  8. Quali dei seguenti frammenti sono validi/errati?

    (Dopo ogni punto, cancellate la lista lista con del, per evitare problemi con i punti successivi)

    1. lista = []
    2. lista = [}
    3. lista = [[]]
    4. lista.append(0)
    5. lista = []; lista.append(0)
    6. lista = [1 2 3]
    7. lista = list(range(3)), elemento = lista[3]
    8. lista = list(range(3)), elemento = lista[-1]
    9. lista = list(range(3)), sottolista = lista[0:2]
    10. lista = list(range(3)), sottolista = lista[0:3]
    11. lista = list(range(3)), sottolista = lista[0:-1]
    12. lista = list(range(3)), lista[2] = "due"
    13. lista = list(range(3)), lista[3] = "tre"
    14. lista = list(range(3)), lista[-1] = "tre"
    15. lista = list(range(3)), lista[1.2] = "uno virgola due"
    16. lista = list(range(3)), lista[1] = ["testo-1", "testo-2"]
  9. Data la lista:

    matrice = [
        [1, 2, 3],          # <-- prima riga
        [4, 5, 6],          # <-- seconda riga
        [7, 8, 9],          # <-- terza riga
    ]
    #    ^  ^  ^
    #    |  |  |
    #    |  |  +-- terza colonna
    #    |  |
    #    |  +----- seconda colonna
    #    |
    #    +-------- prima colonna
    

    Come faccio a:

    1. Estrarre la prima riga?
    2. Estrarre il secondo elemento della prima riga?
    3. Sommare gli elementi della prima riga?
    4. Creare una nuova lista con gli elementi della la seconda colonna?
    5. Creare una nuova lista con gli elementi la diagonale maggiore?
    6. Creare una lista concatenando la prima, seconda, e terza riga?

Metodi

Ritorna Metodo Significato
None list.append(object) Aggiunge un elemento alla fine della lista
None list.extend(list) Estende una lista con un’altra lista
None list.insert(int,object) Inserisce un elemento in una posizione arbitraria
None list.remove(object) Rimuove la prima ripetizione di un valore
None list.reverse() Inverte la lista
None list.sort() Ordina la lista
int list.count(object) Conta il numero di ripetizioni di un valore

Warning

Tutti i metodi delle liste (escluso count()):

  • Modificano la lista stessa.
  • Restituiscono None.

Questo comportamento e’ l’esatto opposto di cio’ che accade con i metodi delle stringhe!

Una conseguenza e’ che fare qualcosa come:

print(lista.append(10))

non ha senso, perche’ print stampa il risultato di append(), che e’ sempre None!

Per lo stesso motivo non possiamo fare:

lista.append(1).append(2).append(3)

perche’ il primo append() restituisce None – che non e’ una lista, e non possiamo farci append()!


Esempio. append() aggiunge in coda:

lista = list(range(10))
print(lista)                         # [0, 1, 2, ..., 9]

lista.append(10)
print(lista)                         # [0, 1, 2, ..., 9, 10]

Notate come lista sia stata modificata! Se invece faccio:

lista = list(range(10))
risultato = lista.append(10)

print(risultato)                     # None

Come ci aspettavamo, append() restituisce None.

Lo stesso vale per extend():

lista = list(range(10))
risultato = lista.extend(list(range(10, 20)))

print(lista)                         # [0, 1, 2, ..., 19]
print(risultato)                     # None

Per inserire elementi in una posizione arbitraria, uso insert():

lista = list(range(10))
risultato = lista.insert(2, "un altro valore")

print(lista)                         # [0, 1, "un altro valore", 3, ...]
print(risultato)                     # None

remove() invece prende un valore, non una posizione:

lista = ["una", "lista", "non", "una", "stringa"]
risultato = lista.remove("una")

print(lista)                         # ["lista", "non", "una", "stringa"]
print(risultato)                     # None

Anche sort() e reverse() modificano la lista stessa:

lista = [3, 2, 1, 5, 4]

risultato = lista.reverse()
print(lista)                         # [4, 5, 1, 2, 3]
print(risultato)                     # None

risultato = lista.sort()
print(lista)                         # [1, 2, 3, 4, 5]
print(risultato)                     # None

Invece count() non modifica affatto la lista, e restituisce un int:

lista = ["a", "b", "a", "b", "a"]
risultato_a = lista.count("a")      # 3
risultato_b = lista.count("b")      # 2
print("ci sono", risultato_a, "a, e", risultato_b, "b")

Esempio. Contrariamente ad append() e soci, la concatenazione non modifica la lista originale:

lista_1 = list(range(0, 10))
lista_2 = list(range(10, 20))

# usando la concatenazione +
lista_completa = lista_1 + lista_2
print(lista_1, "+", lista_2, "->", lista_completa)

# usando extend()
lista_completa = lista_1.extend(lista_2)
print(lista_1, "estesa con", lista_2, "->", lista_completa)

Nel primo caso tutto funziona come vorrei; nel secondo lista_1 e’ estesa con lista_2 (che resta invariata), mentre lista_completa vale None.


Warning

Le liste sono mutabili, e contengono riferimenti ad oggetti.

Questi due fatti possono dare luogo ad effetti complicati – che esploriamo nei prossimi esercizi.

Esempio. Questo codice:

sottolista = list(range(5))
lista = [sottolista]
print(lista)

crea una lista lista che contiene una lista sottolista come elemento. Quando modifico sottolista, che e’ una lista e quindi e’ mutabile, finisco inavvertitamente per modificare anche lista!:

sottolista.append(5)
print(sottolista)

print(lista)

Esempio. Questo codice mostra un’altra anomalia:

lista = list(range(5))
print(lista)

finta_copia = lista         # copio solo il *riferimento* a lista!
print(finta_copia)

lista.append(5)
print(lista)

print(finta_copia)           # Ooops!

Questo accade perche’ lista_1 e lista_2 si riferiscono allo stesso oggetto lista. Se voglio creare una copia reale della lista lista, scrivo:

lista = list(range(5))
print(lista)

copia_vera = list(lista)
# oppure
# copia_vera = [elem for elem in lista]

print(copia_vera)

lista.append(5)
print(lista)

print(copia_vera)

Esercizi

  1. Inserire nella lista lista prima un intero, poi una stringa, poi una lista.

    Warning

    La lista deve esistere gia’ prima di poterci fare append(), extend(), insert(), etc.. Ad esempio:

    >>> una_lista_che_non_ho_mai_definito.append(0)
    

    da’ errore:

    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    NameError: name 'una_lista_che_non_ho_mai_definito' is not defined
    
  2. Partendo (per ogni punto) da:

    lista = list(range(3))
    

    cosa fanno i seguenti frammenti di codice? (Ripartite ogni volta da lista = list(range(3)).)

    1. lista.append(3)
    2. lista.append([3])
    3. lista.extend([3])
    4. lista.extend(3)
    5. lista.insert(0, 3)
    6. lista.insert(3, 3)
    7. lista.insert(3, [3])
    8. lista.insert([3], 3)
  3. Che differenza c’e’ tra:

    lista = []
    lista.append(list(range(10)))
    lista.append(list(range(10, 20)))
    

    e:

    lista = []
    lista.extend(list(range(10)))
    lista.extend(list(range(10, 20)))
    

    Di che lunghezza e’ lista nei due casi?

  4. Che cosa fa questo codice?:

    lista = [0, 0, 0, 0]
    lista.remove(0)
    
  5. Che cosa fa questo codice?:

    lista = [1, 2, 3, 4, 5]
    lista.reverse()
    lista.sort()
    

    Posso riscriverlo cosi’?:

    lista = [1, 2, 3, 4, 5]
    lista.reverse().sort()
    
  6. Data la lista:

    lista = list(range(10))
    

    mettere in lista_inversa gli elementi di lista in ordine inverso (dall’ultimo al primo) usando reverse(). lista non deve essere alterata.

  7. Data la lista:

    frammenti = [
        "KSYK",
        "SVALVV"
        "GVTGI",
        "VGSSLAEVLKLPD",
    ]
    

    mettere in frammenti_ordinati gli elementi di frammenti ordinati alfanumericamente con sort(). frammenti non deve essere alterata.

  8. (Una Curiosita’ Inutile). Che “struttura” ha questa lista?:

    lista = []
    lista.append(lista)
    

Metodi Stringhe-Liste

Ritorna Metodo Significato
list-of-str str.split(str) Rompe una stringa in una lista di stringhe
str str.join(list-of-str) Ricompone una lista di stringhe in una stringa

Esempio. La lista di stringhe:

tabella = [
    "nome,cognome,numero di premi nobel vinti",
    "Albert,Einstein,1",
    "Marie,Curie,2",
]

che riporta informazioni su personaggi noti in tre colonne separate da virgole ",".

Estraggo i titoli delle colonne dall’intestazione (la prima riga della tabella) con split():

titoli_colonne = tabella[0].split(",")

print(titoli_colonne)
print(type(titoli_colonne))

e calcolo quante colonne ci sono:

num_colonne = len(titoli_colonne)

Esempio. join() e’ utile per ricomporre liste di stringhe, ad esempio:

lista_di_stringhe = ["uno", "due", "tre"] * 100

print(type(lista_di_stringhe), lista_di_stringhe)

stringa_intera = " ".join(lista_di_stringhe)

print(type(stringa_intera), stringa_intera)

Warning

Quando uso join(), la lista deve contenere stringhe! Questo non funziona:

" ".join([1, 2, 3])

Esercizi

  1. Data la stringa:

    testo = """The Wellcome Trust Sanger Institute
    is a world leader in genome research."""
    

    mettere le parole di testo in una lista di stringhe. Poi stampare a schermo quante parole contiene.

    Poi mettere in prima_riga la prima riga di testo.

    Fare la stessa cosa con seconda_riga.

    Estrarre la prima parola di seconda_riga e stamparla a schermo.

  2. La tabella di stringhe:

    tabella = [
        "protein | database | domain | start | end",
        "YNL275W | Pfam | PF00955 | 236 | 498",
        "YHR065C | SMART | SM00490 | 335 | 416",
        "YKL053C-A | Pfam | PF05254 | 5 | 72",
        "YOR349W | PANTHER | 353 | 414",
    ]
    

    presa da Saccharomyces Genome Database, rappresenta una lista di (informazioni su) domini identificati nella sequenza di alcune proteine del lievito.

    Ogni riga e’ un dominio, tranne la prima (che fa da intestazione).

    Usando split() ottenere una lista dei titoli delle varie colonne, avendo cura di accertarsi che le stringhe che corrispondono ai titoli non contengano spazi superflui.

    Hint: non e’ necessario usare strip().

  3. Data la lista di stringhe:

    parole = ["parola_1", "parola_2", "parola_3"]
    

    costruire, usando solo join() ed un opportuno delimitatore le seguenti stringhe:

    1. "parola_1 parola_2 parola_3"
    2. "parola_1,parola_2,parola_3"
    3. "parola_1 e parola_2 e parola_3"
    4. "parola_1parola_2parola3"
    5. r"parola_1\parola_2\parola_3"
  4. Data la lista di stringhe:

    versi = [
        "Taci. Su le soglie",
        "del bosco non odo",
        "parole che dici",
        "umane; ma odo",
        "parole piu' nuove",
        "che parlano gocciole e foglie",
        "lontane."
    ]
    

    usare join() per creare una stringa multi-linea con i versi in versi.Il risultato ("poesia") deve essere:

    >>> print(poesia)
    Taci. Su le soglie
    del bosco non odo
    parole che dici
    umane; ma odo
    parole piu' nuove
    che parlano gocciole e foglie
    lontane.
    

    Hint: che delimitatore devo usare?

List Comprehension

La list comprehension permette di trasformare e/o filtrare una lista.

Data una lista qualunque lista_originale, posso creare una nuova lista che contiene solo gli elementi che soddisfano una certa condizione:

lista_filtrata = [elemento
                  for elemento in lista_originale
                  if condizione(elemento)]

Qui condizione() ce la inventiamo noi.


Esempio. Creo una nuova lista contenente solo i numeri pari da 0 a 9:

numeri = range(10)
numeri_pari = [numero for numero in numeri
               if numero % 2 == 0]
print(numeri_pari)

Esempio. Data la lista di sequenze nucleotidiche:

sequenze = ["ACTGG", "CCTGT", "ATTTA", "TATAGC"]

tengo solo le sequenze che contengono almeno una adenosina:

sequenze_con_a = [sequenza for sequenza in sequenze
                  if "A" in sequenza]
print(sequenze_con_a)

Per tenere solo quelle che non contengono adenosina, nego la condizione:

sequenze_senza_a = [sequenza for sequenza in sequenze
                    if not "A" in sequenza]
print(sequenze_senza_a)

Esempio. Se ometto la condizione, cosi’:

lista_2 = [elemento for elemento in lista]

ottengo una copia di lista.


Esempio. Uso una lista di liste per descrivere una rete di regolazione tra geni:

microarray = [
    ["G1C2W9", "G1C2Q7", 0.2],
    ["G1C2W9", "G1C2Q4", 0.9],
    ["Q6NMS1", "G1C2W9", 0.8],
    # ^^^^^^    ^^^^^^   ^^^
    #  gene1     gene2   correlazione
]

Ogni lista “interna” ha tre elementi: i primi due sono identificativi di geni di A. Thaliana, il terzo e’ una misura di correlazione tra l’espressione dei due geni in un qualche microarray.

Posso usare una list comprehension per tenere solo le coppie di geni con correlazione alta:

geni_altamente_correlati = \
    [tripla[:-1] for tripla in microarray if tripla[-1] > 0.75]

oppure ottenere i geni che sono altamente coespressi con il gene "G1C2W9":

soglia = 0.75
geni_coespressi = \
    [tripla[0] for tripla in microarray
     if tripla[1] == "G1C2W9" and tripla[-1] >= soglia] + \
    [tripla[1] for tripla in microarray
     if tripla[0] == "G1C2W9" and tripla[-1] >= soglia]

Warning

Il nome della variabile che itera sugli elementi (nell’esempio sopra, elemento) e’ arbitrario. Questo codice:

intervallo = range(10)
print([x for x in intervallo if x > 5])

e’ identico a questo:

intervallo = range(10)
print([y for y in intervallo if y > 5])

Il nome della variabile, x o y, e’ immateriale.


La list comprehension puo’ essere usata anche per creare una nuova lista che contiene gli elementi di lista_originale trasformati (uno per uno, individualmente) in qualche modo:

lista_trasformata = [trasforma(elemento)
                     for elemento in lista_originale]

Qui trasforma() e’ una “trasformazione” che ci inventiamo noi.


Esempio. Dato l’intervallo:

numeri = range(10)

creo una nuova lista con i loro doppi:

doppi = [numero * 2 for numero in numeri]
#        ^^^^^^^^^^
#      trasformazione
print(doppi)

Esempio. Data la lista di percorsi relativi alla directory data/:

percorsi = ["aatable", "fasta.1", "fasta.2"]

prefisso il percorso "data/" a ciascun elemento:

percorsi_completi = ["data/" + percorso
                     for percorso in percorsi]
print(percorsi_completi)

Esempio. Data la lista di sequenze primarie:

sequenze = [
    "MVLTIYPDELVQIVSDKIASNK",
    "GKITLNQLWDIS",
    "KYFDLSDKKVKQFVLSCVILKKDIE",
    "VYCDGAITTKNVTDIIGDANHSYS",
]

metto in una lista nuova lunghezze le lunghezze di ciascuna sequenza, in ordine:

lunghezze = [len(sequenza) for sequenza in sequenze]
print(lunghezze)

Esempio. Data una lista di stringhe:

atomi = [
    "SER A 96 77.253 20.522 75.007",
    "VAL A 97 76.066 22.304 71.921",
    "PRO A 98 77.731 23.371 68.681",
    "SER A 99 80.136 26.246 68.973",
    "GLN A 100 79.039 29.534 67.364",
    "LYS A 101 81.787 32.022 68.157",
]

che rappresenta (parte della) struttura terziaria di una catena proteica, voglio ottenere una lista di liste che contiene, per ogni residuo (stringa) in atomi, le sue coordinate (tre elementi).

Scrivo:

coordinate = [riga.split()[-3:] for riga in atomi]

ed ottengo:

>>> print(coordinate)
[
    ["77.253", "20.522", "75.007"],
    ["76.066", "22.304", "71.921"],
    ["77.731", "23.371", "68.681"],
    ["80.136", "26.246", "68.973"],
    ["79.039", "29.534", "67.364"],
    ["81.787", "32.022", "68.157"],
]

Come funziona questo codice? Consideriamo la prima riga di``atomi``:

"SER A 96 77.253 20.522 75.007"

Quando la list comprehension incontra questa riga, fa questo:

riga = "SER A 96 77.253 20.522 75.007"

poi applica la trasformazione riga.split()[-3:], i cui passaggi sono:

>>> print(riga.split())
["SER", "A", "96", "77.253", "20.522", "75.007"]
#                  ^^^^^^^^  ^^^^^^^^  ^^^^^^^^
#                     -3        -2        -1

>>> print(riga.split()[-3:])
["77.253", "20.522", "75.007"]

quindi il risultato della trasformazione applicata a questa riga e’ la lista:

["77.253", "20.522", "75.007"]

Questa lista viene appesa a coordinate.

A questo punto la list comprehension prende la seconda seconda riga di atomi:

"VAL A 97 76.066 22.304 71.921"

la mette in riga, ed applica la stessa trasformazione, ottenendo la lista:

["76.066", "22.304", "71.921"]

che appende a coordinate.

Poi prende la terza riga di atomi, etc.


Infine, posso combinare filtro e trasformazione per creare una nuova lista che contiene solo gli elementi di lista_originale che soddisfano una certa condizione, ma trasformati in qualche modo:

nuova_lista = [trasforma(elemento)
               for elemento in lista_originale
               if condizione(elemento)]

Esempio. Dati gli interi da 0 a 10, voglio tenere solo quelli pari e dividerli per 3:

pari_diviso_3 = [float(numero) / 3
                 for numero in range(10)
                 if numero % 2 == 0]

Notate che la condizione opera su numero (l’elemento originale della lista oridinale, non trasformato), non su float(numero) / 3.


Warning

La list comprehension costruisce una nuova lista, lasciando l’originale inalterata, sia quando filtro:

numeri = range(10)
numeri_pari = [numero
               for numero in lista_originale
               if numero % 2 == 0]

print(numeri, "e' lunga", len(numeri))
print(numeri_pari, "e' lunga", len(numeri_pari))

sia quando trasformo:

numeri = range(10)
doppi = [numero * 2 for numero in numeri]

print(numeri)
print(doppi)

Esercizi

Warning

Nei prossimi esercizi, se open() da’ errore e’ probabile che non abbiate fatto partire il terminale dalla directory giusta. Ad esempio in questo caso:

>>> righe = open("file/che/non/esiste").readlines()

Python da’ (giustamente) errore:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 2] No such file or directory: 'file/che/non/esiste'
#                  ^^^^^^^^^^^^^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^^^^^^^
#                  non esiste questo file!        nome del file

Assicuratevi di adattare il percorso in base alla directory nella quale vi trovate.

  1. Data la lista:

    lista = list(range(100))
    
    1. Creare una nuova lista lista_piu_3 contenente il valore degli elementi di lista piu’ 3. Il risultato deve essere:

      [3, 4, 5, ...]
      
    2. Creare una nuova lista lista_dispari contenente solo gli elementi dispari di lista. Il risultato deve essere:

      [1, 3, 5, ...]
      

      Hint: un intero e’ dispari se e solo se il risultato di:

      numero % 2
      

      e’ 1.

    3. Creare una nuova lista lista_opposti contenente l’opposto aritmetico (l’opposto di x e’ -x) degli elementi di lista. Il risultato deve essere:

      [0, -1, -2, ...]
      
    4. Creare una nuova lista lista_inversi contenente l’inverso aritmetico (l’inverso aritmetico di x e’ \frac{1}{x}) degli elementi di lista. Se l’inverso di un elemento non esiste, l’elemento deve essere ignorato (non comparire in lista_inversi). Il risultato deve essere:

      [1, 0.5, 0.33334, ...]
      

      Hint: l’unico intero senza un inverso e’ 0.

    5. Creare una nuova lista contenente solo il primo e l’ultimo elemento di lista. Il risultato deve essere:

      [0, 99]
      

      Hint: si fa con una list comprehension?

    6. Creare una nuova lista contenente tutti gli elementi di lista tranne il primo e l’ultimo. Il risultato deve essere:

      [1, 2, ..., 97, 98]
      
    7. Contare quanti numeri dispari ci sono in lista. Il risultato deve essere 50.

      Hint: basta usare una list comprehension?

    8. Creare una nuova lista contenente tutti gli elementi di lista divisi per 5 (anche quelli non divisibili per 5!). Il risultato deve essere:

      [0.0, 0.2, 0.4, ...]
      
    9. Creare una nuova lista lista_multipli_5_divisi contenente solo i multipli di 5, ma divisi per 5. Il risultato deve essere:

      [0.0, 1.0, 2.0, ..., 19.0]
      
    10. Creare una nuova lista lista_di_stringhe contenente tutti gli elementi di lista ma convertiti in stringhe. Il risultato deve essere:

      ["0", "1", "2", ...]
      
    11. Contare quante stringhe rappresentanti un numero dispari ci sono in lista_di_stringhe.

    12. Creare una stringa che contenga tutti gli elementi di lista, visti come stringhe, e separati da uno spazio. Il risultato deve essere:

      "0 1 2 ..."
      

      Hint: basta usare una list comprehension?

  2. Per ciascuno dei punti seguenti, scrivere due list comprehension che producano lista_1 da lista_2 e viceversa.

    1. lista_1 = [1, 2, 3]
      lista_2 = ["1", "2", "3"]
      
    2. lista_1 = ["nome", "cognome", "eta'"]
      lista_2 = [["nome"], ["cognome"], ["eta'"]]
      
    3. lista_1 = ["ACTC", "TTTGGG", "CT"]
      lista_2 = [["actc", 4], ["tttgggcc", 6], ["ct", 2]]
      
  3. Data la lista:

    lista = list(range(10))
    

    quali dei seguenti frammenti sono validi o errati, e cosa fanno?

    1. [x for x in lista]
    2. [y for y in lista]
    3. [y for x in lista]
    4. ["x" for x in lista]
    5. [str(x) for x in lista]
    6. [x for str(x) in lista]
    7. [x + 1 for x in lista]
    8. [x + 1 for x in lista if x == 2]
  4. Data la lista di stringhe dna restituita da:

    dna = open("data/dna-fasta/fasta.1").readlines()
    print(dna)
    
    1. Creare una nuova lista di stringhe che contenga tutte le stringhe in dna tranne quella di intestazione (la riga che comincia per ">").
    2. Ci sono caratteri di a capo o spazi nella lista di stringhe ottenuta? Se si’, creare una nuova lista di stringhe che sia identica a quella ottenuta, ma dove le stringhe non contengano caratteri di a capo ne’ spazi.
    3. Concatenare in una singola stringa tutte le righe ottenute.
    4. Calcolare la percentuale di citosina e guanina nella sequenza ottenuta.
    5. Calcolare il GC-content della sequenza.
  5. Consideriamo la stringa:

    risultato_cdhit = """\
    >Cluster 0
    0 >YLR106C at 100.00%
    >Cluster 50
    0 >YPL082C at 100.00%
    >Cluster 54
    0 >YHL009W-A at 90.80%
    1 >YHL009W-B at 100.00%
    2 >YJL113W at 98.77%
    3 >YJL114W at 97.35%
    >Cluster 52
    0 >YBR208C at 100.00%
    """
    

    ottenuta raggruppando le strutture primarie del genoma di S. Cerevisiae (preso da SGD) con un software di clustering (CD-HIT).

    risultato_cdhit codifica in forma testuale alcuni cluster di proteine raggruppate in base alla similarita’ delle loro sequenze.

    Un gruppo comincia con la riga:

    >Cluster N
    

    dove N e’ il numero del cluster. I contenuti del cluster sono dati dalle righe successive, ad esempio:

    >Cluster 54
    0 >YHL009W-A at 90.80%
    1 >YHL009W-B at 100.00%
    2 >YJL113W at 98.77%
    3 >YJL114W at 97.35%
    

    rappresenta un gruppo di quattro sequenze, denominato "Cluster 54": di quel gruppo fanno parte la proteina "YHL009W-A" con una similarita’ del 90.80%, la proteina "YHL009-B" con una similarita’ del 100.00%, etc.

    Data risultato_cdhit, usare delle list comprehension per:

    1. Estrarre i nomi dei vari cluster. Il risultato deve essere:

      >>> print(nomi_cluster)
      ["0", "50", "54", "52"]
      
    2. Estrarre i nomi di tutte le proteine (non importa se ci sono doppioni). Il risultato deve essere:

      >>> print(proteine)
      ["YLR1106C", "YPL082C", "YHL00W-A", ...]
      
    3. Estrarre le coppie proteina-percentuale per tutte le proteine. il risultato deve essere:

      >>> print(coppie_proteina_percentuale)
      [["YLR106C", 100.0],
       ["YPL082C", 100.0],
       ["YHL009W-A", 90.8],
       # ...
      ]
      
  6. Il comando:

    righe = open("data/prot-pdb/1A3A.pdb").readlines()
    print(" ".join(righe))                   # stampo le righe
    print(len(righe))                        # 5472
    

    restituisce una lista di righe del file data/prot-pdb/1A3A.pdb, preso dalla Protein Data Bank. Descrive una proteina di E. Coli.

    Hint: aprite il file con un editor di testo (nano, gedit, quello che preferite) e fatevi un’idea dei contenuti prima di procedere!

    1. Estrarre tutte le righe che cominciano per "SEQRES" e mettere il risultato nella lista righe_seqres.

      Dovrebbero esserci esattamente 48 righe di questo tipo. Il risultato deve somigliare a questo:

      >>> print(" ".join(righe_seqres))
      SEQRES   1 A  148  MET ALA ASN LEU PHE LYS LEU GLY ALA GLU ASN ILE PHE
      SEQRES   2 A  148  LEU GLY ARG LYS ALA ALA THR LYS GLU GLU ALA ILE ARG
      SEQRES   3 A  148  PHE ALA GLY GLU GLN LEU VAL LYS GLY GLY TYR VAL GLU
      # ...
      SEQRES  10 D  148  LEU THR ASN ALA LEU ASP ASP GLU SER VAL ILE GLU ARG
      SEQRES  11 D  148  LEU ALA HIS THR THR SER VAL ASP GLU VAL LEU GLU LEU
      SEQRES  12 D  148  LEU ALA GLY ARG LYS
      #          ^       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      #        catena    sequenza primaria della catena
      

      La prima colonna delle righe in righe_seqres e’ sempre "SEQRES" (per costruzione), la terza e’ il nome della catena di 1A3A descritta in quella riga, mentre le colonne dalla quinta in poi descrivono la sequenza primaria della catena stessa.

      1. Estrarre le catene da righe_seqres (non importa se ci sono doppioni).

        Il risultato deve essere:

        >>> print(catene)
        ["A", ..., "B", ..., "C", ..., "D", ...]
        
      2. Estrarre solo le righe della catena B e metterle in righe_seqres_B. Devono esserci esattamente 12 righe.

      3. Estrarre da righe_seqres_B la sequenza della catena B e metterla in una sola stringa sequenza_B.

        Il risultato deve essere:

        >>> print(sequenza_B)
        "MET ALA ASN LEU PHE ... ALA GLY ARG LYS"
        
    2. Estrarre da righe tutte le righe che cominciano per "HELIX" e mettere il risultato nella lista righe_helix.

      Devono esserci esattamente 30 righe di questo tipo. Il risultato deve somigliare a questo:

      >>> print(" ".join(righe_helix))
      HELIX    1   1 ALA A    9  ASN A   11  5                                   3
      HELIX    2   2 LYS A   21  LYS A   34  1                                  14
      HELIX    3   3 PRO A   40  LEU A   52  5                                  13
      HELIX    4   4 VAL A   68  ARG A   73  5                                   6
      HELIX    5   5 HIS A  111  ALA A  121  1                                  11
      #              ^^^^^^^^^^  ^^^^^^^^^^
      #             inizio elica  fine elica
      

      La prima colonna delle righe in righe_helix e’ sempre "HELIX" (per costruzione). Ogni riga descrive una \alpha-helix della proteina 1A3A.

      La quarta, quinta e sesta colonna descrivono il residuo dal quale parte l’elica: tipo del residuo, catena di riferimento, e posizione del residuo.

      La settima, ottava e nona colonna descrivono il residuo dove finisce l’elica: sempre con tipo, catena e posizione.

      Estrarre una lista info_eliche in cui ogni elemento rappresenta un’elica, e ne contiene la posizione di inizio, la posizione di fine, e la lunghezza.

  7. Data la matrice 3\times 3:

    matrice = [list(range(0,3)), list(range(3,6)), list(range(6,9))]
    
    1. Mettere in una lista prima_riga la prima riga.
    2. Mettere in una lista prima_colonna la prima colonna.
    3. Creare una matrice sottosopra che contenga le righe di matrice ma sottosopra.
    4. (Difficile.) Creare una matrice palindromo che contenga le colonne di matrice ma da sinistra a destra.
    5. (Difficile.) Ricreare matrice con una sola list comprehension.
  8. (Difficile). Data la lista:

    lista = range(100)
    

    Creare una lista lista_quadrati contenente i quadrati degli elementi di lista. Il risultato deve essere:

    [0, 1, 4, 9, ...]
    

    Poi creare una lista lista_differenze_quadrati che contenga, nella posizione i-esima il valore:

    lista_quadrati[i+1] - lista_quadrati[i]
    

    per tutti, tranne l’ultimo, valore di lista_quadrati. E’ consigliabile usare piu’ di un passaggio, ed eventualmente liste ausiliarie.

    (Che numeri avete ottenuto? Ecco perche’.)


Python: Liste (Soluzioni)

Note

In alcune soluzioni uso il carattere \ alla fine di una riga di codice.

Usato in questo modo, \ spiega a Python che il comando continua alla riga successiva. Se non usassi \, Python potrebbe pensare che il comando finisca li’ e quindi che sia sintatticamente sbagliato – dando errore.

Potete tranquillamente ignorare questi \.

Operazioni

  1. Soluzione:

    lista = []
    print(lista, len(lista))         # controllo
    
  2. Soluzione:

    lista = list(range(5))
    print(lista, len(lista))         # controllo
    print(len(lista))
    
  3. Soluzione:

    lista = [0] * 100
    print(lista, len(lista))         # controllo
    
  4. Soluzione:

    lista_1 = list(range(10))
    lista_2 = list(range(10, 20))
    
    lista_completa = lista_1 + lista_2
    print(lista_completa)
    
    print(lista_completa) == list(range(20))   # Ture
    
  5. Soluzione:

    lista = ["sono", "una", "lista"]
    print(lista, len(lista))         # controllo
    
    print(len(lista[0]))
    print(len(lista[1]))
    print(len(lista[2]))
    
  6. Soluzione:

    lista = [0.0, "b", [3], [4, 5]]
    
    print(len(lista))                # 4
    
    print(type(lista[0]))            # float
    
    print(lista[1], len(lista[1]))   # "b", 1
    
    print(lista[2], len(lista[2]))   # [3], 1
    
    print(lista[-1], len(lista[-1])) # [4, 5], 2
    
    print("b" in lista)              # True
    
    print(4 in lista)                # False
    print(4 in lista[-1])            # True
    
  7. Soluzione: la prima e’ una lista di interi, la seconda una lista di stringhe, mentre la terza e’ una stringa!:

    print(type(lista_1))             # list
    print(type(lista_2))             # list
    print(type(lista_3))            # str
    
  8. Soluzioni:

    # una lista vuota
    lista = []
    print(len(lista))                # 0
    del lista
    
    
    # sintassi non valida, Python da' errore
    lista = [}
    
    
    # una lista che contiene una lista vuota
    lista = [[]]
    print(len(lista))                # 1
    print(len(lista[0]))             # 0
    del lista
    
    
    # non funziona perche' lista non e' definita!
    lista.append(0)
    
    
    # questa invece funziona
    lista = []
    lista.append(0)
    print(lista)                     # [0]
    del lista
    
    
    # non funziona perche' mancano le virgole!
    lista = [1 2 3]
    
    
    # da' errore perche' la lista ha solo 3 elementi!
    lista = list(range(3))
    print(lista[3])
    
    
    # estrae l'ultimo elemento
    lista = list(range(3))
    print(lista[-1])
    del lista
    
    
    # estrae i primi due elementi (lista[2], il terzo,
    # e' escluso)
    lista = list(range(3))
    sottolista = lista[0:2]
    print(lista)
    del lista
    
    
    # estrare tutti gli elementi (lista[3], che 'non
    # esiste', e' escluso)
    lista = list(range(3))
    sottolista = lista[0:3]
    print(lista)
    del lista
    
    
    # estrae i primi due elementi (lista[-1], il terzo,
    # e' escluso)
    lista = list(range(3))
    sottolista = lista[0:-1]
    print(lista)
    del lista
    
    
    # inserisce in terza posizione la stringa "due"
    lista = list(range(3))
    lista[2] = "due"
    print(lista)
    del lista
    
    
    # non funziona: la lista contiene solo tre elementi,
    # quindi non ha una quarta posizione, e Python da'
    # errore
    lista = list(range(3))
    lista[3] = "tre"
    
    
    # inserisce in terza posizione la stringa "tre"
    lista = list(range(3))
    lista[-1] = "tre"
    print(lista)
    del lista
    
    
    # l'indice deve essere un intero, Python da' errore
    lista = list(range(3))
    lista[1.2] = "uno virgola due"
    
    
    # sostituisce il secondo elemento di lista (cioe' 1)
    # con una lista di due stringhe; e' perfettamente
    # legale: le liste *possono* contenere altre stringhe
    lista = list(range(3))
    lista[1] = ["testo-1", "testo-2"]
    print(lista)
    del lista
    
  9. Soluzione:

    matrice = [
        [1, 2, 3],
        [4, 5, 6],
        [7, 8, 9],
    ]
    
    prima_riga = matrice[0]
    print(prima_riga)
    
    secondo_elemento_prima_riga = prima_riga[1]
    # oppure
    secondo_elemento_prima_riga = matrice[0][1]
    print(secondo_elemento_prima_riga)
    
    somma_prima_riga = matrice[0][0] + matrice[0][1] + matrice[0][2]
    print(somma_prima_riga)
    
    seconda_colonna = [matrice[0][1], matrice[1][1], matrice[2][1]]
    print(seconda_colonna)
    
    diagonale = [matrice[0][0], matrice[1][1], matrice[2][2]]
    print(diagonale)
    
    tre_righe_assieme = matrice[0] + matrice[1] + matrice[2]
    print(tre_righe_assieme)
    

Metodi

  1. Prima dichiaro una lista qualunque, ad esempio quella vuota:

    lista = []
    

    poi aggiungo i vari elementi richiesti con append():

    lista.append(0)
    lista.append("testo")
    lista.append([0, 1, 2, 3])
    
  2. Soluzione:

    # aggiunge un 3 alla fine della lista
    lista = list(range(3))
    lista.append(3)
    print(lista)
    del lista
    
    # aggiunge una lista con un 3 dentro alla fine della lista
    lista = list(range(3))
    lista.append([3])
    print(lista)
    del lista
    
    # aggiunge un 3 (il solo elemento contenuto nella lista [3]) alla
    # fine della lista
    lista = list(range(3))
    lista.extend([3])
    print(lista)
    del lista
    
    # non funziona: extend() estende una lista con i contenuti
    # di un'altra lista, ma qui 3 *non* e' una lista! Python da'
    # errore
    lista = list(range(3))
    lista.extend(3)
    
    # sostituisce l'elemento in posizione 0, il primo, con il
    # valore 3
    lista = list(range(3))
    lista.insert(0, 3)
    print(lista)
    del lista
    
    # inserisce un 3 alla fine di lista
    lista = list(range(3))
    lista.insert(3, 3)
    print(lista)
    del lista
    
    # inserisce la lista [3] alla fine di lista
    lista = list(range(3))
    lista.insert(3, [3])
    print(lista)
    del lista
    
    # non funziona: il primo argomento di insert() deve essere
    # un intero, qui gli stiamo dando una lista! Python da' errore
    lista = list(range(3))
    lista.insert([3], 3)
    
  3. Soluzione:

    lista = []
    lista.append(range(10))
    lista.append(range(10, 20))
    print(lista)
    

    Qui uso append(), che inserisce un elemento alla fine di lista. In questo caso inserisco due liste, cioe’ i risultati di range(10) e range(10, 20).

    E’ chiaro che len(lista) e’ 2, visto che ho inserito solo due elementi.

    Invece:

    lista = []
    lista.extend(range(10))
    lista.extend(range(10, 20))
    print(lista)
    

    fa uso di extend(), che “estende” una lista con un’altra lista. Qui la lista finale ha 20 elementi, come si evince con:

    print(len(lista))
    
  4. Soluzione:

    lista = [0, 0, 0, 0]
    lista.remove(0)
    print(lista)
    

    solo la prima ripetizione di 0 viene rimossa!

  5. Soluzione:

    lista = [1, 2, 3, 4, 5]
    
    # inverte l'ordine degli elementi di lista
    lista.reverse()
    print(lista)
    
    # ordina gli elementi di lista
    lista.sort()
    print(lista)
    

    Il risultato e’ che dopo le due operazioni lista torna al suo valore iniziale.

    Invece questo:

    lista = [1, 2, 3, 4, 5]
    lista.reverse().sort()
    

    non si puo’ fare. Il risultato di lista.reverse() e’ None:

    lista = [1, 2, 3, 4, 5]
    risultato = lista.reverse()
    print(risultato)
    

    che certamente non e’ una lista, e quindi non ci posso fare sopra sort(). Python dara’ errore.

  6. Sono tentato di scrivere:

    lista = list(range(10))
    lista_inversa = lista.reverse()
    
    print(lista)                         # modificata!
    print(lista_inversa)                 # None!
    

    ma questa cosa non funziona: reverse() modifica lista e restituisce None! Perdipiu’ questo codice modifica lista direttamente, ed io non voglio.

    Quindi prima faccio una copia di lista, e poi ordino quella:

    lista = list(range(10))
    lista_inversa = lista[:]            # *non* lista_inversa = lista
    lista_inversa.reverse()
    print(lista)                         # invariata
    print(lista_inversa)                 # invertita
    

    Invece questo codice:

    lista = list(range(10))
    lista_inversa = lista
    lista_inversa.reverse()
    print(lista)                         # modificata!
    print(lista_inversa)                 # invertita
    

    non funziona come vorrei: quello che succede e’ che lista_inversa contiene non una copia di lista, ma un riferimento allo stesso oggetto riferito da lista.

    Quindi quando inverto lista_inversa finisco per invertire anche lista.

  7. Come sopra:

    frammenti = [
        "KSYK",
        "SVALVV"
        "GVTGI",
        "VGSSLAEVLKLPD",
    ]
    frammenti_ordinati = frammenti.sort()
    

    non funziona: sort() ordina lista e restituisce None! Quindi sono costretto a fare prima una copia di frammenti, e poi ad ordinare quella:

    frammenti_ordinati = frammenti[:]
    frammenti_ordinati.sort()
    print(frammenti)                     # invariata
    print(frammenti_ordinati)            # ordinata
    
  8. Soluzione:

    lista = []
    lista.append(lista)
    
    print(lista)
    

    crea una lista che contiene se’ stessa; lista e’ una struttura che in gergo viene detta ricorsiva.

    lista e’ chiaramente infinita (in termini di annidamento), visto che posso farci cose come queste:

    print(lista)
    print(lista[0])
    print(lista[0][0])
    print(lista[0][0][0])
    print(lista[0][0][0][0])
    # ...
    

    Visto che lista ha profondita’ infinita, Python quando la stampa usa un’ellissi ... per indicare che c’e’ una quantita’ infinita di liste dentro a lista (ed ovviamente non puo’ stamparle tutte).

    Non vedremo altre strutture ricorsive nel corso.

Metodi Stringhe-Liste

  1. Soluzione:

    testo = """The Wellcome Trust Sanger Institute
    is a world leader in genome research.""""
    
    parole = testo.split()
    print(len(parole))
    
    
    righe = testo.split("\n")
    
    prima_riga = righe[0]
    print("la prima riga e':", prima_riga)
    
    seconda_riga = righe[1]
    print("la seconda riga e':", seconda_riga)
    
    
    print("la prima parola della seconda riga e':", seconda_riga.split()[0])
    
  2. Soluzione:

    tabella = [
        "protein | database | domain | start | end",
        "YNL275W | Pfam | PF00955 | 236 | 498",
        "YHR065C | SMART | SM00490 | 335 | 416",
        "YKL053C-A | Pfam | PF05254 | 5 | 72",
        "YOR349W | PANTHER | 353 | 414",
    ]
    
    prima_riga = tabella[0]
    titoli_colonne_quasi = prima_riga.split("|")
    print(titoli_colonne_quasi)
    # ["protein ", " database ", ...]
    
    # purtroppo qui i titoli delle colonne contengono spazi superflui
    # per evitarli posso cambiare il delimitatore che do a split()
    
    titoli_colonne = prima_riga.split(" | ")
    print(titoli_colonne)
    # ["protein", "database", ...]
    

    Volendo si puo’ usare anche strip() assieme ad una list comprehension su titoli_colonne_quasi, ma (come appena dimostrato) non e’ necessario.

  3. Soluzione:

    parole = ["parola_1", "parola_2", "parola_3"]
    
    print(" ".join(parole))
    
    print(",".join(parole))
    
    print(" e ".join(parole))
    
    print("".join(parole))
    
    backslash = "\\"
    print(backslash.join(parole))
    
  4. Soluzione:

    versi = [
        "Taci. Su le soglie"
        "del bosco non odo"
        "parole che dici"
        "umane; ma odo"
        "parole piu' nuove"
        "che parlano gocciole e foglie"
        "lontane."
    ]
    
    poesia = "\n".join(versi)
    

List Comprehension

  1. Soluzioni:

    1. Soluzione:

      lista_piu_tre = [numero + 3 for numero in lista]
      
      print(lista_piu_tre)             # controllo
      
    2. Soluzione:

      lista_dispari = [numero for numero in lista
                       if (numero % 2 == 1)]
      
    3. Soluzione:

      lista_opposti = [-numero for numero in lista]
      
    4. Soluzione:

      lista_inversi = [1.0 / numero for numero in lista
                       if numero != 0]
      
    5. Soluzione:

      primo_e_ultimo = [lista[0], lista[-1]]
      
    6. Soluzione:

      dal_secondo_al_penultimo = lista[1:-1]
      
    7. Soluzione:

      lista_dispari = [numero for numero in lista
                       if (numero % 2 == 1)]
      quanti_dispari = len(lista_dispari)
      print(quanti_dispari)
      

      oppure abbreviando:

      quanti_dispari = len([numero for numero in lista
                            if (numero % 2 == 1)])
      
    8. Soluzione:

      lista_divisi_per_5 = [float(numero) / 5
                            for numero in lista]
      
    9. Soluzione:

      lista_multipli_5_divisi = [float(numero) / 5.0)
                                 for numero in lista
                                 if (numero % 5 == 0)]
      
    10. Soluzione:

      lista_di_stringhe = [str(numero) for numero in lista]
      
    11. Soluzione:

      # Come sopra, ma iterando su `lista_di_stringhe`
      # piuttosto che direttamente su `lista`
      quanti_dispari = len([stringa for stringa in lista_di_stringhe
                            if (int(stringa) % 5 == 0)])
      
    12. Soluzione:

      testo = " ".join([str(numero) for numero in lista])
      

      Occhio che se dimentico di fare str(numero), join() si rifiuta di funzionare.

  2. Soluzioni:

    1. # andata
      lista_1 = [1, 2, 3]
      lista_2 = [str(x) for x in lista_1]
      
      # ritorno
      lista_2 = ["1", "2", "3"]
      lista_1 = [int(x) for x in lista_2]
      
    2. # andata
      lista_1 = ["nome", "cognome", "eta'"]
      lista_2 = [[x] for x in lista_1]
      
      # ritorno
      lista_2 = [["nome"], ["cognome"], ["eta'"]]
      lista_1 = [l[0] for l in lista_2]
      
    3. # andata
      lista_1 = ["ACTC", "TTTGGG", "CT"]
      lista_2 = [[x.lower(), len(x)] for x in lista_1]
      
      # ritorno
      lista_2 = [["actc", 4], ["tttgggcc", 6], ["ct", 2]]
      lista_1 = [l[0].upper() for l in lista_2]
      
  3. Soluzione:

    1. [x for x in lista]: crea una copia di lista.
    2. [y for y in lista]: crea una copia di lista (identico a sopra).
    3. [y for x in lista]: invalida. (Se x rappresenta l’elemento della lista, cos’e’ y?)
    4. ["x" for x in lista]: crea una lista piena di stringhe "x" lunga quanto lista. Il risultato sara’: ["x", "x", ..., "x"].
    5. [str(x) for x in lista]: per ogni intero x in lista, lo converte in stringa con str(x) e mette il risultato nella lista che sta creando. Il risultato sara’: ["0", "1", ..., "9"].
    6. [x for str(x) in lista]: invalida: la trasformazione str(...) e’ nel posto sbagliato!
    7. [x + 1 for x in lista]: per ogni intero x in lista, aggiunge uno con x + 1 e mette il risultato nella lista che sta creando. Il risultato sara’: [1, 2, ..., 10].
    8. [x + 1 for x in lista if x == 2]: per ogni intero x in lista controlla se vale 2. Se lo e’ mette x + 1 nella lista che sta creando, altrimento lo scarta. Il risultato sara’: [3].
  4. Soluzione:

    dna = open("data/dna-fasta/fasta.1").readlines()
    print(" ".join(dna))
    
    
    # Rimuovo l'intestazione: due alternative
    dna_no_intestazione = [riga for riga in dna
                           if not riga[0].startswith(">")]
    dna_no_intestazione = [riga for riga in dna
                           if riga[0] != ">"]
    
    
    # Si', ci sono caratteri di a capo o spazi
    print(["\n" in riga for riga in dna_no_intestazione])
    print([" " in riga for riga in dna_no_intestazione])
    
    # Rimuovo i caratteri a capo da tutte le righe
    dna_solo_seq = [riga.strip() for riga in dna_no_intestazione]
    
    # Ricontrollo per sicurezza: non ci sono caratteri
    # di a capo ne' spazi
    print(["\n" in riga for riga in dna_solo_seq])
    print([" " in riga for riga in dna_solo_seq])
    
    
    # Concateno tutte le righe di dna_solo_seq
    sequenza = "".join(dna_solo_seq)
    
    
    # Calcolo il numero di "C" e "G"
    num_c = sequenza.count("C")
    num_g = sequenza.count("G")
    
    
    # Calcolo il GC-content, facendo attenzione da usare
    # dei float per evitare errori di approssimazione
    gc_content = float(num_c + num_g) / len(sequenza)
    
  5. Soluzione:

    risultato_cdhit = """\
    >Cluster 0
    0 >YLR106C at 100.00%
    >Cluster 50
    0 >YPL082C at 100.00%
    >Cluster 54
    0 >YHL009W-A at 90.80%
    1 >YHL009W-B at 100.00%
    2 >YJL113W at 98.77%
    3 >YJL114W at 97.35%
    >Cluster 52
    0 >YBR208C at 100.00%
    """
    
    righe = risultato_cdhit.split("\n")
    

    Per ottenere i nomi dei cluster, devo tenere solo le righe che cominciano per ">", e per ciascuna di queste fare lo split() in modo da poter ottenere il secondo elemento (che e’ il nome del cluster):

    nomi_cluster = [riga.split()[1] for riga in righe
                    if riga.startswith(">")]
    

    Per ottenere i nomi delle proteine, devo tenere solo le righe che non cominciano per ">", e per ciascuna di queste fare lo split() e tenere il secondo elemento (avendo cura di rimuovere il ">" dal nome della proteina):

    proteine = [riga.split()[1].lstrip(">") for riga in righe
                if not riga.startswith(">")]
    

    Per ottenere le coppie proteina-percentuale, come nel caso precedente, tengo solo le righe che non cominciano per ">". Su ciascuna di queste faccio split() e tengo il nome della proteina (secondo elemento) e la percentuale (ultimo elemento):

    coppie_proteina_percentuale = \
        [[riga.split()[1].lstrip(">"), riga.split()[-1].rstrip("%")]
         for riga in righe if not riga.startswith(">")]
    

    Versione annotata:

    coppie_proteina_percentuale = \
        [[riga.split()[1].lstrip(">"), riga.split()[-1].rstrip("%")]
    #     ^^^^^^^^^^^^^^^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    #       nome proteina, come sopra            percentuale
    #    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    #                     coppia proteina-percentuale
         for riga in righe if not riga.startswith(">")]
    
  6. Soluzione:

    righe = open("data/prot-pdb/1A3A.pdb").readlines()
    
    
    # ~~~~ prima parte ~~~~
    
    # Estraggo tutte le righe SEQRES
    righe_seqres = [riga for riga in righe
                    if riga.startswith("SEQRES")]
    print(len(righe_seqres))                     # 48
    
    # Estraggo i nomi delle catene
    catene = [riga.split()[2] for riga in righe_seqres]
    print(catene)
    
    # Estraggo da righe_seqres quelle relative alla catena B
    righe_seqres_b = [riga for riga in righe_seqres
                      if riga.split()[2] == "B"]
    print(len(righe_seqres_b))                   # 12
    
    # Estraggo le sequenze da ciascuna riga di righe_seqres_b,
    # poi concateno per ottenere il risultato voluto.
    sequenze_parziali_B = [" ".join(riga.split()[4:])
                           for riga in righe_seqres_b]
    sequenza_B = "".join(sequenze_parziali_B)
    
    
    # ~~~~ seconda parte ~~~~
    
    # Estraggo tutte le righe HELIX
    righe_helix = [riga for riga in righe
                   if riga.startswith("HELIX")]
    print(len(righe_helix))                      # 30
    
    # Estraggo dalle righe le posizioni di ciascuna elica
    eliche_inizio_fine = [[int(riga.split()[5]), int(riga.split()[8])]
    #                      ^^^^^^^^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^^^^^^
    #                           inizio elica         fine elica
                          for riga in righe_helix]
    
    # In un secondo passaggio (per comodita') calcolo
    # il risultato voluto
    info_eliche = [coppia + [coppia[1] - coppia[0] + 1]
    #              ^^^^^^   ^^^^^^^^^^^^^^^^^^^^^^^^^^^
    #           [inizio, fine]       lunghezza
                   for coppia in eliche_inizio_fine]
    
  7. Soluzione:

    matrice = [list(range(0,3)), list(range(3,6)), list(range(6,9))]
    
    
    # estraggo la prima riga
    prima_riga = matrice[0]
    
    
    # estraggo la prima colonna
    prima_colonna = [matrice[0][i] for i in range(3)]
    
    
    # inverto l'ordine delle righe
    sottosopra = matrice[:]
    sottosopra.reverse()
    # oppure
    sottosopra = [matrice[2-i] for i in range(3)]
    
    
    # inverto l'ordine delle colonne
    palindromo = []
    # appendo la prima riga di matrice invertita
    palindromo.append([matrice[0][2-i] for i in range(3)])
    # appendo la seconda riga di matrice invertita
    palindromo.append([matrice[1][2-i] for i in range(3)])
    # appendo la terza riga di matrice invertita
    palindromo.append([matrice[2][2-i] for i in range(3)])
    
    # oppure in un solo passaggio -- ma e' complicato e potete ignorarlo!!!
    palindromo = [[riga[2-i] for i in range(3)]
                   for riga in matrice]
    
    
    # ricreo matrice con una sola list comprehension
    matrice_di_nuovo = [range(i, i+3) for i in range(9)
                        if i % 3 == 0]
    
  8. Soluzioni:

    lista = list(range(100))
    
    lista_quadrati = [numero**2 for numero in numeri]
    
    lista_differenze_quadrati = \
        [lista_quadrati[i+1] - lista_quadrati[i]
         for i in range(len(lista_quadrati) - 1)]
    


Python: Tuple

Le tuple sono liste immutabili.

Per definire una lista uso le parentesi tonde:

partners = ("BIOGRID:112315", "BIOGRID:108607")

Esempio. Se voglio creare una tupla con un solo elemento (puo’ capitare…) devo ricordarmi di usare la virgola, oltre alle parentesi tonde:

tupla = (0,)
print(tupla)
print(type(tupla))
print(len(tupla))

non_tupla = (0)
print(non_tupla)
print(type(non_tupla))

Come si vede, non_tupla e’ un intero, 0.

Operazioni

Ritorna Operatore Significato
int len(tuple) Restituisce la lunghezza della tuple
tuple tuple + tuple Concatena le due tuple
tuple tuple * int Replica la tupla
bool object in tuple Contolla se un oggetto arbitrario appare nella tupla
tuple tuple[int:int] Estrae una sotto-tupla

Le tuple supportanto tutti gli operatori delle stringhe e delle liste, tranne l’assegnamento.


Esempio. Creo una tupla con elementi misti:

proteina = ("2B0Q", "phosphotransferase", "PF03881")

che contiene informazioni su una proteina. Posso accedere ai vari elementi come farei con una lista:

id_proteina = proteina[0]
print(id_proteina)

nome_proteina = proteina[1]
print(nome_proteina)

Pero’ non posso modificare la tupla:

>>> proteina[0] = "1A3A"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

Esempio. Proprio come le liste e gli intervalli, posso usare le tuple come input ad una list comprehension:

tripla = (1, 2, 3)

risultato = [2*numero for numero in tripla]
print(risultato)
print(type(risultato))

Il risultato di una list comprehension e’ comunque una lista.


Metodi

Ritorna Metodo Significato
int tuple.index(object) Restituisce la posizione di object.
int tuple.count(object) Conta il numero di ripetizioni di un valore

Conversione Lista-Tupla

Per convertire tra liste e tuple uso tuple() e list():

lista = list(range(10))

tupla = tuple(lista)
print(tupla, type(tupla))

lista_2 = list(tupla)
print(lista_2, type(lista_2))

Ne segue che se proprio voglio “modificare” una tupla posso scrivere:

tupla = ("sono", "una", "tupla")
print(tupla)

copia = list(tupla)
copia.insert(2, "bella")
tupla = tuple(copia)
print(tupla)

In realta’ quello che ottengo e’ una nuova tupla che contiene gli elementi della vecchia tupla in aggiunta a …

Esercizi

  1. Creare:

    1. Una tupla di due interi di valore 0 e 1.

    2. Una tupla di due stringhe, "una" e "tupla".

    3. Una tupla di un solo elemento, 0.

      Hint: ci sono due modi di farlo: usare la sintassi vista all’inzio del capitolo, oppure usare la conversione lista-tupla.

    4. Una tupla di cento elementi: i numeri interi da 0 a 99.

      Hint: posso usare (indirettamente) range()?

    5. Una tupla con due elementi: la lista degli interi da 0 a 49 e la lista degli interi da 50 a 99.

    6. Una tupla con due elementi: la tupla degli interi da 0 a 49 e la tupla degli interi da 50 a 99.

  2. Date l = [0, 1, 2] e t = (0, 1, 2), che differenza c’e’ tra:

    1. Primo caso:

      x = l
      x[0] = 100
      
    2. Secondo caso:

      x = t
      x[0] = 100
      
  3. Data la tupla:

    tupla = (0, 1, 2, [3, 4, 5], 6, 7, 8)
    
    1. Di che tipo e’ il primo elemento della tupla? Quanto vale?
    2. Di che tipo e’ il quarto elemento della tupla? Quanto vale?
    3. Quanti elementi contiene la tupla?
    4. Quanti elementi contiene la lista contenuta nella tupla?
    5. Cambiare l’ultimo elemento della lista contenuta in tupla in "ultimo".
    6. Cambiare l’ultimo elemento di tupla in "ultimo". (Si puo’ fare?)

Python: Tuple (Soluzioni)

  1. Soluzioni:

    coppia_di_interi = (0, 1)
    print(type(coppia_di_interi))            # tuple
    
    
    coppia_di_stringhe = ("una", "tupla")
    print(type(coppia_di_stringhe))          # tuple
    
    
    un_solo_elemento = (0,)
    print(type(un_solo_elemento))            # tuple
    print(len(un_solo_elemento))             # 1
    
    un_solo_elemento_alt = tuple([0])
    print(type(un_solo_elemento_alt))        # tuple
    print(len(un_solo_elemento_alt))         # 1
    
    sbagliato = (0)
    print(type(sbagliato))                   # int
    print(len(sbagliato))                    # errore!
    
    
    cento_elementi = tuple(range(100))
    print(type(cento_elementi))              # tuple
    
    
    coppia_di_liste = (range(50), range(50, 100))
    print(type(coppia_di_liste))
    print(type(coppia_di_liste[0]))
    
    
    coppia_di_tuple = (tuple(range(50)), tuple(range(50, 100)))
    print(type(coppia_di_tuple))
    print(type(coppia_di_tuple[0]))
    
  2. Soluzioni:

    l = [0, 1, 2]
    t = (0, 1, 2)
    
    # x si riferisce ad una lista, il codice sostituisce
    # il primo elemento con 100
    x = l
    x[0] = 100
    
    # x ora si riferisce ad una tupla, che e' immutabile:
    # non posso sostituire i suoi elementi, Python da'
    # errore
    x = t
    x[0] = 100                          # errore!
    
  3. Soluzioni:

    tupla = (0, 1, 2, [3, 4, 5], 6, 7, 8)
    
    print(tupla[0])                      # 0
    print(type(tupla[0]))                # int
    
    print(tupla[3])                      # [3, 4, 5]
    print(type(tupla[3]))                # list
    
    print(len(tupla))                    # 9
    
    print(len(tupla[3]))                 # 3
    
    tupla[3][-1] = "ultimo"
    print(tupla)
    # ebbene lo posso fare! ho "modificato" la
    # tupla modificando la lista contenuta
    # in essa.
    
    tupla[-1] = "ultimo"                # errore!
    # non posso modificare la tupla "direttamente"
    # e' un oggetto immutabile
    


Python: Dizionari

I dizionari rappresentano mappe tra oggetti: mappano una chiave nel valore corrispondente.

Warning

I dizionari sono mutabili!

Per definire un dizionario scrivo:

codice = {
    "UUU": "F",     # fenilalanina
    "UCU": "S",     # serina
    "UAU": "Y",     # tirosina
    "UGU": "C",     # cisteina
    "UUC": "F",     # fenilalanina
    "UCC": "S",     # serina
    "UAC": "Y",     # tirosina
    # ...
}

Qui codice implementa il codice genetico, che mappa da stringhe di tre lettere (le chiavi) al nome dell’aminoacido corrispondente (i valori).

La sintassi e’:

{ chiave1: valore1, chiave2: valore2, ...}

Uso un dizionario in questo modo:

aa_di_uuu = codice["UUU"]
print(aa_di_uuu)             # "phenylalanine"

aa_di_ucu = codice["UCU"]
print(aa_di_ucu)             # "serine"

Posso usare questo dizionario per “simulare” il processo della traduzione. Ad esempio, partendo da una sequenza di mRNA:

rna = "UUUUCUUAUUGUUUCUCC"

la spezzo in triplette:

triplette = [rna[i:i+3] for i in range(0, len(rna), 3)]

ottenendo:

>>> print(triplette)
['UUU', 'UCU', 'UAU', 'UGU', 'UUC', 'UCC']

ed a questo punto posso fare:

proteina = "".join([codice[tripletta] for tripletta in triplette])

ottenendo:

>>> print(proteina)
"FSYCFS"

La differenza piu’ notevole rispetto alla vera traduzione e’ che il nostro codice non rispetta i codoni di terminazione, ma e’ un difetto correggibile.

Warning

Le chiavi non possono essere ripetute, i valori si’!

Nell’esempio sopra, ogni chiave e’ una tripletta ed e’ associata ad un unico valore, ma lo stesso valore puo’ essere associato a piu’ chiavi:

print(codice["UCU"])            # "S", serina
print(codice["UCC"])            # "S", serina

Esempio. Creo un dizionario che mappa ogni aminoacido nel suo volume (approssimato, in Amstrong cubici):

volume_di = {
    "A":  67.0, "C":  86.0, "D":  91.0,
    "E": 109.0, "F": 135.0, "G":  48.0,
    "H": 118.0, "I": 124.0, "K": 135.0,
    "L": 124.0, "M": 124.0, "N":  96.0,
    "P":  90.0, "Q": 114.0, "R": 148.0,
    "S":  73.0, "T":  93.0, "V": 105.0,
    "W": 163.0, "Y": 141.0,
}

# Stampo il volume della citosina
print(volume_di["C"])                    # 86.0
print(type(volume_di["C"]))              # float

Qui le chiavi sono di tipo str (immutabili) ed i valori sono dei float (sempre immutabili).


Warning

Non ci sono restrizioni sul tipo degli oggetti che si possono usare come valori. Come chiavi invece si possono usare solo oggetti immutabili!

Riassumo in che modo possono essere usati i vari tipi:

Tipo Come chiavi Come valori
bool
int
float
str
list NO
tuple
dict NO
set NO

Si veda l’esempio seguente.

Esempio. Creo un dizionario che mappa da ogni aminoacido ad una lista di due proprieta’: massa e volume:

proprieta_di = {
    "A": [ 89.09,  67.0],
    "C": [121.15,  86.0],
    "D": [133.10,  91.0],
    "E": [147.13, 109.0],
    # ...
}

# Stampo massa e volume dell'alanina
print(proprieta_di["A"])                 # [89.09, 67.0]
print(type(proprieta_di["A"]))           # list

Qui le chiavi sono str (immutabili) ed i valori sono list (mutabili).

Provo a creare il dizionario inverso (limitandomi al primo elemento per semplicita’):

da_proprieta_ad_aa = { [89.09, 67.0]: "A" }

Mi aspetto che questo dizionario mappi dalla lista:

[89.09, 67.0]

ad "A". Pero’ quando provo a crearlo Python da’ errore:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
#          ^^^^^^^^^^^^^^^^^^^^^^^
#             list e' mutabile!

Questo succede perche’ le chiavi hanno tipo list, che non e’ immutabile!

Per risolvere il problema posso usare, al posto di liste, delle tuple:

da_proprieta_ad_aa = {
    ( 89.09,  67.0): "A",
    (121.15,  86.0): "C",
    (133.10,  91.0): "D",
    (147.13, 109.0): "E",
    # ...
}

aa = da_proprieta_ad_aa[(133.10,  91.0)]
print(aa)                                # "D"
print(type(aa))                          # str

Ora le chiavi sono tuple, immutabili, e va tutto bene.


Operazioni

Ritorna Operatore Significato
int len(dict) Restituisce il numero di coppie chiave-valore
object dict[object] Restituisce il valore associato ad una chiave
dict[object]=object Inserisce o sostituisce una coppia chiave-valore

Esempio. Partendo dal dizionario vuoto:

codice = {}

print(codice)                        # {}
print(len(codice))                   # 0

ricreo il dizionario del primo esempio inserendo a mano tutte le coppie chiave-valore:

codice["UUU"] = "F"                 # fenilalanina
codice["UCU"] = "M"                 # metionina
codice["UAU"] = "Y"                 # tirosina
# ...

print(codice)
print(len(codice))

Mi accorgo di avere fatto un errore qui sopra: "UCU" dovrebbe corrispondere a "S" (serina), non a "M" (metionina). Come faccio a correggere l’errore?

Risposta: sostituendo il valore corrispondente alla chiave "UCU" con quello corretto:

codice["UCU"] = "S"                 # serina

Qui alla chiave "UCU", che gia’ era nel dizionario, associo un nuovo valore "S" (serina).


Warning

Se provo ad ottenere il valore di una chiave non presente nel dizionario:

>>> codice[":-("]

Python da’ errore:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: ":-("
#         ^^^^^
#          questa e' la chiave che non esiste

Metodi

Ritorna Metodo Significato
object dict.get(k, [default]) Resituisce il valore della chiave, oppure il default
bool object in dict True se la chiave e’ nel dizionario
dict_keys dict.keys() Restituisce una vista sulle chiavi
dict_values dict.values() Restituisce una vista sui valori
dict_items dict.items() Restituisce una vista sulle coppie chiave-valore

Esempio. Partendo dal dizionario codice definito nel primo esercizio:

codice = {
    "UUU": "F",     # fenilalanina
    "UCU": "S",     # serina
    "UAU": "Y",     # tirosina
    "UGU": "C",     # cisteina
    "UUC": "F",     # fenilalanina
    "UCC": "S",     # serina
    "UAC": "Y",     # tirosina
    # ...
}

posso ottenere le chiavi cosi’:

>>> chiavi = codice.keys()
>>> print(chiavi)
dict_keys(["UUU", "UCU", "UAU", ...])

ed i valori cosi’:

>>> valori = codice.values()
>>> print(valori)
dict_values(["F", "S", Y", "C", ...])

oppure sia chiavi che valori cosi’:

>>> coppie_chiave_valore = codice.items()
>>> print(coppie_chiave_valore)
dict_items([("UUU", "F"), ("UCU", "S"), ...])

Infine posso controllare se una certa chiave sta nel dizionario:

>>> print("UUU" in codice)
True
>>> print(":-(" in codice)
False

Warning

Non c’e’ alcuna garanzia che un dizionario preservi l’ordine in cui vengono inseriti gli elementi.

Ad esempio:

>>> d = {}
>>> d["z"] = "zeta"
>>> d["a"] = "a"
>>> d
{'a': 'a', 'z': 'zeta'}
>>> list(d.keys())
['a', 'z']
>>> list(d.values())
['a', 'zeta']
>>> list(d.items())
[('a', 'a'), ('z', 'zeta')]

Qui ho inserito prima "z" e poi "a", ma quando estraggo dal dizionario d le chiavi, i valori e le coppie chiave-valore, l’ordine in cui mi restituisce "z" ed "a" e’ invertito!


Esempio. Posso usare un dizionario per rappresentare un oggetto strutturato, ad esempio le proprieta’ di una catena proteica:

chain = {
    "name": "1A3A",
    "chain": "B",
    "sequence": "MANLFKLGAENIFLGRKAATK...",
    "num_scop_domains": 4,
    "num_pfam_domains": 1,
}

print(chain["name"])
print(chain["sequence"])
print(chain["num_scop_domains"])

Scrivere a mano dizionari come questo e’ sconveniente, ma quando leggeremo informazioni da file (possibilmente da interi database biologici), avere sotto mano dizionari fatti cosi’ puo’ essere molto comodo.

(Soprattutto se sopra ci costruiamo algoritmi per analizzare in modo automatico i dati!)


Esempio. Dato il sequente testo in FASTA (accorciato per motivi di spazio) che descrive la sequenza primaria della retrotranscriptasi del virus HIV-1:

>2HMI:A|PDBID|CHAIN|SEQUENCE
PISPIETVPVKLKPGMDGPKVKQWPLTEEKIKALVEICTEMEKEGKISKI
NKRTQDFWEVQLGIPHPAGLKKKKSVTVLDVGDAYFSVPLDEDFRKYTAF
QSSMTKILEPFKKQNPDIVIYQYMDDLYVGSDLEIGQHRTKIEELRQHLL
VQPIVLPEKDSWTVNDIQKLVGKLNWASQIYPGIKVRQLSKLLRGTKALT
PSKDLIAEIQKQGQGQWTYQIYQEPFKNLKTGKYARMRGAHTNDVKQLTE
WWTEYWQATWIPEWEFVNTPPLVKLWYQLEKEPIVGAETFYVDGAANRET
AIYLALQDSGLEVNIVTDSQYALGIIQAQPDKSESELVNQIIEQLIKKEK
>2HMI:B|PDBID|CHAIN|SEQUENCE
PISPIETVPVKLKPGMDGPKVKQWPLTEEKIKALVEICTEMEKEGKISKI
NKRTQDFWEVQLGIPHPAGLKKKKSVTVLDVGDAYFSVPLDEDFRKYTAF
QSSMTKILEPFKKQNPDIVIYQYMDDLYVGSDLEIGQHRTKIEELRQHLL
VQPIVLPEKDSWTVNDIQKLVGKLNWASQIYPGIKVRQLSKLLRGTKALT
PSKDLIAEIQKQGQGQWTYQIYQEPFKNLKTGKYARMRGAHTNDVKQLTE
WWTEYWQATWIPEWEFVNTPPLVKLWYQLE
>2HMI:C|PDBID|CHAIN|SEQUENCE
DIQMTQTTSSLSASLGDRVTISCSASQDISSYLNWYQQKPEGTVKLLIYY
EDFATYYCQQYSKFPWTFGGGTKLEIKRADAAPTVSIFPPSSEQLTSGGA
NSWTDQDSKDSTYSMSSTLTLTADEYEAANSYTCAATHKTSTSPIVKSFN
>2HMI:D|PDBID|CHAIN|SEQUENCE
QITLKESGPGIVQPSQPFRLTCTFSGFSLSTSGIGVTWIRQPSGKGLEWL
FLNMMTVETADTAIYYCAQSAITSVTDSAMDHWGQGTSVTVSSAATTPPS
TVTWNSGSLSSGVHTFPAVLQSDLYTLSSSVTVPSSTWPSETVTCNVAHP
>2HMI:E|PDBID|CHAIN|SEQUENCE
ATGGCGCCCGAACAGGGAC
>2HMI:F|PDBID|CHAIN|SEQUENCE
GTCCCTGTTCGGGCGCCA

Le sequenza e’ presa dalla Protein Data Bank, struttura 2HMI.

Posso immaginare di mettere questa informazione in un dizionario:

sequenze_2HMI = {
    "A": "PISPIETVPVKLKPGMDGPKVKQWPLTEEKI...",
    "B": "PISPIETVPVKLKPGMDGPKVKQWPLTEEKI...",
    "C": "DIQMTQTTSSLSASLGDRVTISCSASQDISS...",
    "D": "QITLKESGPGIVQPSQPFRLTCTFSGFSLST...",
    "E": "ATGGCGCCCGAACAGGGAC",
    "F": "GTCCCTGTTCGGGCGCCA",
}

Dato questo dizionario posso estrarre facilmente la sequenza di ogni singola catena della struttura 2HMI:

>>> print(sequenze_2HMI["F"])
"GTCCCTGTTCGGGCGCCA"

calcolare di quante catene e’ composta la struttura:

num_catene = len(sequenze_2HMI)

calcolare statistiche sulla co-occorrenza di aminoacidi, provare a predire le strutture secondarie, allineare le sequenze contro qualche database…


Esempio. I dizionari sono utili anche per descrivere istogrammi. Ad esempio, supponiamo di avere una sequenza:

seq = "GTCCCTGTTCGGGCGCCA"

Calcolo le proporzioni dei vari nucleotidi:

num_A = seq.count("A")                          # 1
num_T = seq.count("T")                          # 4
num_C = seq.count("C")                          # 7
num_G = seq.count("G")                          # 6

Voglio catturare questa informazione statistica in un istogramma. Lo implemento cosi’:

istogramma = {
    "A": num_A / len(seq),               # 1 / 18 ~ 0.06
    "T": num_T / len(seq),               # 4 / 18 ~ 0.22
    "C": num_C / len(seq),               # 7 / 18 ~ 0.38
    "G": num_G / len(seq),               # 6 / 18 ~ 0.33
}

A questo punto posso recuperare la proporzione dei vari nucelotidi dall’istogramma:

prop_A = istogramma["A"]
print(prop)

Posso anche verificare che l’istogramma definisca una distribuzione di probabilita’ (approssimata), cioe’ che la somma delle proporzioni dia 1:

print(istogramma["A"] + istogramma["C"] + ...)

Esempio. Possiamo codificare una rete (in questo esempio fittizia) di interazioni fisiche o funzionali tra proteine cosi’:

partners_di = {
    "2JWD": ("1A3A",),
    "1A3A": ("2JWD", "2ZTI", "3BLU"),
    "2ZTI": ("1A3A", "3BLF"),
    "3BLU": ("1A3A", "3BLF"),
    "3BLF": ("3BLU", "2ZTI"),
}

che rappresenta la rete:

2JWD ---------- 1A3A ---------- 2ZTI
                 |               |
                 |               |
                3BLU ---------- 3BLF

Qui partners_di["1A3A"], ad esempio, e’ una tupla dove abbiamo messo tutti i binding partners della proteina 1A3A.

Possiamo usare questo dizionario per trovare tutti i binding partners dei binding partners di 1A3A, e oltre:

# Estraggo i partner di 1A3A
partners = partners_di["1A3A"]

# Estraggo i partner dei partner di 1A3A
partners_2_step = partners_di[partners[0]] + \
                  partners_di[partners[1]] + \
                  ...
                  partners_di[partners[-1]]

# Estraggo i parner dei partner dei partner di 1A3A
partners_3_step = partners_di[partners_2_step[0]] + \
                  partners_di[partners_2_step[1]] + \
                  ...
                  partners_di[partners_2_step[-1]]

La stessa struttura si puo’ usare per codificare reti sociali (Facebook, Twitter, Google+, etc.) e trovare chi e’ amico (o segue) chi, o per individuare comunita’ di persone che si conoscono tra loro.

Note

Ci sono molti altri modi di codificare reti. Un’alternativa al dizionario di cui sopra, e’ la matrice di adiacenza, che si puo’ implementare come una lista di liste.


Esercizi

  1. Creare a mano:

    1. Un dizionario vuoto. Controllare che sia vuoto con len().

    2. Un dizionario pronomi che rappresenta queste corrispondenze:

      1 -> "io"
      2 -> "tu"
      3 -> "egli"
      ...
      

      Crearlo in due modi:

      1. In una sola riga di codice.
      2. Partendo da un dizionario vuoto ed aggiungendo passo passo tutte le coppie chiave valore.
    3. Un dizionario decuplo_di che implementa la funzione f(n) = 10 n, che associa ogni chiave (un intero) al suo decuplo.

      Le chiavi devono essere gli interi da 1 a 5.

      Una volta costruito il dizionario, applicarlo a tutti gli elementi di range(2, 5) con una list comprehension e stampare a schermo il risultato.

      Poi fare la stessa cosa, pero’ per tutte le chiavi di decuplo_di.

      Hint: la vista sulle chiavi si puo’ ottenere con keys().

    4. Un dizionario info_2TMV che codifica informazioni strutturate sulla struttura PDB 2TMV della capside del Tobacco Mosaic Virus.

      1. Alla chiave "pdb_id" associate il valore "2TMV".
      2. Alla chiave "uniprot_id" associate il valore "P69687 (CAPSD_TMV)".
      3. Alla chiave "numero_domini_scp" associate 1.
      4. Alla chiave "numero_domini_pfam" associate 1.
    5. Un dizionario parenti_di che rappresenta questa rete:

      GIULIA ---- FRANCO ---- MATTEO
        |                       |
        + ----- BENEDETTA ------+
      

      come nell’esempio visto in precedenza.

      Una volta costruito il dizionario, stampare a schermo il numero di parenti di "GIULIA".

    6. Un dizionario da_2_bit_a_intero che rappresenta la mappa dalle seguenti coppie di interi ad intero:

      0, 0 -> 0                           # 0*2**1 + 0*2**0 = 0
      0, 1 -> 1                           # 0*2**1 + 1*2**0 = 1
      1, 0 -> 2                           # 1*2**1 + 0*2**0 = 2
      1, 1 -> 3                           # 1*2**1 + 1*2**1 = 3
      ^^^^    ^                             ^^^^^^^^^^^^^^^^^^^
      chiave  valore                            spiegazione
      

      Una volta creato il dizionario, stampare a schermo il valore corrispondente ad una delle quattro chiavi date (a scelta).

  2. Dato:

    rapporti = {
        ("A", "T"): 10.0 / 5.0,
        ("A", "C"): 10.0 / 7.0,
        ("A", "G"): 10.0 / 6.0,
        ("T", "C"): 5.0 / 7.0,
        ("T", "G"): 5.0 / 6.0,
        ("C", "G"): 7.0 / 6.0,
    }
    

    che rappresenta rapporti tra il numero di A, T, C, e G in una sequenza:

    1. Che differenza c’e’ tra len(rapporti), len(rapporti.keys()), len(rapporti.values()) e len(rapporti.items())?

    2. Controllare se rapporti contiene la chiave ("T", "A"). E la chiave ("C", "G")?

      Hint: posso usare keys()? Posso usare un altro metodo?

    3. Controllare se contiene il valore 2. E il valore 3?

      Hint: posso usare values()?

    4. Controllare se contiene la coppia chiave-valore (("A", "T"), 2). E la coppia chiave-valore (("C", "G"), 1000)?

      Hint: posso usare items()?

    5. Usare una list comprehension per estrarre le chiavi dal risultato di items(). Poi fare la stessa cosa con le chiavi.

  3. Dato:

    mappa = {
        "zero": 1,
        "uno": 2,
        "due": 4,
        "tre": 8,
        "quattro": 16,
        "cinque": 32,
    }
    
    1. Concatenare tutte le chiavi di mappa, separate da spazi, in una sola stringa stringa_delle_chiavi.

    2. Concatenare tutti i valori di mappa come stringhe, separate da spazi, in una sola stringa stringa_dei_valori.

      Hint: occhio che i valori di mappa non sono stringhe!

    3. Mettere in una lista tutte le chiavi di mappa.

    4. Mettere in una lista tutte le chiavi di mappa, ordinate alfanumericamente.

      Hint: la vista restituita da keys() e’ ordinata?

    5. Mettere in una lista tutti i valori di mappa, ordinati in base all’ordine delle chiavi corrispondenti.

      Hint: come faccio ad ordinare una lista in base all’ordine di un’altra lista?

  4. Dato:

    traduzione_di = {"a": "ade", "c": "cyt", "g": "gua", "t": "tym"}
    

    tradurre la lista:

    lista = ["A", "T", "T", "A", "G", "T", "C"]
    

    nella stringa:

    "ade tym tym ade gua tym cyt"
    

    Hint: occhio che le chiavi del dizionario sono minuscole, mentre gli elementi di lista sono maiuscoli! Partite assumendo che non lo siano, poi modificate il codice per tenere conto di questa idiosincrasia.


Python: Dizionari (Soluzioni)

  1. Soluzioni:

    1. Soluzione:

      diz_vuoto = {}
      print(diz_vuoto)
      print(len(diz_vuoto))                        # 0
      
    2. Soluzione:

      pronomi = {}
      pronomi[1] = "io"
      pronomi[2] = "tu"
      pronomi[3] = "egli"
      pronomi[4] = "noi"
      pronomi[5] = "voi"
      pronomi[6] = "essi"
      

      oppure:

      pronomi = {
          1: "io", 2: "tu", 3: "egli",
          4: "noi", 5: "voi", 6: "essi",
      }
      
    3. Soluzione:

      decuplo_di = {1: 10, 2: 20, 3: 30, 4: 40, 5: 50}
      
      print([decuplo_di[n] for n in range(2, 5)])
      
      print([decuplo_di[chiave] for chiave in decuplo_di.keys()])
      
    4. Soluzione:

      info_2TMV = {
          "pdb_id": "2TMV",
          "uniprot_id": "P69687 (CAPSD_TMV)",
          "numero_domini_scp": 1,
          "numero_domini_pfam": 1,
      }
      
    5. Soluzione:

      parenti_di = {
          "GIULIA": ["FRANCO", "BENEDETTA"],
          "FRANCO": ["GIULIA", "MATTEO"],
          "MATTEO": ["FRANCO", "BENEDETTA"],
          "BENEDETTA": ["GIULIA", "MATTEO"],
      }
      
      num_parenti_di_giulia = len(parenti_di["GIULIA"])
      print(num_parenti_di_giulia)
      
    6. Soluzione:

      da_2_bit_a_intero = {
          (0, 0): 0,
          (0, 1): 1,
          (1, 0): 2,
          (1, 1): 3,
      }
      

      Occhio che non posso usare delle liste come chiavi: le liste non sono immutabili!

      Scelgo di stampare il valore corrispondente a 1, 0:

      print(da_2_bit_a_intero[(1, 0)])
      #                       ^^^^^^
      #                        tupla
      
  2. Soluzione:

    rapporti = {
        ("A", "T"): 10.0 / 3.0,
        ("A", "C"): 10.0 / 7.0,
        ("A", "G"): 10.0 / 6.0,
        ("T", "C"): 3.0 / 7.0,
        ("T", "G"): 3.0 / 6.0,
        ("C", "G"): 7.0 / 6.0,
    }
    
    
    print(len(rapporti))                             # 6
    print(len(rapporti.keys()))                      # 6
    print(len(rapporti.values()))                    # 6
    print(len(rapporti.items()))                     # 6
    # tutti contano il numero di coppie chiave-valore!
    
    
    # stampo le chiavi del dizionario per farmi un'idea
    print(rapporti.keys())                           # e' una vista su tuple!
    print(type(rapporti.keys()))                     # dict_keys
    print(type(rapporti.keys()[0]))                  # tuple
    
    contiene_T_A = ("T", "A") in rapporti.keys()
    print(contiene_T_A)                              # False
    
    contiene_C_G = ("C", "G") in rapporti.keys()
    print(contiene_C_G)                              # True
    
    # posso fare la stessa cosa direttamente con l'oggetto dizionario:
    print(("T", "A") in rapporti)                    # False
    print(("C", "G") in rapporti)                    # True
    
    
    # stampo i valori del dizionario per farmi un'idea
    print(rapporti.values())                         # e' una vista su interi!
    print(type(rapporti.values()[0]))                # int
    
    contiene_2 = 2 in rapporti.values()
    print(contiene_2)                                # True
    
    contiene_3 = 3 in rapporti.values()
    print(contiene_3)                                # False
    
    
    # stampo le coppie chiave-valore per farmi un'idea
    print(rapporti.items())
    # e' una vista su coppie (tuple): il primo elemento, la chiave, e'
    # una coppia esso stesso, il secondo e' un intero
    
    print((("A", "T"), 2) in rapporti.items())       # True
    print((("C", "G"), 1000) in rapport.items())     # False
    
    
    # le list comprehension sono
    chiavi = [chiave_valore[0]
              for chiave_valore in rapporti.items()]
    valori = [chiave_valore[-1]
              for chiave_valore in rapporti.items()]
    
  3. Soluzione:

    mappa = {
        "zero": 1,
        "uno": 2,
        "due": 4,
        "tre": 8,
        "quattro": 16,
        "cinque": 32,
    }
    
    
    # le chiavi di mappa sono tutte stringhe, quindi keys() mi
    # restituisce una vista su stringhe: posso usare
    # direttamente join()
    stringa_delle_chiavi = " ".join(mappa.keys())
    
    
    # i valori di mappa sono interi, quindi non posso usare
    # join() direttamente: devo prima trasformare tutti i
    # valori da interi a stringhe
    stringa_dei_valori = " ".join(str(valore)
                                  for valore in mappa.values())
    
    
    vista_sulle_chiavi = mappa.keys()
    print(vista_sulle_chiavi)                        # non e' ordinata
    
    
    lista_ordinata_delle_chiavi = list(mappa.keys())
    lista_ordinata_delle_chiavi.sort()
    print(lista_ordinata_delle_chiavi)               # ora e' ordinata
    
    
    lista_dei_valori_ordinati_per_chiavi = \
        [mappa[chiave] for chiave in lista_ordinata_delle_chiavi]
    
  4. Soluzione:

    # usando una list comprehension posso applicare il dizionario
    # alla lista: qui e' necessario usare lower() *prima* di
    # usare un aminoacido come chiave di traduzione_di!
    traduzione = [traduzione_di[aa.lower()] for aa in lista]
    print(traduzione)
    
    # a questo punto posso usare join() per concatenare le varie
    # parti
    risultato = " ".join(traduzione)
    print(risultato)
    

    oppure, in un solo comando:

    print(" ".join([traduzione_di[aa.lower()] for aa in lista]))
    


Python: Sets

I set (insiemi) sono una collezione non ordinata di elementi senza ripetizioni.

Per definire un insieme, posso scrivere:

sette_nani = {"Brontolo", "Pisolo", "Dotto", ...}

I set possono essere visti come dizionari di sole chiavi.

La sintassi e’:

{ valore1, valore2, ...}

Posso creare un set vuoto utilizzando la funzione set:

empty_set = set()
print(type(empty_set))            # set
print(len(empty_set))             # 0

empty_set2 = {}
print(type(empty_set2))           # dict

La stessa funzione puo’ essere utilizzata per creare un set a partire da una lista:

set_numeri = set([1, 2, 3])
print(set_numeri)                 # {1, 2, 3}
I set possono essere utilizzati per rimuovere le ripetizioni da una lista::
lista = [0, 1, 0, 0, 2, 0] insieme = set(lista) print(insieme) # {0, 1, 2}

Operazioni

Ritorna Operatore Significato
int len(set) Restituisce il numero di elementi nel set
bool object in set Controlla se l’elemento e’ presente nel set
None set.add(object) Inserisce un elemento
None set.update(list) Inserisce una lista di elementi
None set.remove(object) Rimuove un elemento (errore se non presente)
None set.discard(object) Rimuove un elemento

Esempio. Partendo da un set vuoto:

numeri_fortunati = set()

print(numeri_fortunati)              # set()
print(len(numeri_fortunati))         # 0

inserisco i primi cinque numeri naturali:

primi_cinque = range(5)
numeri_fortunati.update(primi_cinque)
print(len(numeri_fortunati))         # 5

inserisco i numeri pari tra 0 e 10 (incluso):

pari = [x for x in range(11) if x % 2 == 0]
numeri_fortunati.update(pari)
print(len(numeri_fortunati))         # 8

provo a rimuovere "0":

numeri_fortunati.discard("0")        # non fa nulla
numeri_fortunati.remove("0")         # errore

aggiungo la stringa "0" al set e controllo che sia presente:

numeri_fortunati.add("0")
"0" in numeri_fortunati              # True

Warning

Come per i dizionari, c’e’ alcuna garanzia che un set preservi l’ordine in cui vengono inseriti gli elementi.

Warning

I set sono oggetti mutabili, come nel caso delle liste conviene prestare attenzione per evitare situazioni spiacevoli, ad esempio:

pasto_completo = {'antipasto', 'primo', 'secondo', 'dolce', "caffe'"}

pasto_ridotto = pasto_completo
pasto_ridotto.remove('antipasto', 'dolce')

print(pasto_completo)
{'primo', 'secondo', "caffe'"}    # Doh!

Per creare una copia di un set:

L = {1, 2, 3}
copia_di_L = set(L)
copia_di_L.remove(2)
print(copia_di_L)                 # {1, 3}
print(L)                          # {1, 2, 3}

Operazioni

Ritorna Operatore Significato
set set.union(set) Restituisce l’unione di due set
set set.intersection(set) Restituisce l’intersezione tra due set
set set.difference(set) Restituisce la differenza tra due set

Esempio. Creo il set dei primi dieci numeri naturali:

set_A = set(range(10))

Creo il set dei multipli di 3 compresi tra 0 e 20:

set_B = set([x for x in range(20) if x % 3 == 0])

Creo l’unione di set_A e set_B:

unione = set_A.union(set_B)
print(unione)                             # {0, 1, 2, 3, .., 8, 9, 12, 15, 18}
_images/sets_union.png

Ora creo l’intersezione di set_A e set_B:

intersezione = set_A.intersection(set_B)
print(intersezione)                       # {0, 3, 6, 9}
_images/sets_intersection.png

Nota: le operazioni di unione e intersezione sono simmetriche, per qualsiasi set_A e set_B vale:

set_A.union(set_B) == set_B.union(set_A)                 # True
set_A.intersection(set_B) == set_B.intersection(set_A)   # True

Ottengo i numeri naturali tra 0 e 9 che NON sono multipli di 3:

diff1 = set_A.difference(set_B)
print(diff1)                              # {1, 2, 4, 5, 7, 8}
_images/sets_difference.png

Analogamente, ottengo i multipli di 3 fino al 18 che NON sono compresi tra 0 e 9:

diff2 = set_B.difference(set_A)
print(diff2)                              # {12, 15, 18}

Nota al contrario di unione ed intersezione, la differenza non e’ simmetrica!

Esercizi

  1. Creare:

    1. Un set vuoto set_vuoto. Controllare che sia vuoto con len().
    2. Un set primi10 contenente i primi 10 numeri naturali. Controllare se contiene 10, in caso contrario, inserirlo e ricontrollare che sia presente. Rimuoverlo nuovamente.
    3. Un set primi10no7 contenente i primi 10 numeri naturali, tranne 7 (partendo da primi10 ma lasciandolo inalterato). Controllo che 7 sia presente in primi10 e assente in primi10no7.
    4. Ricreare primi10no7, questa volta utilizzando una list comprehension.
  2. Per ogni testo, creare l’insieme delle parole che contiene (maiuscole/minuscole non contano):

    testo_A = "Le due ore di informatica piu' noiose della vostra vita"
    testo_B = "La vita e' come una scatola di cioccolatini"
    testo_C = "Cioccolatini come se piovesse LA La lA laaa"
    
    1. Contare il numero di parole diverse per ognuno dei tre testi.
    2. Ottenere gli insiemi delle parole in comune tra i tre testi: condivise_A_B, condivise_A_C, condivise_B_C.
    3. Dati i set creati precedentemente, ottenere l’insieme delle parole che compaiono in almeno due testi, utilizzando soltanto operazioni tra set.
    4. Ottenere l’insieme delle parole che compaiono esattamente in un testo. Hint posso farlo utililizzando il risultato precedente?
    5. Ottenere l’insieme delle parole che compaiono ripetute nello stesso testo.

Python: Sets (Soluzioni)

  1. Soluzioni:

    1. Soluzione:

      set_vuoto = {}
      print(set_vuoto)
      print(len(set_vuoto))                        # 0
      
    2. Soluzione:

      primi10 = set(range(10))
      print(10 in primi10)                         # False
      primi10.add(10)
      print(10 in primi10)                         # True
      primi10.remove(10)
      
    3. Soluzione, provo cosi’:

      primi10no7 = primi10
      primi10no7.remove(7)
      print(7 in primi10)                          # False
      

      Ricordo che i set sono strutture mutabili:

      primi10 = set(range(10))                     # Ricreo il set originale
      primi10no7 = set(primi10)
      primi10no7.remove(7)
      print(7 in primi10)                          # True
      print(7 in primi10no7)                       # False
      
    4. Soluzione:

      primi10no7 = set([x in range(10) if x != 7])
      print(primi10no7)                            # Controllo
      
  2. Soluzione:

    Convertendo i testi in minuscolo:

    tA_lower = testo_A.lower()
    tB_lower = testo_B.lower()
    tC_lower = testo_C.lower()
    

    Creo gli insiemi delle parole contenute nei testi:

    parole_in_A = set(tA_lower.split())
    parole_in_B = set(tB_lower.split())
    parole_in_C = set(tC_lower.split())
    
    1. Conto per ogni testo il numero di parole diverse:

      len(parole_in_A)                             # 10
      len(parole_in_B)                             # 8
      len(parole_in_C)                             # 6
      
    2. Ottengo le parole in comune utilizzando l’intersezione:

      condivise_A_B = parole_in_A.intersection(parole_in_B)
      condivise_A_C = parole_in_A.intersection(parole_in_C)
      condivise_B_C = parole_in_B.intersection(parole_in_C)
      

      Controllo:

      print(condivise_A_B)           # {'vita', 'di'}
      print(condivise_A_C)           # set()
      print(condivise_B_C)           # {'la', 'cioccolatini', 'come'}
      
    3. Soluzione:

      almeno_in_2 = condivise_A_B.union(condivise_A_C).union(condivise_B_C)
      print(almeno_in_2)             # {'vita', 'di', 'la', 'cioccolatini', 'come'}
      
    4. Creo l’insieme di tutte le parole contenute nei tre testi:

      tutte_le_parole = parole_in_A.union(parole_in_B).union(parole_in_C)
      

      Ottengo le parole che appaiono esattamente in **UN* testo:

      solo_in_uno = tutte_le_parole.difference(almeno_in_2)
      print(solo_in_uno)
      {'le', 'una', 'se', 'vostra', 'della', 'laaa', "piu'", 'ore', "e'", \
      'piovesse', 'scatola', 'noiose', 'informatica', 'due'}
      
    5. Ci sono diversi modi per farlo. Una soluzione (consideriamo solo testo_A per brevita’):

      ripetute_in_A = set([p for p in parole_in_A \
                           if tA_lower.count(p) > 1])
      


Python: Input-Output

Interfaccia Utente

Ritorna Operatore Significato
str input([str]) Fa una domanda all’utente e restituisce la risposta

Esempio. Faccio una domanda all’utente:

risposta = input("scrivi tre parole separate da spazi: ")

print(risposta)
print(type(risposta))

Ho messo nella variabile risposta la risposta dell’utente (che e’ sempre una stringa). Il contenuto di risposta dipende dall’utente, che potrebbe aver dato una risposta qualunque.

Voglio controllare che abbia effettivamente risposto con tre parole separate da spazi:

parti = parole.split()

print("hai scritto", len(parti), "parole")

reazione_a = {True: "bravo!", False: "impegnati di piu'"}
print(reazione_a[len(parti) == 3])

Esempio. Faccio una domanda all’utente:

risposta = input("scrivi due numeri: ")

print(risposta)
print(type(risposta))

Voglio stampare la somma dei numeri dati dall’utente. Prima estraggo le varie parole, poi le converto in float, poi eseguo la somma e la stampo:

parti = risposta.split()

numero_1 = float(parti[0])
numero_2 = float(parti[1])

print("la loro somma e'", numero_1 + numero_2)

Esercizi

  1. Usando input(), chiedere all’utente il suo piatto preferito, mettere il risultato in una variabile cibo, poi stampare a schermo: anche a me piace ed il piatto preferito.

  2. Chiedere all’utente due interi, diciamo a e b, poi un terzo intero, diciamo risultato.

    Controllare se la somma di a e b e’ uguale a risultato: in tale caso stampare a schermo True, altrimenti False.

  3. Chiedere all’utente una chiave ed un valore, e costruire un dizionario che include (solo) quella coppia chiave-valore. Stampare a schermo il risultato.

  4. Chiedere all’utente il suo nome, metterlo nella variabile nome, poi stampare a schermo il nome assicurandosi che le prime lettere di tutte le parole del nome siano maiuscole e le altre minuscole.

    Infine stampare a schermo il risultato.

Interfaccia Filesystem

Ritorna Operatore Significato
file open(str, [str]) Restituisce un riferimento ad un file
str file.read() Legge i contenuti del file
str file.readline() Legge una riga dal file
list-of-str file.readlines() Legge tutte le righe del file
int file.write(str) Scrive una stringa nel file
file.close() Chiude il riferimento ad un file

Ci sono diverse modalita’ di accesso usabili con open():

  1. "r": modalita’ di sola lettura (non posso scrivere nel file). Questo e’ il default.
  2. "w": modalita’ di sola scrittura (non posso leggere dal file), sovrascrivendo.
  3. "a": modalita’ di sola scrittura (non posso leggere dal file), appendendo.
  4. "+": modificatore per update (lettura/scrittura).

Esempio. Apro un file in modalita’ di lettura:

un_file = open("data/aatable", "r")

print(type(un_file))                 # file

Il primo argomento e’ il percorso al file che voglio aprire (in questo caso un percorso relativo alla directory da cui ho fatto partire python), il secondo "r"" specifica in che modalita’ aprire il file.

Warning

Non potete usare directory speciali, ad esempio "~" (la vostra home) nel percorso che date a open().


Esempio. Una volta aperto un file:

un_file = open("data/aatable", "r")

leggo i contenuti o per intero, in un’unica stringa:

contenuti = un_file.read()
print(contenuti)
print(type(contenuti))

oppure per intero ma spezzati in una lista di righe:

righe = un_file.readlines()
print(righe)
print(type(righe))

oppure una sola riga alla volta, dalla prima in giu’:

riga_1 = un_file.readline()
riga_2 = un_file.readline()
riga_3 = un_file.readline()

print(riga_1)
print(riga_2)
print(riga_3)

Warning

Una volta aperto un file, non posso leggere due volte la stessa riga.

Questo vuol dire che una volta che ho letto tutto il file, ad esempio usando readlines() (che legge tutto il contenuto), se uso una seconda volta readlines() o qualunque altro metodo di lettura, questa mi restituira’ una riga vuota.

Ad esempio:

un_file = open("data/aatable", "r")
# non ho ancora letto niente, quindi readlines() mi
# restituisce tutti i contenuti del file

contenuti = un_file.readlines()
print(len(contenuti))

# ora che ho letto tutti i contenuti, non resta piu' niente
# da leggere, e infatti...
contenuti_2 = un_file.readlines()
print(len(contenuti_2))

# qui contenuti_2 ha ZERO righe

Visto che il file e’ stato aperto con open() in modalita’ di sola lettura, non posso scriverci dentro:

un_file.write("del testo\n")

perche’ Python mi da’ errore:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: File not open for writing
#        ^^^^^^^^^^^^^^^^^^^^^^^^^
#         il file non e' aperto in modalita' di scrittura!

La stessa cosa accade se provo a leggere da un file aperto in modalita’ di sola scrittura (sia con "w" che con "a").


Esempio. Per aprire un file in cui mettere i risultati di un lunghissimo calcolo:

file_dei_risultati = open("risultati", "w")

# blah blah blah
# blah blah blah
# blah blah blah

file_dei_risultati.close()

Una volta finito di leggere o scrivere, chiamo close().

Warning

Per i file aperti in modalita’ di scrittura (con "w", "r+", "a", …), close() si assicura che tutto cio’ che avete detto a Python di scriverci effettivamente finisca per essere scritto nel file.

Se dimendicate di chiamare close() la scrittura nel file potrebbe non accadere.


Esempio. Chiedo all’utente il nome di un file (che puo’ esistere o meno), e poi cosa vuole scriverci:

percorso = input("dove vuoi scrivere? ")
testo = input("cosa vuoi scrivere? ")

il_file = open(percorso, "w")
il_file.write("l'utente ha scritto:\n" + testo)
il_file.close()

Ora riapro lo stesso file, ma in modalita’ di lettura, ne leggo i contenuti e li stampo a schermo:

stesso_file = open(percroso, "r")
contenuti = stesso_file.read()
stesso_file.close()

print("Nel file ", percorso, " hai scritto:")
print(contenuti)

Esercizi

Warning

Se open() vi da’ errore, puo’ darsi che vi troviate nella directory sbagliata. Aggiustate il percorso di conseguenza.

  1. Usate open() per aprire il file "data/aatable" in modalita’ di sola lettura, mettendo il risultato di open() nella variabile f.

    Poi usate readlines() per leggere il contenuto del file, mettendo il risultato in righe.

    Di che tipo e’ righe? Di che tipo e’ il primo elemento di righe? Quante righe ci sono nel file?

  2. Usate open() per aprire il file "data/aatable" in modalita’ di sola lettura, mettendo il risultato di open() nella variabile f.

    Poi usate readline() (non readlines()!) per leggere la prima riga del file, mettendo il risultato in prima_riga.

    Quante righe sono rimaste da leggere? Controllate usando readlines().

    A questo punto, quante altre righe sono rimaste da leggere?

  3. Usate open() per aprire il file "output.txt" in modalita’ di sola scrittura "w", mettendo il risultato di open() nella variabile f.

    Poi scrivete la stringa "prova prova uno due tre prova" nel file.

    Chiudete il file usando il metodo close().

    Ora invece aprite "output.txt" in modalita’ di sola lettura e stampate a schermo i suoi contenuti.

  4. usate open() per aprire il file "poesia.txt" in modalita’ di sola scrittura. Poi scrivete nel file le stringhe in questa lista, una per riga:

    versi = [
        "S'i fosse fuoco, arderei 'l mondo"
        "s'i fosse vento, lo tempestarei"
    ]
    

    Ora fare la stessa cosa aprendo due volte il file "poesia2.txt" in modalita’ di sola scrittura (appendendo), ed ogni volta scrivendoci uno dei due versi.

    Cosa succede se riapro "poesia2.txt" in modalita’ "w" e lo richiudo subito?

  5. Scrivere un modulo trucco.py che stampa il codice del modulo stesso a schermo.

    Curiosita’: abbiamo appena scritto (barando!) un programma di Quine.


Python: Input-Output (Soluzioni)

Interfaccia Utente

  1. Soluzione:

    risposta = input("qual'e' il tuo piatto preferito? ")
    
    print("anche a me piace il/la/lo", risposta)
    
  2. Soluzione:

    risposta = input("scrivi due interi: ")
    parole = risposta.split()
    a = int(parole[0])
    b = int(parole[1])
    
    risposta = input("quanto fa " + str(a) + " " + str(b) " ? ")
    risultato = int(risposta)
    
    print(a + b == risultato)
    
  3. Soluzione:

    chiave = input("dammi una chiave: ")
    valore = input("dammi un valore: ")
    
    dizionario = {chiave: valore}
    # oppure
    dizionario = {}
    dizionario[chiave] = valore
    
    print("dizionario =", dizionario)
    
  4. Soluzione:

    nome = input("dimmi il tuo nome per intero: ")
    
    parole_aggiustate = [parola[0].upper() + parola[1:].lower()
                         for parola in nome.split()]
    print("il tuo nome e':", " ".join(parole_aggiustate))
    

Interfaccia Filesystem

  1. Soluzione:

    f = open("data/aatable", "r")
    # oppure
    f = open("data/aatable")
    
    righe = f.readlines()
    print(type(righe))                           # list
    print(type(righe[0]))                        # str
    print(len(righe))
    
    f.close()
    
  2. Soluzione:

    f = open("data/aatable")
    
    prima_riga = f.readline()
    print("la prima riga e': ", prima_riga)
    
    righe_restanti = f.readlines()
    print("restavano", len(righe_restanti), "righe")
    
    righe_restanti_bis = f.readlines()
    print("poi restavano", len(righe_restanti), "righe")
    
    # Nell'ultimo caso, restavano 0 righe: il primo
    # readlines() aveva gia' letto tutte le righe
    # di f
    
    f.close()
    
  3. Soluzione:

    f = open("output.txt", "w")
    f.write("prova prova uno due tre prova")
    f.close()
    
    g = open("output.txt", "r")
    print(g.readlines())
    g.close()
    
  4. Soluzione:

    versi = [
        "S'i fosse fuoco, arderei 'l mondo"
        "s'i fosse vento, lo tempestarei"
    ]
    
    f = open("poesia.txt", "w")
    f.write("\n".join(versi))
    f.close()
    

    Ora ci riprovo con "a":

    f2 = open("poesia2.txt", "a")
    f2.write(versi[0] + "\n")
    f2.close()
    
    f2 = open("poesia2.txt", "a")
    f2.write(versi[1] + "\n")
    f2.close()
    

    E se uso "w" su "poesia2.txtx":

    f = open("prova2.txt", "w")
    # QUI NON FACCIO ASSOLUTAMENTE NIENTE AD f, LO CHIUDO E BASTA
    f.close()
    

    mi accorgo che "poesia2.txt" e’ vuoto! Questo succede perche’ ho usato "w" al posto di "a".

  5. Scrivo nel file trucco.py:

    me_stesso = open("trucco.py")
    print(me_stesso.read())
    me_stesso.close()
    

    Eseguo il file per verificare che faccia cio’ che voglio: da una shell scrivo:

    python3 trucco.py
    


Python: Statement Complessi

Codice condizionale: if

if/elif/else (se/altrimenti-se/altrimenti) permettono di scrivere codice che viene eseguito solo se una condizione e’ soddisfatta:

if condizione:
    print("la condizione e' vera")

oppure di gestire separatamente i due casi soddisfatta/non soddisfatta:

if condizione:
    print("la condizione e' vera")
else:
    print("la condizione e' falsa")

oppure n casi diversi:

if condizione_1:
    print("la prima condizione e' vera")
elif condizione_2:
    print("la seconda condizione e' vera")
elif condizione_3:
    print("la terza condizione e' vera")
else:
    print("nessuna condizione e' vera")

L’if, gli elif e l’else formano una “catena”: sono mutualmente esclusivi, solo uno tra loro viene eseguito!


Esempio. Il codice in un elif ed else e’ mutualmente esclusivo con quello dei vari if ed elif che lo precedono.

Ad esempio, supponiamo di avere due variabili Boolean c1 e c2. Guardiamo in dettaglio in che caso vengono eseguite le varie righe di codice nell’ultimo esempio:

                # c1   c2   | c1   c2    | c1    c2   | c1    c2
                # True True | True False | False True | False False
                # ----------|------------|------------|------------
print("inizio") # si        | si         | si         | si
if c1:          # si        | si         | si         | si
    print("1")  # si        | si         | no         | no
elif c2:        # no        | no         | si         | si
    print("2")  # no        | no         | si         | no
else:           # no        | no         | no         | si
    print("0")  # no        | no         | no         | si
print("fine")   # si        | si         | si         | si

E’ chiaro che se c1 e’ vera, il valore di c2 (ed il corrispondente elif c2) non influenza il comportamento del programma: se l’if viene eseguito (cioe’ se c1 e’ vera) gli elif ed else successivi non vengono neanche considerati!

Supponiamo di voler stampare "1" se c1 e’ vera, ed anche "2" se c2 e’ vera – in modo del tutto indipendente. Posso fare cosi’:

print("inizio")
if c1:
    print("1")

if c2:
    print("2")

if not c1 and not c2:
    print("0")
print("fine")

Qui gli if non formano una “catena”: sono indipendenti l’uno dall’altro!


Esempio. Python usa l’indentazione per decidere quale codice fa parte dell’if e quale no.

Scrivo un programma Python per testare se l’utente e’ un telepate:

print("sto pensando ad un numero tra 1 e 10...")
telepate = int(input("qual'e'? ")) == 72

print("sto calcolando...")
if telepate:
    print("DING DING DING DING!")
    print("COMPLIMENTI!")
    print("sei un telepate certificato!")
else:
    print("grazie per aver giocato")
    print("riprova di nuovo")
print("fine.")

Come si vede eseguendo l’esempio con l’interprete, Python considera dentro l’if tutti i print() indentati.


Esempio. Questo codice apre un file e controlla (i) se e’ vuoto, e (ii) se contiene intestazioni (righe che cominciano per ">"), reagendo di conseguenza:

print("comincio...")

righe = open("data/prot-fasta/1A3A.fasta").readlines()
if len(righe) == 0:
    print("il file FASTA e' vuoto")
else:
    primi_caratteri_di_ogni_riga = [riga[0] for riga in righe]
    if not (">" in primi_caratteri_di_ogni_riga):
        print("il file FASTA non e' valido")
    else:
        print("il file FASTA e' valido")

print("fatto!")

Quiz:

  1. E’ possibile che il codice stampi sia che il file e’ vuoto, sia che e’ valido?
  2. E’ possibile che il codice non stampi "comincio..." o "fatto!"?
  3. Se il file e’ effettivamente vuoto, quando Python esegue la riga print("fatto!"), che valore ha la variabile primi_caratteri_di_ogni_riga?
  4. Posso semplificare il codice usando elif?

Esercizi

Warning

Non dimenticate i due punti!

Se provo a scrivere un if e dimentico i due punti, es.:

>>> condizione = input("Dimmi si: ") == "si"
>>> if condizione

appena premo invio, Python mi dice che la sintassi e’ errata:

  File "<stdin>", line 1
    if condizione
                ^
SyntaxError: invalid syntax

e si rifiuta di eseguire il codice. Quindi e’ facile riconoscere l’errore.

Warning

State attenti all’indentazione!

Sbagliare l’indentazione modifica il comportamento del programma senza pero’ renderlo necessariamente invalido.

In alcuni casi e’ facile capire cosa sta succedendo, es.:

>>> condizione = input("Dimmi si: ") == "si"
>>> if condizione:
>>>    print("hai detto:")
>>>        print("si")

Python da’ errore immediatamente:

  File "<stdin>", line 4
        print("si")
        ^
IndentationError: unexpected indent

In altri invece l’errore e’ molto piu’ sottile. Vedi sezione su codice annidato.

  1. Chiedere all’utente un numero con input(). Se il numero e’ pari, stampare "pari" a schermo, se e’ dispari, stampare "dispari".

    Hint. input() restituisce sempre una stringa.

  2. Chiedere all’utente un numero razionale. Se il numero e’ nell’intervallo [-1,1], stampare "okay", altrimenti non stampare niente.

    Hint. E’ necessario usare elif/else?

  3. Chiedere all’utente due numeri interi. Se il primo e’ maggiore del secondo, stampare "primo", se il secondo e’ maggiore del primo stampare "secondo", altrimenti stampare "nessuno dei due".

  4. Dato il dizionario:

    oroscopo_di = {
        "gennaio": "fortuna estrema",
        "febbraio": "fortuna galattica",
        "marzo": "fortuna incredibile",
        "aprile": "ultra-fortuna",
    }
    

    chiedere all’utente il suo mese di nascita. Se il mese appare come chiave nel dizionario oroscopo_di, stampare a schermo il valore corrispondente. Altrimenti stampare "non disponibile".

    Hint. Per controllare se una chiave appare in un dizionario si puo’ usare key in dict.

  5. Chiedere all’utente il percorso ad un file e leggere i contenuti del file con il metodo readlines(). Poi stampare:

    1. Se il file e’ vuoto, la stringa "vuoto"
    2. Se il file ha meno di 100 righe, "piccolo" e il numero di righe.
    3. Se il file ha tra le 100 e le 1000 righe, "medio" e il numero di righe.
    4. Altrimenti, "grande" e il numero di righe.

    La risposta deve essere stampata su una sola riga.

  6. Chiedere all’utente due triplette di razionali (usando due chiamate a input()). Le due triplette rappresentano due punti nello spazio tridimensionale (tre coordinate x,y,z a testa).

    Se tutte le coordinate sono non-negative, stampare a schermo la distanza Euclidea dei due punti.

    Hint: la distanza Euclidea e’ \sqrt{(x_1 - x_2)^2 + (y_1 - y_2)^2 + (z_1 - z_2)^2}`

  7. E’ possibile che questo codice:

    numero = int(input("scrivi un numero: "))
    if numero % 3 == 0:
        print("divide 3!")
    elif numero % 3 != 0:
        print("non divide 3!")
    else:
        print("boh")
    

    stampi "boh"?

  8. E’ possibile che questo codice:

    numero = int(input("scrivi un numero: "))
    if numero % 2 == 0:
        print("divide 2!")
    if numero % 3 == 0:
        print("divide 3!")
    if numero % 2 != 0 and numero % 3 != 0:
        print("boh")
    

    stampi "boh"?

  9. Chiedere all’utente se vuole eseguire una somma o un prodotto.

    Se l’utente vuole eseguire una somma, chiedere due numeri, effettuare la somma, e stampare il risultato.

    Idem se l’utente vuole eseguire un prodotto.

    Se l’utente non risponde ne’ "somma" ne’ "prodotto", non fare niente.

Codice iterativo: for

for permette di scrivere codice che viene ripetuto (una ed una sola volta) per ciascun elemento di una collezione (stringa, lista, tupla, dizionario).

La sintassi di for e’:

collezione_di_oggetti = range(10) # ad esempio

for elemento in collezione_di_oggetti:
    codice_che_fa_qualcosa_con_elemento(elemento)

Questo ciclo for esegue codice_che_fa_qualcosa_con_elemento() per ciascun elemento in collezione_di_oggetti, in ordine dal primo all’ultimo.

elemento e’ una variabile Python che prende il valore di ciascun elemento di collezione_di_oggetti, dal primo all’ultimo: viene “creata” sul momento quando scriviamo il ciclo for.

Proprio come con le list comprehension, il nome che le diamo e’ arbitrario.

Warning

Se collezione_di_oggetti e’ un dizionario, for itera sulle chiavi.

Occhio che l’ordine delle chiavi in un dizionario non e’ ovvio. Si veda sopra la sezione sui dizionari.


Esempio. Questo ciclo for:

lista = [1, 25, 6, 27, 57, 12]

for numero in lista:
    print(numero)

itera su tutti gli elementi del risultato di lista: prima l’elemento 1, poi l’elemento 25, etc., fino a 12, e li stampa nell’ordine in cui appaiono nella lista.

Ad ogni iterazione il valore dell’elemento corrente viene automaticamente messo nella variabile numero, mentre il print ne stampa il valore.

Posso ottenere lo stesso comportamento anche senza il ciclo for, cosi’:

numero = lista[0]   # prima iterazione
print(numero)

numero = lista[1]   # seconda iterazione
print(numero)

numero = lista[2]   # terza iterazione
print(numero)

# ...

numero = lista[5]   # ultima iterazione
print(numero)

Il for permette di compattare questo codice in due sole righe.


Esempio. Piuttosto che stampare gli elementi della lista, voglio stampare la loro somma.

Modifico il for dell’esempio sopra:

lista = [1, 25, 6, 27, 57, 12]

somma = 0
for numero in lista:
    somma = somma + numero

print("la somma e'", somma)

Ho creato una variabile di supporto somma che inizializzo a 0.

Poi scorro su tutti i numeri contenuti in lista, e man mano li aggiungo a somma.

Una volta terminato il ciclo for, somma varra’ (per costruzione):

lista[0] + lista[1] + ... + lista[-1]

che e’ esattamente la somma degli elementi.


Esempio. Piuttosto che calcolare la somma degli elementi della lista, voglio trovare il massimo.

L’idea e’ questa:

  • Itero la lista con un for.
  • Creo una nuova variabile massimo_fino_ad_ora in cui memorizzo l’elemento piu’ grande che ho trovato fino ad ora. Il valore viene aggiornato ad ogni iterazione del ciclo for.
  • Per ogni elemento della lista (cioe’ in ogni iterazione del for) controllo se l’elemento che ho sotto mano e’ piu’ grande di massimo_fino_ad_ora:
    • Se non lo e’, non faccio niente.
    • Se lo e’, aggiorno massimo_fino_ad_ora.
  • Quando il for avra’ finito di scorrere sugli elementi della lista, massimo_fino_ad_ora conterra’ (suspance!) il massimo elemento trovato fino ad ora.

Scrivo:

lista = [1, 25, 6, 27, 57, 12]

# creo la variabile ausiliaria, la inizializzo con il
# primo numero della lista per convenienza
massimo_fino_ad_ora = lista[0]

# itero su tutti gli elementi tranne il primo
for numero in lista[1:]:

    # l'elemento che ho sotto mano e' piu' grande del
    # piu' grande che ho visto fino ad ora?
    if numero > massimo_fino_ad_ora:

        # si': aggiorno massimo_fino_ad_ora
        massimo_fino_ad_ora = numero

print("il massimo e':", massimo_fino_ad_ora)

Esempio. Data la seguente tabella (che potrebbe essere il risultato di readlines() su un file):

tabella = [
    "protein domain start end",
    "YNL275W PF00955 236 498",
    "YHR065C SM00490 335 416",
    "YKL053C-A PF05254 5 72",
    "YOR349W PANTHER 353 414",
]

voglio convertirla in un dizionario fatto cosi’:

dati = {
    "YNL275W": ("PF00955", 236, 498),
    "YHR065C": ("SM00490", 335, 416),
    "YKL053C-A": ("PF05254", 5, 72),
    "YOR349W": ("PANTHER", 353, 414)
}

che contiene per ogni dominio (riga) di tabella, esclusa l’intestazione, come chiave la proteina corrispondente (prima colonna) e come valore le informazioni associate (altre colonne: nome, inizio e fine del dominio).

Scrivo:

# parto da un dizionario vuoto
dati = {}

# per ogni riga, esclusa l'intestazione
for riga in tabella[1:]:
    parole = riga.split()

    proteina = parole[0]
    dominio = parole[1]
    inizio = parole[2]
    fine = parole[3]

    # aggiorno il dizionario
    dati[proteina] = (dominio, inizio, fine)

Esempio. break permette di interrompere il ciclo for. Ad esempio:

percorso = input("scrivi un percorso a file: ")
righe = open(percorso).readlines()

for riga in righe:
    riga = riga.strip()
    print("ho letto:", riga)

    if len(riga) == 0:
        # se la riga e' vuota, esco dal ciclo
        break

# <--- il break ci porta immediatamente QUI

legge le righe dal file indicato dall’utente, e le stampa una per una. Pero’ appena incontra una riga vuota (vedi l’if), esce dal ciclo.

Esempio. continue permette di passare all’iterazione successiva del for. Ad esempio:

percorso = input("scrivi un percorso a file: ")
righe = open(percorso).readlines()

for riga in righe:
    # <--- il continue ci riporta QUI, ma all'iterazione
    #      (e quindi all'elemento di righe) successivo

    riga = riga.strip()
    print("ho letto:", riga)

    if riga[0] == ">":
        print("intestazione")
        continue

    print("sequenza")

legge le righe del file indicato dall’utente, che supponiamo essere un file fasta. Stampa ogni riga che incontra. Poi, se la riga e’ un’intestazione, stampa "intestazione" ed il continue fa saltare a Python tutto cio’ che c’e’ tra il continue stesso e la fine dell’iterazione corrente del ciclo for.

In altre parole, salta all’iterazione successiva. Python riprende noncurante l’esecuzione all’elemento successivo di righe, e riprende ad eseguire il for.

Codice iterativo: while

while permette di scrivere codice che viene ripetuto finche’ una condizione e’ vera.

La sintassi e’:

while condizione:
    condizione = codice_che_fa_qualcosa_e_aggiorna_condizione()

Il codice all’interno del while viene ripetuto un numero indefinito di volte: dipende da quanto ci mette condizione a diventare False.


Esempio. Scrivo un ciclo while che chiede all’utente se vuole fermarsi, e continua a chiedere finche’ l’utente non risponde "si":

while input("vuoi che mi fermi? ") != "si":
    print("se non rispondi 'si' non mi fermo!")

Esempio. Esattamente come con il for, posso usare continue e break per alterare il flusso del ciclo. Ad esempio:

while True:
    risposta = input("qual'e la capitale d'Italia? ")

    if risposta.lower() == "roma":
        print("giusto!")
        break

    print("riprova!")

# <--- il break ci porta QUI
print("finito")

questo codice continua a girare finche’ l’utente non risponde "roma" (con maiuscole o minuscole, poco importa).

Riscrivo il ciclo per fare in modo che chieda all’utente se continuare o meno:

while True:
    risposta = input("qual'e' la capitale d'Italia? ")
    if risposta.lower() == "roma":
        print("giusto!")
        break                   # esce dal while
    else:
        print("doh!")

    risposta = input("vuoi riprovare? ")
    if risposta.lower() == "no":
        print("va bene")
        break                   # esce dal while

Esercizi

  1. Scrivere un ciclo for che:

    1. Stampi a schermo gli elementi di range(10), uno per riga.

    2. Stampi a schermo il quadrato degli elementi di range(10), uno per riga.

    3. Stampi a schermo la somma dei quadrati di range(10).

    4. Stampi a schermo il prodotto degli elementi di range(1,10).

    5. Dato il dizionario:

      volume_di = {
          "A":  67.0, "C":  86.0, "D":  91.0,
          "E": 109.0, "F": 135.0, "G":  48.0,
          "H": 118.0, "I": 124.0, "K": 135.0,
          "L": 124.0, "M": 124.0, "N":  96.0,
          "P":  90.0, "Q": 114.0, "R": 148.0,
          "S":  73.0, "T":  93.0, "V": 105.0,
          "W": 163.0, "Y": 141.0,
      }
      

      che codifica il volume di ciascun aminoacido, stampi a schermo la somma dei valori.

    6. Dato il dizionario:

      volume_di = {
          "A":  67.0, "C":  86.0, "D":  91.0,
          "E": 109.0, "F": 135.0, "G":  48.0,
          "H": 118.0, "I": 124.0, "K": 135.0,
          "L": 124.0, "M": 124.0, "N":  96.0,
          "P":  90.0, "Q": 114.0, "R": 148.0,
          "S":  73.0, "T":  93.0, "V": 105.0,
          "W": 163.0, "Y": 141.0,
      }
      

      che codifica il volume di ciascun aminoacido, e la stringa FASTA:

      fasta = """>1BA4:A|PDBID|CHAIN|SEQUENCE
      DAEFRHDSGYEVHHQKLVFFAEDVGSNKGAIIGLMVGGVV"""
      

      stampi a schermo il volume totale della proteina (leggi: la somma dei volumi di tutti i suoi residui).

      Hint. Prima conviene estrarre la sequenza vera e propria da fasta, poi, per ciascun carattere nella sequenza (for carattere in sequenza) prendere dal dizionario il volume corrispondente e sommarlo al totale.

    7. Trovi il valore minimo della lista [1, 25, 6, 27, 57, 12].

      Hint. Si veda l’esempio sopra in cui troviamo il massimo della lista. E’ sufficiente adattare la logica che decide quando aggiornare la variabile ausiliaria (e magari rinominarla da massimo_fino_ad_ora a minimo_fino_ad_ora).

    8. Trovi sia il massimo che il minimo della lista [1, 25, 6, 27, 57, 12].

      Hint. E’ necessario usare due variabili ausiliarie: massimo_fino_ad_ora e minimo_fino_ad_ora.

    9. Data la sequenza nucleotidica:

      sequenza = "ATGGCGCCCGAACAGGGA"
      

      restituisca la lista di tutte le sue sotto-sequenze di tre nucleotidi. La soluzione deve essere:

      ["ATG", "GCG", "CCC", "GAA", "CAG", "GGA"]
      

      Hint: conviene iterare sul risultato di range(0, len(sequenza), 3) ed aggiungere man mano ogni tripletta ad una lista vuota preventivamente creata.

    10. Dato il testo (in formato FASTA):

      testo = """>2HMI:A|PDBID|CHAIN|SEQUENCE
      PISPIETVPVKLKPGMDGPKVKQWPLTEEKIKALVEICTEMEKEGKISKI
      >2HMI:B|PDBID|CHAIN|SEQUENCE
      PISPIETVPVKLKPGMDGPKVKQWPLTEEKIKALVEICTEMEKEGKISKI
      >2HMI:C|PDBID|CHAIN|SEQUENCE
      DIQMTQTTSSLSASLGDRVTISCSASQDISSYLNWYQQKPEGTVKLLIYY
      >2HMI:D|PDBID|CHAIN|SEQUENCE
      QITLKESGPGIVQPSQPFRLTCTFSGFSLSTSGIGVTWIRQPSGKGLEWL
      >2HMI:E|PDBID|CHAIN|SEQUENCE
      ATGGCGCCCGAACAGGGAC
      >2HMI:F|PDBID|CHAIN|SEQUENCE
      GTCCCTGTTCGGGCGCCA"""
      

      restituisca un dizionario sequenza_di che abbia come chiavi i nomi delle sequenze cosi’ come sono scritti nelle intestazioni (il primo sara’ 2HMI:A, il secondo 2HMI:B, etc.), e come valore la sequenza corrispondente.

      Il risultato dovra’ somigliare a questo:

      sequenza_di = {
          "2HMI:A": "PISPIETVPVKLKPGMDGPKVKQW...",
          "2HMI:B": "PISPIETVPVKLKPGMDGPKVKQW...",
          # ...
      }
      

      Hint. Conviene prima spezzare testo nelle sue righe. Poi si puo’ iterare sulle righe cosi’ ottenute: se una riga e’ di intestazione, mi salvo il nome della sequenza corrispondente; se la riga invece e’ una sequenza, aggiorno il dizionario con il nome ottenuto alla riga sopra e la sequenza ottenuta dalla riga corrente.

  2. Scrivere un ciclo while che:

    1. Continui a chiedere all’utente di scrivere "STOP". Se l’utente scrive "STOP" (in maiuscolo) termina, senno’ scrive all’utente "devi scriviere 'STOP'..." e continua.
    2. Come sopra, ma deve terminare anche se l’utente risponde "stop" in minuscolo.
  3. Che cosa stampa a schermo questo codice?

    1. for numero in range(10):
          print("processo l'elemento", numero)
      
    2. for numero in range(10):
          print("processo l'elemento", numero)
          break
      
    3. for numero in range(10):
          print("processo l'elemento", numero)
          continue
      
    4. for numero in range(10):
          print(numero)
          if numero % 2 == 0:
              break
      
    5. for numero in range(10):
          if numero % 2 == 0:
              break
          print(numero)
      
    6. condizione = False
      while condizione:
          print("la condizione e' vera")
      
    7. condizione = False
      while condizione:
          print("la condizione e' vera")
          condizione = True
      
    8. condizione = True
      while condizione:
          print("la condizione e' vera")
      
    9. numeri = range(10)
      
      i = 0
      while i < len(numeri):
          print("all'indice", i, "c'e' l'elemento", numeri[i])
          i += 1
      
    10. righe = [
          "riga 1",
          "riga 2",
          "riga 3",
          "",
          "riga 5",
          "riga 6",
      ]
      
      for riga in righe:
          riga = riga.strip()
          if len(riga) == 0:
              break
          else:
              print("ho letto:", riga)
      
  4. Data la tupla:

    numeri = (0, 1, 1, 0, 0, 0, 1, 1, 2, 1, 2)
    

    scrivere un ciclo che itera su numeri, si ferma appena incontra il valore 2 e ne stampa a schermo la posizione.

  5. Data la tupla:

    stringhe = ("000", "51", "51", "32", "57", "26")
    

    scrivere un ciclo che itera su stringhe, si ferma appena incontra una stringa che contiene un carattere "2", e stampa a schermo posizione e valore della stringa sulla quale si e’ fermato.

    La soluzione e’: posizione 4, valore "32".

Codice annidato

Posso combinare un numero arbitrario di statement complessi (if, for e while) usando l’indentazione, inclusi cicli innestati.


Esempio. Voglio simulare un orologio che ha due lancette: ore e minuti:

for ora in range(24):
    for minuto in range(1, 60+1):
        print("ora =", ora, "minuro =", minuto)

Qui all’esterno itero sulle ore, mentre all’interno itero sui minuti: ogni volta che il for interno finisce le sue sessanta iterazioni, il for esterno ne completa una.

Posso “estendere” l’orologio per comprendere anche i giorni dell’anno: si tratta solo di aggiungere un for sui giorni che contenga il for delle ore, cosi’:

for giorno in range(1, 365+1):
    for ora in range(24):
        for minuto in range(1, 60+1):
            print(giorno, ora, minuto)

(ignorando gli anni bisestili).

Naturalmente posso “estendere” l’orologio agli anni aggiungendo un altro for ancora piu’ esterno, etc.


Esempio. Voglio sapere se in una lista ci sono elementi ripetuti, e se ci sono in che posizioni si trovano. Partiamo dalla lista:

numeri = [5, 9, 4, 4, 9, 2]

L’idea e’ di usare due cicli for innestati per iterare sulle coppie di elementi di numeri.

In pratica, per ciascun elemento (diciamo in posizione i) voglio controllare se almeno uno di quelli che stanno alla sua destra (diciamo in posizione j) e’ uguale a lui. Immagine:

+---+---+---+---+---+---+
| 5 | 9 | 4 | 4 | 9 | 2 |
+---+---+---+---+---+---+
  ^
  i
    \__________________/
      i possibili valori di j


+---+---+---+---+---+---+
| 5 | 9 | 4 | 4 | 9 | 2 |
+---+---+---+---+---+---+
      ^           ^
      i           doppione!
        \______________/
      i possibili valori di j


+---+---+---+---+---+---+
| 5 | 9 | 4 | 4 | 9 | 2 |
+---+---+---+---+---+---+
          ^   ^
          i   doppione!
            \__________/
      i possibili valori di j

Scrivo:

posizioni_ripetizioni = []

for i in range(len(numeri)):
    numero_in_pos_i = numeri[i]

    # ho il numero in posizione i; ora lo voglio
    # confrontare con quelli che lo seguono
    for j in range(i + 1, len(numeri)):
        numero_in_pos_j = numeri[j]

        # ora confronto i due numeri
        if numero_in_pos_i == numero_in_pos_j:

            # sono uguali: aggiungo le loro
            # posizioni alla lista
            posizioni_ripetizioni.append((i, j))

print(posizioni_ripetizioni)

Okay, ho ottenuto le posizioni dei ripetizioni. Verifico stampando, per ciascuna coppia di posizioni in posizioni_ripetizioni i valori corrispondenti:

for i, j in posizioni_ripetizioni:
    numero_in_pos_i = numeri[i]
    numero_in_pos_j = numeri[j]
    print(numero_in_pos_i, numero_in_pos_j)

Esempio. Apro un file FASTA e ne leggo i contenuti in una lista di stringhe:

righe = open("data/prot-fasta/3J01.fasta").readlines()

Il valore di righe sara’ e’:

righe = [
    ">3J01:0|PDBID|CHAIN|SEQUENCE",
    "AVQQNKPTRSKRGMRRSHDALTAVTSLSVDKTSGEKHLRHHITADGYYRGRKVIAK",
    ">3J01:1|PDBID|CHAIN|SEQUENCE",
    "AKGIREKIKLVSSAGTGHFYTTTKNKRTKPEKLELKKFDPVVRQHVIYKEAKIK",
    ">3J01:2|PDBID|CHAIN|SEQUENCE",
    "MKRTFQPSVLKRNRSHGFRARMATKNGRQVLARRRAKGRARLTVSK",
    ">3J01:3|PDBID|CHAIN|SEQUENCE",
    # ...
]

Voglio convertire righe in un dizionario dove le chiavi sono le intestazioni, e i valori corrispondenti sono le sequenze.

Scrivo:

# parto da un dizionario vuoto
dizionario = {}

# per ogni riga...
for riga in righe:

    if riga[0] == ">":
        # e' una riga di intestazione: la memorizzo
        # nella variabile 'intestazione'
        intestazione = riga

    else:
        # non e' una riga di intestazione: la
        # memorizzo nella variabile 'sequenza'
        sequenza = riga

        # a questo punto ho sia l'intestazione (che
        # ho memorizzato nella riga precedente) sia
        # la sequenza (che ho memorizzato in questa
        # riga): aggiorno il dizionario
        dizionario[intestazione] = sequenza

# una volta scorse tutte le righe, ho finito
# di creare il mio dizionario. lo stampo
print(dizionario)

Funziona, ma c’e’ un problema.

Se guardiamo bene, in righe ci sono casi in cui la sequenza di una catena proteica occupa piu’ righe. Ad esempio:

righe = [
    # ...
    ">3J01:5|PDBID|CHAIN|SEQUENCE",
    "MAKLTKRMRVIREKVDATKQYDINEAIALLKELATAKFVESVDVAVNLGIDARKSDQNVRGATVLPHGTGRSVRVAVFTQ",
    "GANAEAAKAAGAELVGMEDLADQIKKGEMNFDVVIASPDAMRVVGQLGQVLGPRGLMPNPKVGTVTPNVAEAVKNAKAGQ",
    "VRYRNDKNGIIHTTIGKVDFDADKLKENLEALLVALKKAKPTQAKGVYIKKVSISTTMGAGVAVDQAGLSASVN",
    # ...
]

In questo caso il nostro codice non funziona: quando facciamo dizionario[intestazione] = sequenza mettiamo nel dizionario solo l’ultima riga della sequenza corrente, dimenticandoci di tutte quelle che la precedono!

Per sistemare il codice, devo fare in modo che si ricordi di tutte le righe della sequenza che corrisponde all’intestazione corrente. Scrivo:

sequenza_di = {}

for riga in righe:

    if riga[0] == ">":
        intestazione = riga

    else:
        sequenza = riga

        # qui, al posto di mettere nel dizionario la sequenza,
        # metto una lista di TUTTE le righe che compongono
        # la sequenza

        if not intestazione in sequenza_di:
            sequenza_di[intestazione] = []

        sequenza_di[intestazione].append(sequenza)

L’if not ... serve per accertarsi che la lista di righe associata ad intestazione esista, altrimenti non posso farci append().

Una alternativa e’ questa:

for riga in righe:

    if riga[0] == ">":
        intestazione = riga
        sequenza_di[intestazione] = []

    else:
        sequenza = riga
        sequenza_di[intestazione].append(sequenza)

In questa versione garantisco che sequenza_di[intestazione] sia una lista ogni volta che leggo una nuova intestazione.

Assumiamo che un burlone abbia formattato in modo sbagliato il file FASTA: ha messo prima le sequenze, e poi le intestazioni corrispondenti. Esempio:

fasta_sottosopra = [
    # prima sequenza e prima intestazione
    "AVQQNKPTRSKRGMRRSHDALTAVTSLSVDKTSGEKHLRHHITADGYYRGRKVIAK",
    ">3J01:0|PDBID|CHAIN|SEQUENCE",

    # seconda sequenza e seconda intestazione
    "AKGIREKIKLVSSAGTGHFYTTTKNKRTKPEKLELKKFDPVVRQHVIYKEAKIK",
    ">3J01:1|PDBID|CHAIN|SEQUENCE",
]

Il nostro codice non funziona piu’: nel codice assumiamo che quando leggiamo una riga della sequenza, l’intestazione corrispondente sia gia’ nota. Pero’ in questo FASTA sottosopra e’ vero il contrario!

Riscriviamo il codice in modo da assumere, invece, che e’ quando otteniamo l’intestazione che gia’ conosciamo la sequenza!

Scrivo:

dizionario = {}
ultima_sequenza = []

for riga in righe:

    if riga[0] == ">":
        # e' una riga di intestazione, ho gia' memorizzato
        # la sequenza in 'ultima_sequenza'. aggiorno il
        # dizionario
        intestazione = riga
        dizionario[intestazione] = ultima_sequenza

        # ora che ho messo il valore di 'ultima_sequenza',
        # la faccio ricominciare dalla lista vuota
        ultima_sequenza = []

    else:
        # e' una riga di sequenza, ma ancora non conosco
        # l'intestazione (nel file, viene dopo!). non
        # tocco il dizionario, mi limito a memorizzare
        # la sequenza nella lista 'ultima_sequenza'
        sequenza = riga
        ultima_sequenza.append(sequenza)

print(dizionario)

Esercizi

  1. Data la matrice:

    n = 5
    matrice = [list(range(n)) for i in range(n)]
    

    scrivere un doppio ciclo for che stampi a schermo tutti gli elementi di matrice, uno per riga.

  2. Data la matrice:

    n = 5
    matrice = [list(range(n)) for i in range(n)]
    

    cosa stampano i seguenti frammenti di codice?

    1. for riga in matrice:
          for elemento in riga:
              print(elemento)
      
    2. somma = 0
      for riga in matrice:
          for elemento in riga:
              somma = somma + elemento
      print(somma)
      
    3. for i in range(len(matrice)):
          riga = matrice[i]
          for j in range(len(riga)):
              elemento = riga[j]
              print(elemento)
      
    4. for i in range(len(matrice)):
          for j in range(len(matrice[i])):
              print(matrice[i][j])
      
    5. non_lo_so = []
      for i in range(len(matrice)):
          for j in range(len(matrice[i])):
              if i == j:
                  non_lo_so.append(matrice[i][j])
      print(" ".join([str(x) for x in non_lo_so]))
      
  3. Data la lista:

    numeri = [8, 3, 2, 9, 7, 1, 8]
    

    scrivere un doppio ciclo for che stampa a schermo tutte le coppie di elementi di numeri.

  4. Modificare la soluzione dell’esercizio sopra in modo che se la coppia (i,j) e’ gia’ stata stampata, allora la coppia simmetrica (j,i) non venga stampata.

    Hint. Vedi l’esempio sopra.

  5. Fare la stessa cosa con la lista:

    stringhe = ["io", "sono", "una", "lista"]
    
  6. Dato l’intervallo:

    numeri = range(10)
    

    scrivere un doppio ciclo for che stampa a schermo solo le coppie di elementi di numeri dove il secondo elemento della coppia e’ il doppio del primo.

    Il risultato dovra’ essere:

    0 0
    1 2
    2 4
    ...
    
  7. Data la lista:

    numeri = [8, 3, 2, 9, 7, 1, 8]
    

    scrivere un doppio ciclo for che itera su tutte le coppie degli elementi di numeri e stampa a schermo le coppie di elementi la cui somma e’ 10.

    (E’ lecito stampare eventuali “ripetizioni”, ad esempio 8 + 2 e 2 + 8.)

    Il risultato dovra’ essere:

    8 2
    3 7
    2 8
    9 1
    

    Hint. C’e’ un esempio che mostra come iterare sulle coppie di elementi di una lista. E’ sufficiente adattarlo.

  8. Come sopra, ma al posto di stampare a schermo, memorizzare le coppie degli elementi la cui somma e’ 10 in una lista lista_delle_coppie.

    Il risultato dovra’ essere:

    >>> lista_delle_coppie
    [(8, 2), (3, 7), (2, 8), 9, 1)]
    
  9. Date le liste:

    numeri_1 = [5, 9, 4, 4, 9, 2]
    numeri_2 = [7, 9, 6, 2]
    

    scrivere un doppio ciclo for che itera sulle due liste e stampa a schermo valori e posizioni degli elementi di numeri_1 che appaiono anche in numeri_2.

    Il risultato dovra’ essere:

    posizioni: 1, 1; valore ripetuto: 9
    posizioni: 4, 1; valore ripetuto: 9
    posizioni: 5, 3; valore ripetuto: 2
    
  10. Come sopra, ma al posto di stampare a schermo, memorizzare le posizioni ed il valore ripetuto una lista di triple della forma (posizione_1, posizione_2, valore_ripetuto).

  11. Data la matrice:

    n = 5
    matrice = [list(range(n)) for i in range(n)]
    

    scrivere un doppio ciclo for che trovi l’elemento piu’ grande.

    Hint. E’ sufficiente adattare il codice per trovare il massimo-minimo di una lista (che e’ ad una dimensione) alla matrice (che ha due dimensioni).

  12. Data la lista di sequenze nucleotidiche:

    sequenze = [
        "ATGGCGCCCGAACAGGGA",
        "GTCCCTGTTCGGGCGCCA",
    ]
    

    voglio ottenere una lista che contenga, per ogni sequenza in sequenze, la lista delle sue triplette.

    Hint. Si puo’ riutilizzare un esercizio precedente.

  13. Data la lista:

    numeri = [5, 9, 4, 4, 9, 2]
    

    scrivere del codice che conta il numero di ripetizioni di ogni elemento, e metta il risultato in un dizionario. Il dizionario dovra’ somigliare a questo:

    num_ripetizioni = {
        5: 1,
        9: 2,
        4: 2,
        2: 1,
    }
    

    Hint. Si puo’ modificare uno degli esempi sopra in modo che, invece di salvare la posizione delle ripetizioni, incrementi il numero di ripetizioni in num_ripetizioni.

    Hint. Occhio che se la chiave 5 nel dizionario non c’e’, non posso fare num_ripetizioni[5] += 1, perche’ num_ripetizioni[5] non esiste! Vedi l’esempio sulla lettura del file FASTA.

  14. Data una lista di cluster di geni (liste), ad esempio:

    gruppi = [["gene1", "gene2"], ["gene3"], [], ["gene4", "gene5"]]
    

    scrivere un singolo ciclo che trova il gruppo piu’ grande e lo memorizza in una variabile gruppo_piu_grande_fino_ad_ora.

    Hint: e’ simile a trovare il minimo/massimo di una lista di interi, ma la variabile ausiliaria deve contenere la lista piu’ lunga trovata fin’ora.

  15. Data la lista di sequenze:

    sequenze_2HMI = {
        "A": "PISPIETVPVKLKPGMDGPKVKQWPLTEEKI",
        "B": "PISPIETVPVKLKPGMDGPKVKQWPLTEEKI",
        "C": "DIQMTQTTSSLSASLGDRVTISCSASQDISS",
        "D": "QITLKESGPGIVQPSQPFRLTCTFSGFSLST",
        "E": "ATGGCGCCCGAACAGGGAC",
        "F": "GTCCCTGTTCGGGCGCCA",
    }
    

    scrivere un ciclo for che (iterando sulle coppie chiave-valore del dizionario) restituisca un dizionario degli istogrammi (dizionari aminoacido->numero di ripetizioni) di ciascun elemento di sequenze_2HMI.

    Hint. Calcolare un istogramma richiede esso stesso un ciclo for: quindi in totale ci si puo’ aspettare che ci siano due cicli for innestati.

    Il risultato (un dizionario di dizionari) dovra’ somigliare a questo:

    istogrammi = {
        "A": {
            "P": 6,
            "I": 3,
            "S": 1,
            #...
        },
        "B": {
            "P": 6,
            "I": 3,
            "S": 1,
            #...
        },
    
        #...
    
        "F": {
            "A": 1,
            "C": 7,
            "G": 6,
            "T": 4,
        }
    }
    
  16. Data la lista di stringhe:

    tabella = [
        "protein domain start end",
        "YNL275W PF00955 236 498",
        "YHR065C SM00490 335 416",
        "YKL053C-A PF05254 5 72",
        "YOR349W PANTHER 353 414",
    ]
    

    scrivere del codice che prenda i nomi delle colonne dalla prima riga di tabella e:

    • per ciascuna riga compili un dizionario di questo tipo:

      dizionario = {
          "protein": "YNL275W",
          "domain": "PF00955",
          "start": "236",
          "end":, "498"
      }
      
    • appenda il dizionario ad una lista.

  17. Date:

    alfabeto_min = "abcdefghijklmnopqrstuvwxyz"
    alfabeto_mai = alfabeto_min.upper()
    

    scrivere un ciclo (for o while) che, partendo da un dizionario vuoto, inserisca tutte le coppie chiave-valore:

    "a": "A",
    "b": "B",
    ...
    

    cioe’ che mappi dal carattere i-esimo di alfabeto_min al carattere i-esimo di alfabeto_mai.

    Poi usare il dizionario cosi’ costruito per implementare un ciclo for che, data una stringa arbitraria, ad esempio:

    stringa = "sono una stringa"
    

    abbia lo stesso effetto di stringa.upper().

  18. Scrivere un modulo che chiede all’utente il percorso a due file di testo, e stampa a schermo le righe dei due file, una per una, appaiate: le righe del primo file vanno stampate sulla sinistra, le righe del secondo sulla destra.

    Se il primo file contiene:

    prima riga
    seconda riga
    

    ed il secondo:

    ACTG
    GCTA
    

    il risultato deve essere:

    prima riga ACTG
    seconda riga GCTA
    

    Hint. Attenzione che i due file potrebbero avere lunghezze diverse. In questo caso (opzionalmente) le righe “mancanti” vanno stampate come se fossero righe vuote.

  19. Scrivere un modulo che, dato il file data/dna-fasta/fasta.1:

    1. Legga i contenuti del file FASTA in un dizionario.
    2. Calcoli quante volte ogni nucleotide appare in ciascuna sequenza.
    3. Calcoli il GC-content della sequenza.
    4. Calcoli la AT/GC-ratio della sequenza.

Python: Statement Complessi (Soluzioni)

Codice condizionale: if

  1. Soluzione:

    numero = int(input("scrivi un numero: "))
    
    if numero % 2 == 0:
        print("pari")
    else:
        print("dispari")
    

    Uso else perche’ pari e dispari sono le uniche due possibilita’.

    Volendo, posso esplicitare la terza possibilita’, cioe’ il caso in cui numero non e’ ne’ pari ne’ dispari, cosi’:

    if numero % 2 == 0:
        print("pari")
    elif numero % 2 == 1:
        print("dispari")
    else:
        print("impossibile!")
    

    ma il codice nell’else non verra’ eseguito per nessun valore di numero!

    Visto che le due possibilita’ (numero e’ pari, numero e’ dispari) sono mutualmente esclusive, posso anche permettermi di scrivere:

    if numero % 2 == 0:
        print("pari")
    if numero % 2 == 1:
        print("dispari")
    

    perche’ anche in assenza dell’else, uno e solo uno dei due if puo’ essere eseguito.

  2. Soluzione:

    numero = float(input("scrivi un razionale: "))
    
    if numero >= -1 and numero <= 1:
        print("okay")
    

    Non servono ne’elif (c’e’ una sola condizione) ne’ else (se la condizione e’ falsa, non devo fare niente).

  3. Soluzione:

    risposta = input("scrivi due numeri separati da spazio: ")
    
    parole = risposta.split()
    numero1 = int(parole[0])
    numero2 = int(parole[1])
    
    if numero1 > numero2:
        print("primo")
    elif numero2 > numero1:
        print("secondo")
    else:
        print("nessuno dei due")
    

    In alternativa:

    risposta = input("scrivi due numeri separati da spazio: ")
    
    numeri = [int(parola) for parola in risposta.split()]
    
    if numeri[0] > numeri[1]:
        print("primo")
    elif numeri[0] < numeri[1]:
        print("secondo")
    else:
        print("nessuno dei due")
    
  4. Soluzione:

    oroscopo_di = {
        "gennaio": "fortuna estrema",
        "febbraio": "fortuna galattica",
        "marzo": "fortuna incredibile",
        "aprile": "ultra-fortuna",
    }
    
    mese = input("dimmi il tuo mese di nascita: ")
    
    if mese in oroscopo_di:
        print(oroscopo_di[mese])
    else:
        print("non disponibile")
    
  5. Soluzione:

    percorso = input("scrivi il percorso: ")
    
    righe = open(percorso, "r").readlines()
    if len(righe) == 0:
        print("vuoto")
    elif len(righe) < 100:
        print("piccolo", len(righe))
    elif len(righe) < 1000:
        print("medio", len(righe))
    else:
        print("grande", len(righe))
    

    Si noti che non e’ necessario specificare per intero le condizioni: nel codice abbrevio 100 < len(righe) < 1000 con len(righe) < 1000. Me lo posso permettere perche’ quando len(righe) e’ minore di 100 eseguo il primo elif: il secondo elif non viene neanche considerato.

  6. Soluzione:

    punto1 = [float(parola) for parola
              in input("scrivi tre coordinate: ").split()]
    
    punto2 = [float(parola) for parola
              in input("scrivi tre coordinate: ").split()]
    
    if punto1[0] >= 0 and punto1[1] >= 0 and punto1[2] >= 0 and \
       punto2[0] >= 0 and punto2[1] >= 0 and punto2[2] >= 0:
        diff_x = punto1[0] - punto2[0]
        diff_y = punto1[1] - punto2[1]
        diff_z = punto1[2] - punto2[2]
    
        print("la distanza e'", (diff_x**2 + diff_y**2 +  diff_z**2)**0.5)
    

    Si noti che il print e’ dentro l’if.

  7. Soluzione: sappiamo che numero e’ un intero arbitrario (puo’ essere qualunque intero deciso dall’utente). Il codice che ci interessa e’ questo:

    if numero % 3 == 0:
        print("divide 3!")
    elif numero % 3 != 0:
        print("non divide 3!")
    else:
        print("boh")
    

    L’if, l’elif e l’else formano una catena: solo uno tra loro viene eseguito.

    1. L’if viene eseguito se e solo se numero e’ divisibile per tre.
    2. L’elif viene eseguito se e solo se l’if precedente non viene eseguito e se numero non e’ divisibile per tre.
    3. L’else viene eseguito quando ne’ l’if ne’ l’elif vengono eseguito.

    Visto che non ci sono numeri che non siano ne’ divisibili ne’ non-divisibili per 3, non resta alcuna altra possibilita’. O viene eseguito l’if, o viene eseguito l’elif: l’else non viene mai eseguito.

    Quindi la risposta e’ no.

  8. Soluzione: come sopra, numero e’ un intero arbitrario. Il codice e’:

    numero = int(input("scrivi un numero: "))
    if numero % 2 == 0:
        print("divide 2!")
    if numero % 3 == 0:
        print("divide 3!")
    if numero % 2 != 0 and numero % 3 != 0:
        print("boh")
    

    Qui non ci sono “catene” di if, elif ed else: ci sono tre if indipendenti.

    1. Il primo if viene eseguito se e solo se numero e’ divisibile per due.
    2. Il secondo if viene eseguito se e solo se numero e’ divisibile per tre.
    3. Il terzo if viene eseguito se e solo se numero non e’ divisibile ne’ per due, ne’ per tre.

    Se numero e’ es. 6, che e’ divisibile sia per due che per tre, allora i primi due if vengono entrambi eseguiti, mentre il terzo non viene eseguito.

    Se numero e’ es. 5, che non e’ divisibile ne’ per due ne’ per tre, allora i primi due if non vengono eseguiti; in compenso viene eseguito il terzo.

    Quindi la risposta e’ si’.

    (Altri esempi: per numero = 2 viene eseguito solo il primo if, per numero = 3 solo il secondo. Si noti pero’ che non c’e’ verso di non eseguire nessuno uno dei tre if.)

  9. Soluzione:

    risposta = input("somma o prodotto?: ")
    
    if risposta == "somma":
        numero1 = int(input("numero 1: "))
        numero2 = int(input("numero 2: "))
        print("la somma e'", numero1 + numero2)
    
    elif risposta == "prodotto":
        numero1 = int(input("numero 1: "))
        numero2 = int(input("numero 2: "))
        print("il prodotto e'", numero1 * numero2)
    

    Usare un if o un elif non altera l’esecuzione del programma.

    Posso semplificare cosi’:

    risposta = input("somma o prodotto?: ")
    numero1 = int(input("numero 1: "))
    numero2 = int(input("numero 2: "))
    
    if risposta == "somma":
        print("la somma e'", numero1 + numero2)
    
    elif risposta == "prodotto":
        print("il prodotto e'", numero1 * numero2)
    

Codice iterativo

  1. Soluzioni:

    1. Soluzione:

      for numero in range(10):
          print(numero)
      
    2. Soluzione:

      for numero in range(10):
          print(numero**2)
      
    3. Soluzione:

      somma_quadrati = 0
      for numero in range(10):
          somma_quadrati = somma_quadrati + numero**2
      print(somma_quadrati)
      
    4. Soluzione:

      prodotto = 1 # occhio che qui parto da 1!
      for numero in range(1,10):
          prodotto = prodotto * numero
      print(prodotto)
      
    5. Soluzione:

      volume_di = {
          "A":  67.0, "C":  86.0, "D":  91.0,
          "E": 109.0, "F": 135.0, "G":  48.0,
          "H": 118.0, "I": 124.0, "K": 135.0,
          "L": 124.0, "M": 124.0, "N":  96.0,
          "P":  90.0, "Q": 114.0, "R": 148.0,
          "S":  73.0, "T":  93.0, "V": 105.0,
          "W": 163.0, "Y": 141.0,
      }
      
      somma_volumi = 0
      for volume in volume_di.values():
          somma_volumi = somma_volumi + volume
      print(somma_volumi)
      
    6. Soluzione:

      volume_di = {
          "A":  67.0, "C":  86.0, "D":  91.0,
          "E": 109.0, "F": 135.0, "G":  48.0,
          "H": 118.0, "I": 124.0, "K": 135.0,
          "L": 124.0, "M": 124.0, "N":  96.0,
          "P":  90.0, "Q": 114.0, "R": 148.0,
          "S":  73.0, "T":  93.0, "V": 105.0,
          "W": 163.0, "Y": 141.0,
      }
      
      fasta = """>1BA4:A|PDBID|CHAIN|SEQUENCE
      DAEFRHDSGYEVHHQKLVFFAEDVGSNKGAIIGLMVGGVV"""
      
      # estraggo la sequenza
      sequenza = fasta.split("\n")[1]
      
      somma_volumi = 0
      
      # per ciascun carattere nella sequenza...
      for aa in sequenza:
          volume_di_aa = volume_di[aa]
          somma_volumi = somma_volumi + volume_di_aa
      
      print(somma_volumi)
      
    7. Soluzione: adatto il codice dell’esempio sopra:

      lista = [1, 25, 6, 27, 57, 12]
      
      minimo_fino_ad_ora = lista[0]
      for numero in lista[1:]:
          if numero < minimo_fino_ad_ora:
              minimo_fino_ad_ora = numero
      
      print("il minimo e':", minimo_fino_ad_ora)
      
    8. Soluzione: combino l’esempio e l’esercizio sopra:

      lista = [1, 25, 6, 27, 57, 12]
      
      massimo = lista[0]
      minimo = lista[0]
      
      for numero in lista[1:]:
          if numero > massimo:
              massimo = numero
          if numero < minimo:
              minimo = numero
      
      print("minimo =", minimo, "massimo =", massimo)
      
    9. Soluzione: range(0, len(sequenza), 3) restituisce [0, 3, 6, 9, ...], che sono le posizioni di inizio delle varie triplette.

      E’ sufficiente scrivere:

      sequenza = "ATGGCGCCCGAACAGGGA"
      
      # parto da una lista vuota
      triplette = []
      
      for pos_inizio in range(0, len(sequenza), 3):
          tripletta = sequenza[pos_inizio:pos_inizio+3]
          triplette.append(tripletta)
      
      print(triplette)
      
    10. Soluzione:

      testo = """>2HMI:A|PDBID|CHAIN|SEQUENCE
      PISPIETVPVKLKPGMDGPKVKQWPLTEEKIKALVEICTEMEKEGKISKI
      >2HMI:B|PDBID|CHAIN|SEQUENCE
      PISPIETVPVKLKPGMDGPKVKQWPLTEEKIKALVEICTEMEKEGKISKI
      >2HMI:C|PDBID|CHAIN|SEQUENCE
      DIQMTQTTSSLSASLGDRVTISCSASQDISSYLNWYQQKPEGTVKLLIYY
      >2HMI:D|PDBID|CHAIN|SEQUENCE
      QITLKESGPGIVQPSQPFRLTCTFSGFSLSTSGIGVTWIRQPSGKGLEWL
      >2HMI:E|PDBID|CHAIN|SEQUENCE
      ATGGCGCCCGAACAGGGAC
      >2HMI:F|PDBID|CHAIN|SEQUENCE
      GTCCCTGTTCGGGCGCCA"""
      
      # prima di tutto rompo il testo in righe
      righe = testo.split("\n")
      
      # creo il dizionario dove metto il risultato voluto
      sequenza_di = {}
      
      # ora posso iterare sulle varie righe
      for riga in righe:
      
          if riga[0] == ">":
              # se la riga e' un'intestazione, estraggo il nome
              # della sequenza
              nome = riga.split("|")[0]
          else:
              # altrimenti, e' la sequenza vera a propria. il
              # nome l'ho ricavato nell'iterazione precedente
              # (che corrisponde alla riga sopra nel file FASTA)
              # quindi lo posso usare per aggiornare il dizionario
              sequenza_di[nome] = riga
      
      print(sequenza_di)
      
  2. Soluzioni:

    1. Soluzione:

      while input("scrivi 'STOP': ") != "STOP":
          print("devi scrivere 'STOP'...")
      
    2. Soluzione:

      while input("scrivi stop: ").lower() != "stop":
          print("devi scrivere stop...")
      
  3. Soluzioni:

    1. Soluzione: tutti i numeri in range(10).
    2. Soluzione: il numero 0. Il break interrompe immediatamente il for.
    3. Soluzione: tutti i numeri in range(10). Il continue salta all’iterazione successiva, cosa che Python fa automaticamente quando finisce il corpo del ciclo for. Visto che continue in questo caso si trova proprio alla fine del corpo del ciclo for, e come se non ci fosse.
    4. Soluzione: il numero 0. Nella primissima iterazione, quando numero vale 0, prima Python esegue print(numero), che stampa appunto 0; poi l’if viene eseguito, e cosi’ il break che contiene, che fa interrompere immediatamente il for.
    5. Soluzione: niente. Nella primissima iterazione, quando numero vale 0, l’if viene eseguito e cosi’ il break che contiene, che fa interrompere immediatamente il for. Il print non viene mai eseguito.
    6. Soluzione: niente. Il corpo del while non viene mai eseguito, la condizione e’ False!
    7. Soluzione: niente. Visto che il corpo del while non viene mai eseguito (la condizione e’ False!), la riga condizione = True non viene mai eseguita.
    8. Soluzione: "la condizione e' vera" un numero indefinito di volte. Visto che la condizione e’ sempre True, il while non finisce mai di iterare!
    9. Soluzione: dieci stringhe della forma "all'indice 0 c'e' l'elemento 0", "all'indice 1 c'e' l'elemento 1", etc.
    10. Soluzione: tutti gli elementi di righe (processati da strip()) che vengono prima della prima riga vuota, vale a dire "riga 1", "riga 2" e "riga 3". Appena riga vale "" (il quarto elemento di righe) l’if viene eseguito, e con esso il break, che interrompe il ciclo. Si noti che la quarta riga non viene stampata.
  4. Soluzione:

    numeri = (0, 1, 1, 0, 0, 0, 1, 1, 2, 1, 2)
    
    for i in range(len(numeri)):
        numero_in_pos_i = numeri[i]
    
        if numero_in_pos_i == 2:
            print("la posizione e'", i)
            break
    
  5. Soluzione:

    stringhe = ("000", "51", "51", "32", "57", "26")
    
    for i in range(len(stringhe)):
        stringa_in_pos_i = stringhe[i]
    
        if "2" in stringa_in_pos_i:
            print("posizione =", i, "valore =", stringa_in_pos_i)
            break
    

Codice annidato

  1. Soluzione:

    n = 5
    matrice = [list(range(n)) for i in range(n)]
    
    for riga in matrice:
        for elemento in riga:
            print(elemento)
    
  2. Soluzione:

    1. Tutti gli elementi della matrice.
    2. La somma di tutti gli elementi della matrice.
    3. Di nuovo tutti gli elementi della matrice.
    4. Di nuovo tutti gli elementi della matrice.
    5. La lista degli elementi sulla diagonale.
  3. Uso due cicli for per iterare sulle coppie di elementi:

    numeri = [8, 3, 2, 9, 7, 1, 8]
    
    for numero_1 in numeri:
        for numero_2 in numeri:
            print(numero_1, numero_2)
    

    E’ molto simile all’esempio dell’orologio!

  4. Scrivo:

    numeri = [8, 3, 2, 9, 7, 1, 8]
    
    coppie_gia_stampate = []
    
    for i in range(len(numeri)):
        for j in range(len(numeri)):
    
            coppia = (numeri[i], numeri[j])
    
            # controllo se ho gia' stampato la coppia simmetrica
            if (coppia[1], coppia[0]) in coppie_gia_stampate:
                continue
    
            # se arrivo qui vuol dire che non ho gia' stampato la coppia
            # simmetrica (altrimenti avrei fatto `continue`), quindi stampo
            # la coppia; poi aggiorno coppie_gia_stampate
            print(coppia)
    
            coppie_gia_stampate.append(coppia)
    
  5. Come sopra.

  6. Soluzione:

    numeri = range(10)
    
    for elemento_1 in numeri:
        for elemento_2 in numeri:
            if 2 * elemento_1 == elemento_2:
                print(elemento_1, elemento_2)
    
  7. Soluzione:

    numeri = [8, 3, 2, 9, 7, 1, 8]
    
    for elemento_1 in numeri:
        for elemento_2 in numeri:
            if elemento_1 + elemento_2 == 10:
                print(elemento_1, elemento_2)
    
  8. Soluzione:

    numeri = [8, 3, 2, 9, 7, 1, 8]
    
    # parto da una lista vuota
    lista_delle_coppie = []
    
    for elemento_1 in numeri:
        for elemento_2 in numeri:
            if elemento_1 + elemento_2 == 10:
                # aggiorno la lista con append()
                lista_delle_coppie.append((elemento_1, elemento_2))
    
    # stampo la lista che ho appena costruito
    print(lista_delle_coppie)
    
  9. Soluzione:

    numeri_1 = [5, 9, 4, 4, 9, 2]
    numeri_2 = [7, 9, 6, 2]
    
    # qui itero sulla *prima* lista
    for i in range(len(numeri_1)):
        numero_in_pos_i = numeri_1[i]
    
        # qui itero sulla *seconda* lista
        for j in range(len(numeri_2)):
            numero_in_pos_j = numeri_2[j]
    
            if numero_in_pos_i == numero_in_pos_j:
                print("posizioni:", i, j, "; valore ripetuto:", numero_in_pos_i)
    
  10. Soluzione:

    numeri_1 = [5, 9, 4, 4, 9, 2]
    numeri_2 = [7, 9, 6, 2]
    
    # parto da una lista vuota
    lista_delle_triplette = []
    
    # qui itero sulla *prima* lista
    for i in range(len(numeri_1)):
        numero_in_pos_i = numeri_1[i]
    
        # qui itero sulla *seconda* lista
        for j in range(len(numeri_2)):
            numero_in_pos_j = numeri_2[j]
    
            if numero_in_pos_i == numero_in_pos_j:
                # al posto di stampare, aggiorno la lista
                lista_delle_triplette.append((i, j, numero_in_pos_i))
    
    # stampo la lista che ho appena costruito
    print(lista_delle_triplette)
    
  11. Soluzione:

    n = 5
    matrice = [list(range(n)) for i in range(n)]
    
    # inizializzo con il primo elemento (un qualunque altro elemento
    # andrebbe comunque bene)
    max_elemento_fino_ad_ora = matrice[0][0]
    
    # itero...
    for riga in matrice:
        for elemento in riga:
            # se trovo un elemento piu' grande di max_elemento_fino_ad_ora,
            # aggiorno quest'ultimo
            if elemento > max_elemento_fino_ad_ora:
                max_elemento_fino_ad_ora = elemento
    
    print(max_elemento_fino_ad_ora)
    
  12. Soluzione:

    sequenze = [
        "ATGGCGCCCGAACAGGGA",
        "GTCCCTGTTCGGGCGCCA",
    ]
    
    # parto da una lista vuota
    risultato = []
    
    # itero...
    for sequenza in sequenze:
        # spezzo la sequenza corrente in triplette
        triplette = []
        for i in range(0, len(sequenza), 3):
            triplette.append(sequenza[i:i+3])
    
        # appendo (*non* extend()!!!) le triplette
        # ottenute alla lista risultato
        risultato.append(triplette)
    
    # stampo la lista che ho appena costruito
    print(risultato)
    
  13. Soluzione:

    numeri = [5, 9, 4, 4, 9, 2]
    
    num_ripetizioni = {}
    
    for numero in numeri:
        if not numero in num_ripetizioni:
            num_ripetizioni[numero] = 1
        else:
            num_ripetizioni[numero] += 1
    

    o in alternativa:

    numeri = [5, 9, 4, 4, 9, 2]
    
    num_ripetizioni = {}
    
    for numero in numeri:
        if not numero in num_ripetizioni:
            num_ripetizioni[numero] = 0
        num_ripetizioni[numero] += 1
    

    oppure, sfruttando count():

    numeri = [5, 9, 4, 4, 9, 2]
    
    num_ripetizioni = {}
    
    for numero in numeri:
        if not numero in num_ripetizioni:
            num_ripetizioni[numero] = numeri.count(numero)
    

    Si noti che in quest’ultima variante, l’if (ma non il suo “contenuto”!) e’ opzionale.

    Warning

    Nella formulazione originale, l’esercizio richiedeva di usare due cicli for innestati. Una possibile soluzione a questa versione dell’esercizio e’ la seguente:

    numeri = [5, 9, 4, 4, 9, 2]
    
    num_ripetizioni = {}
    
    for numero in numeri:
        if numero in num_ripetizioni:
            continue
        else:
            num_ripetizioni[numero] = 0
            for numero_2 in numeri:
                if numero == numero_2:
                    num_ripetizioni[numero] += 1
    

    Una versione meno “ottimizzata”:

    numeri = [5, 9, 4, 4, 9, 2]
    
    num_ripetizioni = {}
    
    for numero in numeri:
        num_ripetizioni[numero] = 0
        for numero_2 in numeri:
            if numero == numero_2:
                num_ripetizioni[numero] += 1
    
  14. Soluzione:

    gruppi = [["gene1", "gene2"], ["gene3"], [], ["gene4", "gene5"]]
    
    # inizializzo con il primo gruppo
    gruppo_piu_grande_fino_ad_ora = gruppi[0]
    
    # itero...
    for gruppo in gruppi[1:]:
        if len(gruppo) > len(gruppo_piu_grande_fino_ad_ora):
            gruppo_piu_grande_fino_ad_ora = gruppo
    
    print(gruppo_piu_grande_fino_ad_ora)
    
  15. Soluzione:

    sequenze_2HMI = {
        "A": "PISPIETVPVKLKPGMDGPKVKQWPLTEEKI",
        "B": "PISPIETVPVKLKPGMDGPKVKQWPLTEEKI",
        "C": "DIQMTQTTSSLSASLGDRVTISCSASQDISS",
        "D": "QITLKESGPGIVQPSQPFRLTCTFSGFSLST",
        "E": "ATGGCGCCCGAACAGGGAC",
        "F": "GTCCCTGTTCGGGCGCCA",
    }
    
    # parto dal dizionario vuoto
    istogrammi = {}
    
    for chiave, sequenza in sequenze_2HMI.items():
    
        # associo a questa chiave un istogramma vuoto
        istogrammi[chiave] = {}
    
        for residuo in sequenza:
            if not residuo in istogrammi[chiave]:
                istogrammi[chiave][residuo] = 1
            else:
                istogrammi[chiave][residuo] += 1
    
    # stampo il risultato
    print(istogrammi)
    
    # stampo il risultato in modo piu' leggibile
    for chiave, istogramma in istogrammi.items():
        print(chiave)
        print(istogramma)
        print()
    
  16. Soluzione:

    tabella = [
        "protein domain start end",
        "YNL275W PF00955 236 498",
        "YHR065C SM00490 335 416",
        "YKL053C-A PF05254 5 72",
        "YOR349W PANTHER 353 414",
    ]
    
    # come prima cosa estraggo i nomi delle colonne dalla prima riga
    nomi_colonne = tabella[0].split()
    
    # parto da una lista vuota
    righe_come_dizionari = []
    
    # ora itero sulle altre righe
    for riga in tabella[1:]:
    
        # compilo il dizionario per questa riga
        dizionario = {}
        parole = riga.split()
        for i in range(len(parole)):
    
            # estraggo la parola corrispondente
            parola = parole[i]
    
            # estraggo il nome della colonna corrispondente
            nome_colonna = nomi_colonne[i]
    
            # aggiorno il dizionario
            dizionario[nome_colonna] = parola
    
        # ho compilato il dizionario per la riga corrente,
        # aggiorno la lista completa
        righe_come_dizionari.append(dizionario)
    
    # ho finito! stampo il risultato (una riga per volta,
    # per renderlo un po' piu' leggibile)
    for riga_come_dizionario in righe_come_dizionari:
        print(riga_come_dizionario)
    
  17. Soluzione:

    alfabeto_min = "abcdefghijklmnopqrstuvwxyz"
    alfabeto_mai = alfabeto_min.upper()
    
    # costruisco il dizionario
    min_to_mai = {}
    for i in range(len(alfabeto_min)):
        min_to_mai[alfabeto_min[i]] = alfabeto_mai[i]
    
    
    stringa = "sono una stringa"
    
    # converto la stringa
    caratteri_convertiti = []
    for carattere in stringa:
        if carattere in min_to_mai:
            # e' un carattere alfabetico, lo devo convertire
            caratteri_convertiti.append(min_to_mai[carattere])
        else:
            # non e' un carattere alfabetico, non lo converto
            caratteri_convertiti.append(carattere)
    stringa_convertita = "".join(caratteri_convertiti)
    
    print(stringa_convertita)
    
  18. Soluzione:

    righe_1 = open(input("percorso 1: ")).readlines()
    righe_2 = open(input("percoros 2: ")).readlines()
    
    # devo stare attento perche' i due file possono essere di
    # lunghezza diversa!
    max_righe = len(righe_1)
    if len(righe_2) > max_righe:
        max_righe = len(righe_2)
    
    # itero sulle righe di entrambi i file
    for i in range(max_righe):
    
        # prendo la i-esima riga del primo file,
        # sempre che esista
        if i < len(righe_1):
            riga_1 = righe_1[i].strip()
        else:
            riga_1 = ""
    
        # prendo la i-esima riga del secondo file,
        # sempre che esista
        if i < len(righe_2):
            riga_2 = righe_2[i].strip()
        else:
            riga_2 = ""
    
        print(riga_1 + " " + riga_2)
    
  19. Soluzione:

    # qui leggo il file fasta
    fasta_come_dizionario = {}
    for riga in open("data/dna-fasta/fasta.1").readlines():
    
        # mi sbarazzo di eventuali a capo
        riga = riga.strip()
    
        if riga[0] == ">":
            intestazione = riga
            fasta_come_dizionario[intestazione] = ""
    
        else:
            fasta_come_dizionario[intestazione] += riga
    
    # itero sulle coppie intestazione-sequenza
    for intestazione, sequenza in fasta_come_dizionario.items():
    
        print("processo", intestazione)
    
        # conto quante volte appare ogni nucleotide
        conta = {}
        for nucleotide in ("A", "C", "T", "G"):
            conta[nucleotide] = sequenza.count(nucleotide)
        print("le conte sono", conta)
    
        # calcolo il gc-content
        gc_content = (conta["G"] + conta["C"]) / float(len(sequenza))
        print("il GC-content e'", gc_content)
    
        # calcolo il AT/GC-ratio
        somma_at = conta["A"] + conta["T"]
        somma_cg = conta["C"] + conta["G"]
        at_gc_ratio = float(somma_at) / float(somma_cg)
        print("la AT/GC-ratio e'", at_gc_ratio)
    


Python: Funzioni

Le funzioni sono blocchi di codice a cui associamo un nome.

Per definire una nuova funzione, scrivo:

def nome_funzione(argomento_1, argomento_2, ...):

    # qui metto il codice che usa argomento_1, argomento_2,
    # etc. per calcolare il valore della variabile risultato

    return risultato

Una volta definita la funzione, la posso chiamare/invocare dal resto del codice, cosi’:

valore_ritornato = nome_funzione(valore_1, valore_2, ...)

# qui uso valore_ritornato

Esempio. Definisco una funzione che prende due argomenti (che chiamo arbitrariamente numero1 e numero2) e ne stampa la somma:

def stampa_somma(numero1, numero2):
    print("la somma e'", numero1 + numero2)

che uso cosi’:

# stampa: la somma e' 10
stampa_somma(4, 6)

# stampa: la somma e' 17
stampa_somma(5, 12)

# stampa: la somma e' 20
stampa_somma(19, 1)

Il codice qui sopra e’ del tutto equivalente a questo:

numero1 = 4
numero2 = 6
print("la somma e'", numero1 + numero2)

numero1 = 5
numero2 = 12
print("la somma e'", numero1 + numero2)

numero1 = 19
numero2 = 1
print("la somma e'", numero1 + numero2)

Warning

In stampa_somma() non c’e’ un return. Quando manca il return, il risultato della funzione e’ sempre None:

risultato = stampa_somma(19, 1) # stampa: la somma e' 20

print(risultato)                 # stampa: None

Esempio. Riscrivo la funzione stampa_somma(), che stampava la somma dei suoi argomenti, in modo che invece restituisca (con return) la somma come risultato:

def calcola_somma(numero1, numero2):
    return numero1 + numero2

Quando la chiamo succede questo:

# non stampa niente
calcola_somma(4, 6)

# non stampa niente
calcola_somma(5, 12)

# non stampa niente
calcola_somma(19, 1)

Perche’? Proviamo a riscrivere quest’ultimo codice per esteso:

numero1 = 4
numero2 = 6
numero1 + numero2

numero1 = 5
numero2 = 12
numero1 + numero2

numero1 = 19
numero2 = 1
numero1 + numero2

Qui e’ vero che effettivamente calcolo le varie somme, ma e’ anche vero che non le stampo mai: non c’e’ nessun print!

Quello che devo fare e’ mettere il risultato di calcola_somma() in una variabile, e poi stamparlo a parte, cosi’:

# non stampa niente
risultato = calcola_somma(19, 1)

# stampa 20
print(risultato)

(Oppure, abbreviando: print(calcola_somma(19, 1))). Scritto per esteso, questo codice e’ equivalente a:

numero1 = 19
numero2 = 1
risultato = numero1 + numero2
print(risultato)

Ora tutto torna: faccio la somma e ne stampo il risultato.


Warning

Il codice “contenuto” in una funzione non fa niente finche’ la funzione non viene chiamata!

Se scrivo un modulo esempio.py con questo codice:

def funzione():
    print("sto eseguendo la funzione!")

e lo eseguo:

python3 esempio.py

Python non stampera’ niente, perche’ la funzione non viene mai chiamata. Se voglio che venga eseguita, devo modificare il modulo esempio.py cosi’:

def funzione():
    print("sto eseguendo la funzione!")

funzione()  # <-- qui chiamo la funzione

Quano lo eseguo:

python3 esempio.py

Python trova la chiamata alla funzione (l’ultima riga del modulo) ed esegue la funzione: di conseguenza, stampera’:

sto eseguendo la funzione!

Esempio. Creo una funzione fattoriale() che prende un intero n e ne calcola il fattoriale n!, definito cosi’:

n! = 1 \times 2 \times 3 \times \ldots (n - 2) \times (n - 1) \times n

So farlo senza una funzione? Certo. Assumiamo di avere gia’ la variabile n. Scrivo:

fattoriale = 1
for k in range(1, n + 1):
    fattoriale = fattoriale * k

Bene. Come faccio a convertire questo codice in una funzione? Semplice:

def calcola_fattoriale(n):
    # n contiene il valore di cui voglio calcolare il fattoriale

    # qui inserisco il codice sopra
    fattoriale = 1
    for k in range(1, n+1):
        fattoriale = fattoriale * k

    # a questo punto ho calcolato il fattoriale, e lo
    # posso restituire
    return fattoriale

Ora sono libero di chiamare la funzione quante volte mi pare:

print(calcola_fattoriale(1))     # 1
print(calcola_fattoriale(2))     # 2
print(calcola_fattoriale(3))     # 6
print(calcola_fattoriale(4))     # 24
print(calcola_fattoriale(5))     # 120

o anche in modi piu’ complessi:

lista = [calcola_fattoriale(n) for n in range(10)]

Warning

  • Il nome della funzione ed il nome degli argomenti li scegliamo noi!

Quiz. Che differenza c’e’ tra questo frammento di codice:

def calcola(somma_o_prodotto, a, b):
    if somma_o_prodotto == "somma":
        return a + b
    elif:
        return a * b
    else:
        return 0

print(calcola("somma", 10, 10))
print(calcola("prodotto", 2, 2))

e questo?:

def f(operation, x, y):
    if operation == "sum":
        return x + y
    elif operation == "product":
        return x * y
    else:
        return 0

print(f("sum", 10, 10))
print(f("product", 2, 2))

Warning

  • Il codice della funzione non vede le variabili esterne alla funzione: vede solo gli argomenti!
  • Il codice della funzione puo’ restituire un risultato solo attraverso return!

Quiz. Consideriamo questo codice:

def una_funzione(a, b):
    somma = a + b
    return somma

a = 1
b = 2
somma = 3

una_funzione(100, 100)

print(somma)

Cosa viene stampato a schermo?


Esempio. Definisco due funzioni:

def leggi_fasta(percorso):
    righe = open(percorso).readlines()

    dizionario = {}
    for riga in righe:
        if riga[0] == ">":
            intestazione = riga
        else:
            sequenza = riga
            dizionario[intestazione] = sequenza

    return dizionario

def calcola_istogramma(sequenza):
    istogramma = {}
    for carattere in sequenza:
        if not carattere in  istogramma:
            istogramma[carattere] = 1
        else:
            istogramma[carattere] += 1
    return istogramma

Date le due funzioni, posso implementare un programma complesso che (1) legge un file fasta in un dizionario, (2) per ciascuna sequenza nel file fasta calcola l’istogramma dei nucleotidi, e (3) stampa ciascun istogramma a schermo:

dizionario_fasta = leggi_fasta(percorso)

for intestazione, sequenza in dizionario_fasta.items():
    istogramma = calcola_istogramma(sequenza)

    print(istogramma)

Esercizi

  1. Data la funzione:

    def funzione(arg):
        return arg
    

    di che tipo e’ il risultato delle seguenti chiamate?

    1. funzione(1)
    2. funzione({"1A3A": 123, "2B1F": 66})
    3. funzione([2*x for x in range(10)])
    4. funzione(2**-2)
  2. Data la funzione:

    def addizione(a, b):
        return a + b
    

    di che tipo e’ il risultato delle seguenti chiamate?

    1. addizione(2, 2)
    2. addizione([1,2,3], [True, False])
    3. addizione("sono una", "stringa")
  3. Creare una funzione stampa_pari_dispari() che prende un intero e stampa a schermo "pari" se il numero e’ pari e "dispari" altrimenti.

    Cosa succede se scrivo:

    risultato = stampa_pari_dispari(99)
    print(risultato)
    
  4. Creare una funzione calcola_pari_dispari() che prende un intero e restituisce la stringa "pari" se il numero e’ pari e la stringa "dispari" altrimenti.

    Cosa succede se scrivo:

    calcola_pari_dispari(99)
    
  5. Creare una funzione controlla_alfanumerico() che prende una stringa e restituisce True se la stringa e’ alfanumerica (contiene solo caratteri alfabetici o numerici) e False altrimenti.

    Per controllare se un carattere e’ alfanumerico, usare in e la stringa:: "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".

    Hint. Anche i caratteri minuscoli possono essere alfanumerici!

  6. Creare una funzione domanda() che non prende nessun argomento, chiede all’utente un percorso ad un file e stampa a schermo i contenuti del file. (Testarla ad esempio con il file data/aatable.)

  7. Creare una funzione wc() che prende una stringa e restituisce una tripla (tuple) di tre valori:

    1. Il primo elemento della coppia deve essere la lunghezza della stringa.
    2. Il secondo elemento deve essere il numero di a capo nella stringa.
    3. Il terzo elemento deve essere il numero di parole (separate da spazi o a capo) nella stringa.
  8. Creare una funzione stampa_dizionario() che prende un dizionario e stampa a schermo le coppie chiave-valore del dizionario formattate come da esempio sotto.

    Esempio: quando applico stampa_dizionario() a:

    dizionario = {
        "arginina": 0.7,
        "lisina": 0.1,
        "cisteina": 0.1,
        "istidina": 0.1,
    }
    

    che e’ un istogramma di frequenze, il risultato deve essere:

    >>> stampa_dizionario(dizionario)
    istidina -> 10.0%
    cisteina -> 10.0%
    arginina -> 70.0%
    lisina -> 10.0%
    

    Qui l’ordine in cui vengono stampate le righe non importa.

  9. Come sopra, ma le chiavi devono essere ordinate alfanumericamente:

    >>> stampa_dizionario_ordinato(dizionario)
    arginina -> 70%
    cisteina -> 10%
    istidina -> 10%
    lisina -> 10%
    

    Hint: conviene estrarre le chiavi del dizionario, ordinarle a parte, scorrere le chiavi ordinate e di volta in volta stampare la riga corrispondente.

  10. Creare una funzione crea_lista_di_fattoriali() che prende un intero n, e restituisce una lista di n elementi.

    L’i-esimo elemento deve essere il fattoriale di i.

    Ad esempio:

    >>> lista = crea_lista_di_fattoriali(5)
    >>> print(len(lista))
    5                                       # 5 elementi, come richiesto
    >>> print(lista[0])
    1                                       # e' il fattoriale di 0
    >>> print(lista[1])
    1                                       # e' il fattoriale di 1
    >>> print(lista[2])
    2                                       # e' il fattoriale di 2
    >>> print(lista[3])
    6                                       # e' il fattoriale di 3
    >>> print(lista[4])
    24                                      # e' il fattoriale di 4
    

    Hint: conviene usare la funzione fattoriale() definita in uno degli esempi precedenti per calcolare i valori della lista.

  11. Creare una funzione conta_carattere() che prende due stringhe, la prima che rappresenta testo e la seconda che rappresenta un carattere.

    La funzione deve restituire il numero di ripetizioni del carattere nel testo.

    Ad esempio:

    >>> print(conta_carattere("abbaa", "a"))
    3                                           # "a" compare 3 volte
    >>> print(conta_carattere("abbaa", "b"))
    2                                           # "b" compare 2 volte
    >>> print(conta_carattere("abbaa", "?"))
    0                                           # "?" non compare mai
    
  12. Creare una funzione conta_caratteri() che prende due stringhe, la prima che rappresenta testo e la seconda che rappresenta un tot di caratteri.

    La funzione deve restituire un dizionario, in cui le chiavi sono i caratteri da cercare, e il valore associato il loro numero di ripetizioni.

    Ad esempio:

    >>> print(conta_caratteri("abbaa", "ab?"))
    {"a": 3, "b": 2, "?": 0}
    
  13. Creare una funzione distanza() che prende due coppie (x1,y1) e (x2,y2) di punto bidimensionali e ne restituisce la distanza Euclidea.

    Hint. La distanza Euclidea e’ \sqrt{(x1-x2)^2 + (y1-y2)^2}

  14. Creare una funzione sottostringa() che date due stringhe ritorna True se la seconda e’ sottostringa della prima.

  15. Creare una funzione sottostringhe_non_vuote() che data una stringa, restituisca la lista delle sue sottostringhe non vuote.

  16. Creare una funzione conta_sottostringhe() che, date due stringhe pagliaio ed ago, ritorni il numero di ripetizioni di ago in pagliaio.

  17. Creare una funzione sottostringa_piu_lunga() che date due stringhe restituisca la loro sottostringa comune piu’ lunga.

    Hint. Si puo’ risolvere usando l’esercizio precedente!


Python: Funzioni (Soluzioni)

  1. Soluzione: lo stesso tipo del valore che gli passo! La funzione restituisce il valore dell’argomento senza toccarlo.

    1. un intero.
    2. un dizionario.
    3. una lista.
    4. un razionale.
  2. Soluzione: la somma o concatenazione dei due argomenti. Quindi:

    1. un intero.
    2. una lista.
    3. una stringa.
  3. Soluzione:

    def stampa_pari_dispari(numero):
        if numero % 2 == 0:
            print("pari")
        else:
            print("dispari")
    
    stampa_pari_dispari(98)
    stampa_pari_dispari(99)
    

    Occhio che stampa_pari_dispari() stampa gia’ da se’ a schermo, non e’ necessario fare:

    print(stampa_pari_dispari(99))
    

    altrimenti Python stampera’:

    pari
    None
    

    Il None viene dal print aggiuntivo: visto che stampa_pari_dispari() non ha return, il suo risultato e’ sempre None. Il print aggiuntivo stampa proprio questo None.

  4. Soluzione:

    def calcola_pari_dispari(numero):
        if numero % 2 == 0:
            return "pari"
        else:
            return "dispari"
    
    print(calcola_pari_dispari(98))
    print(calcola_pari_dispari(99))
    

    In questo caso invece, visto che non c’e’ nessun print in calcola_pari_dispari(), e’ necessario aggiungere a mano un print che ne stampi il risultato!

  5. Soluzione:

    def controlla_alfanumerico(stringa):
        caratteri_alfanumerici = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
    
        alfanumerica = True
        for carattere in stringa:
            if not carattere in caratteri_alfanumerici:
                alfanumerica = False
    
        return alfanumerica
    
    # testo la funzione
    print(controlla_alfanumerico("ABC123"))
    print(controlla_alfanumerico("A!%$*@"))
    

    Posso anche usare break per interrompere il ciclo for appena trovo un carattere alfanumerico (e’ impossibile che una stringa dove ho appena trovato un carattere non-alfanumerico ridiventi alfanumerica), cosi’:

    def controlla_alfanumerico(stringa):
        caratteri_alfanumerici = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
    
        alfanumerica = True
        for carattere in stringa:
            if not carattere in caratteri_alfanumerici:
                alfanumerica = False
                break # <-- esco dal for
    
        # <-- il break mi fa arrivare qui
        return alfanumerica
    
    # testo la funzione
    print(controlla_alfanumerico("ABC123"))
    print(controlla_alfanumerico("A!%$*@"))
    

    In alternativa, visto che quando faccio break arrivo direttamente al return, posso saltare un passaggio e fare direttamente return:

    def controlla_alfanumerico(stringa):
        caratteri_alfanumerici = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
    
        for carattere in stringa:
            if not carattere.upper() in caratteri_alfanumerici:
                # ho incontrato un carattere non alfanumerico
                # posso rispondere False
                return False
    
        # arrivo alla fine del for solo se non ho mai fatto `return`, il
        # che succede solo se la condizione dell'`if` e' sempre stata False
        # per tutti i caratteri: vuol dire che sono tutti caratteri alfanumerici
        # rispondo True
        return True
    
    # testo la funzione
    print(controlla_alfanumerico("ABC123"))
    print(controlla_alfanumerico("A!%$*@"))
    
  6. Soluzione:

    def domanda():
        percorso = input("scrivi un percorso: ")
        print(open(percorso).readlines())
    
    # la testo
    domanda()
    
  7. Soluzione:

    def wc(stringa):
        num_caratteri = len(stringa)
        num_a_capo = stringa.count("\n")
        num_parole = len(stringa.split()) # split rompe sia sugli spazi che sugli a-capo
        return (num_caratteri, num_a_capo, num_parole)
    
    # la testo
    print(wc("sono\nuna bella\nstringa"))
    
  8. Soluzione:

    def stampa_dizionario(un_dizionario):
        # l'ordine in cui vanno stampate le righe non importa, percio'
        # posso usare l'ordine naturale in cui mi vengono fornite da
        # `items()`
        for chiave, valore in un_dizionario.items():
            print(chiave, "->", (str(valore * 100.0) + "%"))
    
    # la testo
    
    dizionario = {
        "arginina": 0.7,
        "lisina": 0.1,
        "cisteina": 0.1,
        "istidina": 0.1,
    }
    
    stampa_dizionario(dizionario)
    
  9. Soluzione:

    def stampa_dizionario_ordinato(un_dizionario):
        # estraggo le chiavi e le ordino
        chiavi_ordinate = list(un_dizionario.keys())
        chiavi_ordinate.sort()
    
        # ora stampo le coppie chiave-valore in ordine
        for chiave in chiavi_ordinate:
            valore = un_dizionario[chiave]
    
            print(chiave, "->", (str(valore * 100.0) + "%"))
    
    # la testo
    
    dizionario = {
        "arginina": 0.7,
        "lisina": 0.1,
        "cisteina": 0.1,
        "istidina": 0.1,
    }
    
    stampa_dizionario_ordinato(dizionario)
    
  10. Soluzione:

    # presa dall'esempio
    def calcola_fattoriale(n):
        fattoriale = 1
        for k in range(1, n+1):
            fattoriale = fattoriale * k
        return fattoriale
    
    def crea_lista_di_fattoriali(n):
        lista_di_fattoriali = []
        for i in range(n):
            lista_di_fattoriali.append(calcola_fattoriale(i))
        return lista_di_fattoriali
    
    # la testo
    print(crea_lista_di_fattoriali(2))
    print(crea_lista_di_fattoriali(4))
    print(crea_lista_di_fattoriali(6))
    

    qui ho riutilizzato la funzione calcola_fattoriale() definita in uno degli esempi.

  11. Soluzione:

    def conta_carattere(testo, carattere_voluto):
        conta = 0
        for carattere in testo:
            if carattere == carattere_voluto:
                conta += 1
        return conta
    
    # la testo
    print(conta_carattere("abbaa", "a"))
    print(conta_carattere("abbaa", "b"))
    print(conta_carattere("abbaa", "?"))
    

    oppure, piu’ semplicemente, posso sfruttare count():

    def conta_carattere(testo, carattere_voluto):
        return testo.count(carattere_voluto)
    
    # la testo
    print(conta_carattere("abbaa", "a"))
    print(conta_carattere("abbaa", "b"))
    print(conta_carattere("abbaa", "?"))
    
  12. Soluzione:

    def conta_caratteri(testo, caratteri_voluti):
        conta = {}
        for carattere_voluto in caratteri_voluti:
            conta[carattere_voluto] = conta_carattere(testo, carattere_voluto)
        return conta
    
    # la testo
    print(conta_caratteri("abbaa", "ab?"))
    

    dove ho riutilizzato la funzione dell’esercizio precedente.

  13. Soluzione:

    def distanza(coppia1, coppia2):
        x1, y1 = coppia1
        x2, y2 = coppia2
    
        dx = x1 - x2
        dy = y1 - y2
    
        return (float(dx)**2 + float(dy)**2)**0.5
    
    # la testo
    print(distanza((0, 0), (1, 1)))
    print(distanza((2, 3), (3, 2)))
    
  14. Soluzione:

    def sottostringa(prima, seconda):
        return seconda in prima
    
    # la testo
    print(sottostringa("ACGT", "T"))
    print(sottostringa("ACGT", "x"))
    
  15. Soluzione:

    def sottostringhe_non_vuote(stringa):
        sottostringhe = []
    
        # tutte le possibili posizioni in cui far iniziare la sottostringa
        for inizio in range(len(stringa)):
    
            # tutte le poss. posizioni in cui far finire la sottostringa
            for fine in range(inizio + 1, len(stringa) + 1):
    
                # estraggo la sottostringa ed aggiorno la lista
                sottostringhe.append(stringa[inizio:fine])
        return sottostringhe
    
    # la testo
    print(sottostringhe_non_vuote("ACTG"))
    
  16. Soluzione:

    def conta_sottostringhe(pagliaio, ago):
        ripetizioni = 0
        for inizio in range(len(pagliaio)):
            for fine in range(inizio+1, len(pagliaio)+1):
    
                # stampo quanto vale la sottostringa, per sicurezza
                print(inizio, fine, ":", pagliaio[inizio:fine], "==", ago, "?")
    
                # controllo se la sottostringa e' uguale ad `ago`
                if pagliaio[inizio:fine] == ago:
                    print("ho trovato una ripetizione!")
                    ripetizioni += 1
    
        return ripetizioni
    
    # la testo
    print(conta_sottostringhe("ACTGXACTG", "ACTG"))
    
  17. Soluzione:

    def sottostringa_piu_lunga(stringa1, stringa2):
    
        # riutilizzo la soluzione sopra
        sottostringhe1 = sottostringhe_non_vuote(stringa1)
        sottostringhe2 = sottostringhe_non_vuote(stringa2)
    
        # controllo tra tutte le coppie di sottostringhe
        # quale e' quella piu' lunga che appare sia in
        # stringa1 che in stringa2
        piu_lunga = ""
        for sottostringa1 in sottostringhe1:
            for sottostringa2 in sottostringhe2:
    
                # se sono uguali e piu' lunghe della sottostringa
                # comune piu' lunga trovata fin'ora...
                if sottostringa1 == sottostringa2 and \
                   len(sottostringa1) > len(piu_lunga):
    
                    # aggiorno
                    piu_lunga = sottostringa1
    
        return piu_lunga
    
    # la testo
    print(sottostringa_piu_lunga("ACTG", "GCTA"))
    print(sottostringa_piu_lunga("", "ACTG"))
    print(sottostringa_piu_lunga("ACTG", ""))