16.5. Enumerazione combinatoria#
In Python è abbastanza facile generare tutti i tipi di raggruppamenti visti
finora. In particolare, il modulo itertools, fornisce iteratori efficienti
che permettono di generare in sequenza tutti i possibili raggruppamenti di un
dato tipo, senza mai mantenerli tutti insieme in memoria.
Iteratori e memoria
Le funzioni di itertools restituiscono iteratori, non liste. Gli
elementi vengono generati uno alla volta solo quando richiesti, senza
mai caricare l'intera sequenza in memoria. Questo è particolarmente
vantaggioso quando \(n\) e \(k\) sono grandi: per esempio, scegliere tutte le
squadre da 5 eroi tra i 50 personaggi del Marvel Cinematic Universe
produrrebbe \(c_{50,5} = \) 2 118 760
combinazioni. Nell'ipotesi che la descrizione di una squadra richieda di
specificare un identificatore intero per ogni membro, materializzare tutte
le squadre possibili in una lista richiederebbe verosimilmente alcune decine di
megabyte di RAM, mentre l'approccio basato su iteratore manterrebbe di volta in
volta in memoria un'unica squadra — e per questo basterebbero una
ventina di byte!
Se tuttavia un'applicazione richiedesse di salvare in memoria tutte
le descrizioni, per esempio perché si deve svolgere un'elaborazione delle
squadre che non può essere fatta in modo sequenziale, gli iteratori possono
essere convertiti esplicitamente in list usando list.
16.5.1. Permutazioni semplici#
Gli oggetti della classe itertools.permutations sono degli iteratori che,
quando vengono attraversati, generano tutte le permutazioni semplici degli
elementi contenuti nell'oggetto usato per istanziare la classe stessa. Per
esempio, eseguendo la cella seguente si ottiene un elenco delle possibili
permutazioni dei Fantastici 4, analogo a quello della
12:
import itertools as it
fantastic_4 = ['f', 'i', 't', 'c']
for i, p in enumerate(it.permutations(fantastic_4)):
print(p, end='\n' if (i+1) % 3 == 0 else ' ')
('f', 'i', 't', 'c') ('f', 'i', 'c', 't') ('f', 't', 'i', 'c')
('f', 't', 'c', 'i') ('f', 'c', 'i', 't') ('f', 'c', 't', 'i')
('i', 'f', 't', 'c') ('i', 'f', 'c', 't') ('i', 't', 'f', 'c')
('i', 't', 'c', 'f') ('i', 'c', 'f', 't') ('i', 'c', 't', 'f')
('t', 'f', 'i', 'c') ('t', 'f', 'c', 'i') ('t', 'i', 'f', 'c')
('t', 'i', 'c', 'f') ('t', 'c', 'f', 'i') ('t', 'c', 'i', 'f')
('c', 'f', 'i', 't') ('c', 'f', 't', 'i') ('c', 'i', 'f', 't')
('c', 'i', 't', 'f') ('c', 't', 'f', 'i') ('c', 't', 'i', 'f')
In altre parole, il codice precedente genera tutte le \(p_4 = 24\) permutazioni dei quattro membri della squadra.
16.5.2. Permutazioni con ripetizione#
Il modulo itertools non offre la possibilità di generare direttamente tutte
le permutazioni con ripetizione di un insieme di oggetti distinguibili a
gruppi. È però abbastanza facile ottenere queste permutazioni partendo da
quelle semplici e filtrando i doppioni[1], per esempio inserendo le
permutazioni in un insieme, così da potere ignorare le loro successive
occorrenze. La cella seguente utilizza un approccio di questo tipo per produrre
un elenco simile a quello della 13:
clones = ['k', 'k', 'p', 'p', 'p']
seen = set()
for t in it.permutations(clones):
if t not in seen:
print(t, end='\n' if (len(seen)+1) % 3 == 0 else ' ')
seen.add(t)
('k', 'k', 'p', 'p', 'p') ('k', 'p', 'k', 'p', 'p') ('k', 'p', 'p', 'k', 'p')
('k', 'p', 'p', 'p', 'k') ('p', 'k', 'k', 'p', 'p') ('p', 'k', 'p', 'k', 'p')
('p', 'k', 'p', 'p', 'k') ('p', 'p', 'k', 'k', 'p') ('p', 'p', 'k', 'p', 'k')
('p', 'p', 'p', 'k', 'k')
16.5.3. Disposizioni con ripetizione#
È facile rendersi conto che le disposizioni con ripetizione degli oggetti di un insieme \(A\) in \(k \in \mathbb N\) sono esattamente gli elementi del prodotto cartesiano
e che quindi si possono ottenere direttamente generando gli elementi del
prodotto cartesiano iterato, cosa che si ottiene facilmente usando l'iteratore
restituito da itertools.product, che quando viene invocato usando il
parametro opzionale repeat permette appunto di calcolare il prodotto di
un insieme con se stesso per un dato numero di volte. La cella seguente
mostra come riprodurre in tal modo le disposizioni con ripetizione della
14.
alpha_flight = ['g', 's', 'n', 'a']
for i, d in enumerate(it.product(alpha_flight, repeat=3)):
print(d, end='\n' if (i+1) % 4 == 0 else ' ')
if i == 15:
break
('g', 'g', 'g') ('g', 'g', 's') ('g', 'g', 'n') ('g', 'g', 'a')
('g', 's', 'g') ('g', 's', 's') ('g', 's', 'n') ('g', 's', 'a')
('g', 'n', 'g') ('g', 'n', 's') ('g', 'n', 'n') ('g', 'n', 'a')
('g', 'a', 'g') ('g', 'a', 's') ('g', 'a', 'n') ('g', 'a', 'a')
Questo codice non stampa tutte le \(D_{4, 3} = 64\) disposizioni con ripetizione, perché viene forzata l'uscita dal ciclo for dopo averne considerato \(16\), per evitare un output inutilmente lungo.
16.5.4. Disposizioni senza ripetizione#
In virtù della relazione che sussiste tra le permutazioni semplici e le
disposizioni senza ripetizione, queste ultime possono essere generate
utilizzando itertools.permutations, specificando un secondo argomento che
indica il numero di posti. Ad esempio, nella cella seguente vengono prodotte
tutte le \(d_{4, 3} = 24\) disposizioni semplici evidenziate in grassetto nella
14.
for i, d in enumerate(it.permutations(alpha_flight, 3)):
print(d, end='\n' if (i+1) % 4 == 0 else ' ')
('g', 's', 'n') ('g', 's', 'a') ('g', 'n', 's') ('g', 'n', 'a')
('g', 'a', 's') ('g', 'a', 'n') ('s', 'g', 'n') ('s', 'g', 'a')
('s', 'n', 'g') ('s', 'n', 'a') ('s', 'a', 'g') ('s', 'a', 'n')
('n', 'g', 's') ('n', 'g', 'a') ('n', 's', 'g') ('n', 's', 'a')
('n', 'a', 'g') ('n', 'a', 's') ('a', 'g', 's') ('a', 'g', 'n')
('a', 's', 'g') ('a', 's', 'n') ('a', 'n', 'g') ('a', 'n', 's')
16.5.5. Combinazioni#
Il modulo itertools contiene due classi combinations e
combinations_with_replacement i cui oggetti sono iteratori sulle
combinazioni, rispettivamente semplici e con ripetizione. Per esempio, la
cella seguente stampa le combinazioni semplici di tre poteri di Peter Petrelli
(vedi Esempio 15), nel caso questi possano essere
selezionati da un gruppo di cinque superpoteri.
powers = ['telepatia','invisibilità', 'psicocinesi',
'rigenerazione', 'precognizione']
for c in it.combinations(powers, 3):
print(c)
('telepatia', 'invisibilità', 'psicocinesi')
('telepatia', 'invisibilità', 'rigenerazione')
('telepatia', 'invisibilità', 'precognizione')
('telepatia', 'psicocinesi', 'rigenerazione')
('telepatia', 'psicocinesi', 'precognizione')
('telepatia', 'rigenerazione', 'precognizione')
('invisibilità', 'psicocinesi', 'rigenerazione')
('invisibilità', 'psicocinesi', 'precognizione')
('invisibilità', 'rigenerazione', 'precognizione')
('psicocinesi', 'rigenerazione', 'precognizione')
Analogamente, il codice qui sotto genera tutte le combinazioni con ripetizione considerate nella 15.
for c in it.combinations_with_replacement(['k','p'], 4):
print(c)
('k', 'k', 'k', 'k')
('k', 'k', 'k', 'p')
('k', 'k', 'p', 'p')
('k', 'p', 'p', 'p')
('p', 'p', 'p', 'p')
Vero o falso?#
Analizzate ciascuna delle seguenti affermazioni, che riguardano le combinazioni, e dite se è vera o falsa, motivando il vostro ragionamento. Cliccate su di essa per visualizzare la risposta corretta.
itertools.permutations(A) genera tutte le permutazioni semplici degli
elementi di A.
Vera. itertools.permutations(A) produce tutte le sequenze ordinate degli
elementi di A senza ripetizioni, che corrispondono esattamente alle
permutazioni semplici. Per un insieme di \(n\) oggetti, il numero di sequenze
generate è \(p_n = n!\), come verificato nell'
Esercizio 71.
itertools.product(A, repeat=k) genera le disposizioni con ripetizione di
\(k\) elementi scelti da A.
Vera. itertools.product(A, repeat=k) restituisce il prodotto cartesiano
\(A^k\), ovvero tutte le sequenze ordinate di lunghezza \(k\) con possibilità di
ripetere gli elementi. Questo corrisponde esattamente alle disposizioni con
ripetizione \(D_{n,k} = n^k\), come mostrato nell'
Esercizio 73.
itertools.combinations(A, k) restituisce sequenze ordinate di \(k\) elementi
di A.
Falsa. itertools.combinations(A, k) genera sottoinsiemi non ordinati di
\(k\) elementi: due sequenze con gli stessi elementi in ordine diverso
corrispondono alla stessa combinazione e vengono restituite una sola volta.
Per generare sequenze ordinate bisogna usare itertools.permutations(A, k),
che produce le disposizioni semplici.
I generatori di itertools mantengono in memoria tutti gli elementi generati
per poterli restituire su richiesta.
Falsa. I generatori di itertools producono un elemento alla volta senza
memorizzare quelli precedenti, occupando quindi memoria costante indipendentemente
dal numero totale di elementi. Questo li rende efficienti anche quando il numero
combinatorio è molto grande, come discusso nell'
Esercizio 75.
itertools.permutations(A, k) con \(k < |A|\) genera le disposizioni semplici
di \(k\) elementi scelti da A.
Vera. Quando si passa un secondo argomento \(k\), itertools.permutations(A, k)
produce tutte le sequenze ordinate di \(k\) elementi distinti scelti da A,
senza ripetizioni. Il numero di tali sequenze è \(d_{n,k} = n!/(n-k)!\), che
corrisponde esattamente alla formula delle disposizioni semplici.
itertools fornisce una funzione dedicata per generare direttamente le
permutazioni con ripetizione di un multiinsieme.
Falsa. itertools non offre una funzione specifica per le permutazioni
con ripetizione. Per ottenerle bisogna generare tutte le permutazioni con
itertools.permutations e filtrare i duplicati tramite un insieme, come
mostrato nell'Esercizio 78.
16.5.6. Esercizi#
Esercizio 70 (•)
Generate e stampate tutti modi in cui è possibile assegnare tre turni di pattuglia ai cinque Teen Titans (Robin, Starfire, Raven, Beast Boy, Cyborg), eventualmente assegnando a uno stesso supereroe anche più di un turno. Contate e stampate inoltre il numero totale di turnazioni possibili.
Solution to Esercizio 70 (•)
Ogni assegnamento corrisponde a una disposizione con ripetizione dei cinque Teen Titans in tre posti: lo stesso supereroe può coprire più turni (da cui la necessità di considerare le ripetizioni), e l'ordine dei turni conta — essere assegnati al primo turno è diverso dall'essere assegnati al secondo o al terzo (da cui l'uso delle disposizioni).
titans = ['Robin', 'Starfire', 'Raven', 'Beast Boy', 'Cyborg']
count = 0
for t in it.product(titans, repeat=3):
print(t)
count += 1
print(f'In tutto ci sono {count} modi diversi di assegnare i turni.')
('Robin', 'Robin', 'Robin')
('Robin', 'Robin', 'Starfire')
('Robin', 'Robin', 'Raven')
('Robin', 'Robin', 'Beast Boy')
('Robin', 'Robin', 'Cyborg')
('Robin', 'Starfire', 'Robin')
('Robin', 'Starfire', 'Starfire')
('Robin', 'Starfire', 'Raven')
('Robin', 'Starfire', 'Beast Boy')
('Robin', 'Starfire', 'Cyborg')
('Robin', 'Raven', 'Robin')
('Robin', 'Raven', 'Starfire')
('Robin', 'Raven', 'Raven')
('Robin', 'Raven', 'Beast Boy')
('Robin', 'Raven', 'Cyborg')
('Robin', 'Beast Boy', 'Robin')
('Robin', 'Beast Boy', 'Starfire')
('Robin', 'Beast Boy', 'Raven')
('Robin', 'Beast Boy', 'Beast Boy')
('Robin', 'Beast Boy', 'Cyborg')
('Robin', 'Cyborg', 'Robin')
('Robin', 'Cyborg', 'Starfire')
('Robin', 'Cyborg', 'Raven')
('Robin', 'Cyborg', 'Beast Boy')
('Robin', 'Cyborg', 'Cyborg')
('Starfire', 'Robin', 'Robin')
('Starfire', 'Robin', 'Starfire')
('Starfire', 'Robin', 'Raven')
('Starfire', 'Robin', 'Beast Boy')
('Starfire', 'Robin', 'Cyborg')
('Starfire', 'Starfire', 'Robin')
('Starfire', 'Starfire', 'Starfire')
('Starfire', 'Starfire', 'Raven')
('Starfire', 'Starfire', 'Beast Boy')
('Starfire', 'Starfire', 'Cyborg')
('Starfire', 'Raven', 'Robin')
('Starfire', 'Raven', 'Starfire')
('Starfire', 'Raven', 'Raven')
('Starfire', 'Raven', 'Beast Boy')
('Starfire', 'Raven', 'Cyborg')
('Starfire', 'Beast Boy', 'Robin')
('Starfire', 'Beast Boy', 'Starfire')
('Starfire', 'Beast Boy', 'Raven')
('Starfire', 'Beast Boy', 'Beast Boy')
('Starfire', 'Beast Boy', 'Cyborg')
('Starfire', 'Cyborg', 'Robin')
('Starfire', 'Cyborg', 'Starfire')
('Starfire', 'Cyborg', 'Raven')
('Starfire', 'Cyborg', 'Beast Boy')
('Starfire', 'Cyborg', 'Cyborg')
('Raven', 'Robin', 'Robin')
('Raven', 'Robin', 'Starfire')
('Raven', 'Robin', 'Raven')
('Raven', 'Robin', 'Beast Boy')
('Raven', 'Robin', 'Cyborg')
('Raven', 'Starfire', 'Robin')
('Raven', 'Starfire', 'Starfire')
('Raven', 'Starfire', 'Raven')
('Raven', 'Starfire', 'Beast Boy')
('Raven', 'Starfire', 'Cyborg')
('Raven', 'Raven', 'Robin')
('Raven', 'Raven', 'Starfire')
('Raven', 'Raven', 'Raven')
('Raven', 'Raven', 'Beast Boy')
('Raven', 'Raven', 'Cyborg')
('Raven', 'Beast Boy', 'Robin')
('Raven', 'Beast Boy', 'Starfire')
('Raven', 'Beast Boy', 'Raven')
('Raven', 'Beast Boy', 'Beast Boy')
('Raven', 'Beast Boy', 'Cyborg')
('Raven', 'Cyborg', 'Robin')
('Raven', 'Cyborg', 'Starfire')
('Raven', 'Cyborg', 'Raven')
('Raven', 'Cyborg', 'Beast Boy')
('Raven', 'Cyborg', 'Cyborg')
('Beast Boy', 'Robin', 'Robin')
('Beast Boy', 'Robin', 'Starfire')
('Beast Boy', 'Robin', 'Raven')
('Beast Boy', 'Robin', 'Beast Boy')
('Beast Boy', 'Robin', 'Cyborg')
('Beast Boy', 'Starfire', 'Robin')
('Beast Boy', 'Starfire', 'Starfire')
('Beast Boy', 'Starfire', 'Raven')
('Beast Boy', 'Starfire', 'Beast Boy')
('Beast Boy', 'Starfire', 'Cyborg')
('Beast Boy', 'Raven', 'Robin')
('Beast Boy', 'Raven', 'Starfire')
('Beast Boy', 'Raven', 'Raven')
('Beast Boy', 'Raven', 'Beast Boy')
('Beast Boy', 'Raven', 'Cyborg')
('Beast Boy', 'Beast Boy', 'Robin')
('Beast Boy', 'Beast Boy', 'Starfire')
('Beast Boy', 'Beast Boy', 'Raven')
('Beast Boy', 'Beast Boy', 'Beast Boy')
('Beast Boy', 'Beast Boy', 'Cyborg')
('Beast Boy', 'Cyborg', 'Robin')
('Beast Boy', 'Cyborg', 'Starfire')
('Beast Boy', 'Cyborg', 'Raven')
('Beast Boy', 'Cyborg', 'Beast Boy')
('Beast Boy', 'Cyborg', 'Cyborg')
('Cyborg', 'Robin', 'Robin')
('Cyborg', 'Robin', 'Starfire')
('Cyborg', 'Robin', 'Raven')
('Cyborg', 'Robin', 'Beast Boy')
('Cyborg', 'Robin', 'Cyborg')
('Cyborg', 'Starfire', 'Robin')
('Cyborg', 'Starfire', 'Starfire')
('Cyborg', 'Starfire', 'Raven')
('Cyborg', 'Starfire', 'Beast Boy')
('Cyborg', 'Starfire', 'Cyborg')
('Cyborg', 'Raven', 'Robin')
('Cyborg', 'Raven', 'Starfire')
('Cyborg', 'Raven', 'Raven')
('Cyborg', 'Raven', 'Beast Boy')
('Cyborg', 'Raven', 'Cyborg')
('Cyborg', 'Beast Boy', 'Robin')
('Cyborg', 'Beast Boy', 'Starfire')
('Cyborg', 'Beast Boy', 'Raven')
('Cyborg', 'Beast Boy', 'Beast Boy')
('Cyborg', 'Beast Boy', 'Cyborg')
('Cyborg', 'Cyborg', 'Robin')
('Cyborg', 'Cyborg', 'Starfire')
('Cyborg', 'Cyborg', 'Raven')
('Cyborg', 'Cyborg', 'Beast Boy')
('Cyborg', 'Cyborg', 'Cyborg')
In tutto ci sono 125 modi diversi di assegnare i turni.
Esercizio 71 (•)
Se nell'esercizio precedente l'unica richiesta fosse stata quella di contare il numero di turnazioni differenti, senza stamparle, esisterebbero dei modi più efficienti di rispondere alla domanda?
Solution to Esercizio 71 (•)
Siccome stiamo considerando le disposizioni con ripetizione di cinque oggetti in tre posti, sappiamo che ne esistono esattamente \(D_{5, 3} = 5^3 = 125\). Questo ragionamento permette di rispondere alla domanda calcolando un solo elevamento a potenza.
Esercizio 72 (•)
Generate e stampate tutte le possibili coppie formate dai sei membri dei Watchmen (Rorschach, Nite Owl, Silk Spectre, Ozymandias, Dottor Manhattan, Comedian).
Solution to Esercizio 72 (•)
watchmen = ['Rorschach', 'Nite Owl', 'Silk Spectre',
'Ozymandias', 'Dr Manhattan', 'Comedian']
for c in it.combinations(watchmen, 2):
print(c)
('Rorschach', 'Nite Owl')
('Rorschach', 'Silk Spectre')
('Rorschach', 'Ozymandias')
('Rorschach', 'Dr Manhattan')
('Rorschach', 'Comedian')
('Nite Owl', 'Silk Spectre')
('Nite Owl', 'Ozymandias')
('Nite Owl', 'Dr Manhattan')
('Nite Owl', 'Comedian')
('Silk Spectre', 'Ozymandias')
('Silk Spectre', 'Dr Manhattan')
('Silk Spectre', 'Comedian')
('Ozymandias', 'Dr Manhattan')
('Ozymandias', 'Comedian')
('Dr Manhattan', 'Comedian')
Esercizio 73 (•)
Calcolate quante tra le combinazioni con ripetizione di Dupli-Kate e Multi-Paul in quattro posti contengono almeno un clone di Kate.
Solution to Esercizio 73 (•)
count = 0
for c in it.combinations_with_replacement(['k', 'p'], 4):
if 'k' in c:
count += 1
print(f'Esattamente {count} combinazioni contengono almeno un clone di Kate.')
Esattamente 4 combinazioni contengono almeno un clone di Kate.
Esercizio 74 (•)
Riconsiderate l'esercizio precedente, risolvendolo usando una list comprehension.
Solution to Esercizio 74 (•)
count = len([1 for c in it.combinations_with_replacement(['k', 'p'], 4)
if 'k' in c])
print(f'Esattamente {count} combinazioni contengono almeno un clone di Kate.')
Esattamente 4 combinazioni contengono almeno un clone di Kate.
Esercizio 75 (•)
Contate quante disposizioni con ripetizione dei \(21\) membri regolari della Legion of Super-Heroes in \(3\) posti soddisfano la condizione che il primo e il terzo posto siano occupati dallo stesso eroe, senza memorizzare l'intera lista.
Solution to Esercizio 75 (•)
count = sum(1 for d in it.product(range(21), repeat=3) if d[0] == d[2])
print(f'In esattamente {count} casi uno stesso eroe compare in prima '
'e ultima posizione.')
In esattamente 441 casi uno stesso eroe compare in prima e ultima posizione.
Il risultato è \(21^2 = 441\): il primo posto ha \(21\) scelte, il secondo pure, mentre il terzo è vincolato a coincidere con il primo.
Esercizio 76 (••)
Quante sono le disposizioni semplici dei quattro membri degli Alpha Flight (vedi l’Esempio 11) in tre posti che non includono Guardian? Verificate sperimentalmente la risposta a questa domanda generando tutte le disposizioni e contando quelle che lo escludono.
Solution to Esercizio 76 (••)
Escludere Guardian equivale a non considerare uno dei possibili oggetti, e quindi a focalizzarsi sulle disposizioni semplici di tre oggetti in tre posti, che sono \(d_{3, 3} = 6\), come confermato dal codice che segue.
alpha_flight = ['g', 's', 'n', 'a']
no_g = [d for d in it.permutations(alpha_flight, 3) if 'g' not in d]
print(f'Il numero di disposizioni che non include Guardian è {len(no_g)}')
Il numero di disposizioni che non include Guardian è 6
Esercizio 77 (••)
Scrivete una funzione compute_combinatorics(n, k) che, usando gli iteratori
di itertools, conta il numero di disposizioni con ripetizione,
disposizioni semplici e combinazioni semplici di n oggetti in k posti. La
funzione deve restituire un dizionario le cui chiavi sono le stringhe 'D',
'd' e 'c', associate rispettivamente alle disposizioni con ripetizione,
alle disposizioni semplici e alle combinazioni. Verificate che la funzione si
comporti correttamente nel caso in cui gli argomenti n e k sono
rispettivamente uguali a \(5\) e \(3\).
Solution to Esercizio 77 (••)
Sebbene i valori richiesti si possano calcolare direttamente, viene richiesto esplicitamente di utilizzare gli iteratori. È quindi possibile usare dei generatori a partire da questi ultimi, che producono il valore costante \(1\) per ogni raggruppamento considerato. Sommando i valori generati si ottengono i conteggi richiesti.
Il testo dell'esercizio non specifica quali oggetti considerare, ma solo
quanti. Siccome il calcolo degli indici combinatorici non dipende dalla
specifica natura degli oggetti, possiamo usare gli interi da \(0\) al valore
contenuto in n, che vengono prodotti da range in modo efficiente.
def compute_combinatorics(n, k):
objects = range(n)
disp_repeat = sum(1 for _ in it.product(objects, repeat=k))
disp_simple = sum(1 for _ in it.permutations(objects, k))
comb = sum(1 for _ in it.combinations(objects, k))
return {'D': disp_repeat, 'd': disp_simple, 'c': comb}
Applicando le formule corrispondenti ai due tipi di disposizione e alle combinazioni, si ottiene facilmente che \(D_{5, 3} = 125\), \(d_{5, 3} = 60\) e \(c_{5, 3} = 10\), cosa che possiamo verificare tramite delle semplici asserzioni.
result = compute_combinatorics(5, 3)
assert result['D'] == 125
assert result['d'] == 60
assert result['c'] == 10
Esercizio 78 (••)
Generate tutte le permutazioni degli oggetti nel multiinsieme che contiene due cloni di Dupli-Kate e tre di Multi-Paul, inserendo in un insieme tutte le permutazioni semplici.
Solution to Esercizio 78 (••)
Nel paragrafo sulle permutazioni con ripetizione, le permutazioni venivano inserite in un insieme dopo essere state utilizzate, per evitare di riconsiderarle in seguito. Qui, invece, viene richiesto di utilizzarlo per aggregare le permutazioni, magari per un utilizzo futuro. Il modo più intuitivo di procedere è quello di enumerare le permutazioni semplici, inserendole tutte nell'insieme: questa operazione gestisce automaticamente la presenza di doppioni, perché l'inserimento in un insieme di un elemento che già vi appartiene non ha alcun effetto.
clones = ['k', 'k', 'p', 'p', 'p']
distinct = set()
for p in it.permutations(clones):
distinct.add(p)
print(distinct)
{('k', 'p', 'k', 'p', 'p'), ('p', 'k', 'p', 'k', 'p'), ('p', 'p', 'k', 'p', 'k'), ('k', 'p', 'p', 'k', 'p'), ('p', 'p', 'p', 'k', 'k'), ('k', 'p', 'p', 'p', 'k'), ('p', 'k', 'k', 'p', 'p'), ('p', 'k', 'p', 'p', 'k'), ('p', 'p', 'k', 'k', 'p'), ('k', 'k', 'p', 'p', 'p')}
In realtà, è possibile riscrivere questo codice in modo molto più succinto. Infatti, i costruttori di quasi tutti i tipi di dati strutturati che abbiamo considerato accettano come argomenti anche degli iteratori, che vengono automaticamente attraversati, inserendo via via gli elementi generati nella struttura. Nel nostro caso, ciò permette di riscrivere la cella precedente nel modo indicato di seguito.
clones = ['k', 'k', 'p', 'p', 'p']
distinct = set(it.permutations(clones))
print(distinct)
{('k', 'p', 'k', 'p', 'p'), ('p', 'k', 'p', 'k', 'p'), ('p', 'p', 'k', 'p', 'k'), ('k', 'p', 'p', 'k', 'p'), ('p', 'p', 'p', 'k', 'k'), ('k', 'p', 'p', 'p', 'k'), ('p', 'k', 'k', 'p', 'p'), ('p', 'k', 'p', 'p', 'k'), ('p', 'p', 'k', 'k', 'p'), ('k', 'k', 'p', 'p', 'p')}
Esercizio 79 (••)
Leggete la
documentazione
della classe itertools.islice, e utilizzatela per generare e stampare
solamente dieci permutazioni semplici dei cinque membri dei
Doom Patrol (Robotman, Negative Man,
Elasti-Girl, Crazy Jane, Flex Mentallo), senza generare né scorrere le
restanti \(5! - 10 = 110\) permutazioni.
Solution to Esercizio 79 (••)
La classe islice costruisce un iteratore a partire da un altro iteratore,
estrandone una sottosequenza. Nella sua forma più semplice, il costruttore
accetta l'iteratore di partenza e un numero intero \(n\): l'iteratore
restituito produrrà solamente i primi \(n\) elementi di quello originale.
doom_patrol = ['Robotman', 'Negative Man', 'Elasti-Girl',
'Crazy Jane', 'Flex Mentallo']
for p in it.islice(it.permutations(doom_patrol), 10):
print(p)
('Robotman', 'Negative Man', 'Elasti-Girl', 'Crazy Jane', 'Flex Mentallo')
('Robotman', 'Negative Man', 'Elasti-Girl', 'Flex Mentallo', 'Crazy Jane')
('Robotman', 'Negative Man', 'Crazy Jane', 'Elasti-Girl', 'Flex Mentallo')
('Robotman', 'Negative Man', 'Crazy Jane', 'Flex Mentallo', 'Elasti-Girl')
('Robotman', 'Negative Man', 'Flex Mentallo', 'Elasti-Girl', 'Crazy Jane')
('Robotman', 'Negative Man', 'Flex Mentallo', 'Crazy Jane', 'Elasti-Girl')
('Robotman', 'Elasti-Girl', 'Negative Man', 'Crazy Jane', 'Flex Mentallo')
('Robotman', 'Elasti-Girl', 'Negative Man', 'Flex Mentallo', 'Crazy Jane')
('Robotman', 'Elasti-Girl', 'Crazy Jane', 'Negative Man', 'Flex Mentallo')
('Robotman', 'Elasti-Girl', 'Crazy Jane', 'Flex Mentallo', 'Negative Man')
Esercizio 80 (••)
Stampate tutte le combinazioni di tre superpoteri, scelti tra telepatia, invisibilità, psicocinesi, rigenerazione e precognizione, escludendo quelle che contengono simultaneamente telepatia e psicocinesi. Quante sono queste combinazioni?
Solution to Esercizio 80 (••)
powers = ['telepatia', 'invisibilità', 'psicocinesi',
'rigenerazione', 'precognizione']
valid = [c for c in it.combinations(powers, 3)
if not ('telepatia' in c and 'psicocinesi' in c)]
print(f'Vi sono {len(valid)} combinazioni valide.')
Vi sono 7 combinazioni valide.
Il numero di combinazioni valide si può ottenere anche ragionando nel modo seguente: partendo dalle \(c_{5, 3} = 10\) combinazioni totali, vanno sottratte tutte quelle nelle quali due dei tre posti sono occupati da telepatia e psicocinesi; siccome l'unico posto rimanente potrà contenere uno tra i superpoteri restanti, vi sono esattamente tre combinazioni da rimuovere. Pertanto, avremo sette combinazioni valide.
Esercizio 81 (•••)
Scrivete un generatore Python che, dato un elenco di eroi e un intero \(k\), produca tutte le coppie di squadre disgiunte di \(k\) eroi ciascuna. Verificate il comportamento del generatore utilizzando i nove Young Avengers (Patriot, Hulkling, Wiccan, Speed, Stature, Vision, Kate Bishop, Noh-Varr e Jonas) e \(k = 2\), tenendo conto che il numero di coppie di squadre — in questo caso — deve essere uguale a \(c_{9,2} \cdot c_{7,2} = 36 \cdot 21 = 756\).
Solution to Esercizio 81 (•••)
def disjoint_teams(heroes, k):
for team_1 in it.combinations(heroes, k):
remaining = [e for e in heroes if e not in team_1]
for team_2 in it.combinations(remaining, k):
yield team_1, team_2
young_avengers = ['Patriot', 'Hulkling', 'Wiccan', 'Speed',
'Stature', 'Vision', 'Kate Bishop', 'Noh-Varr', 'Jonas']
num_pairs = sum(1 for _ in disjoint_teams(young_avengers, 2))
assert num_pairs == 756
16.5.7. Concetti fondamentali#
Pensate ai concetti fondamentali illustrati in questo paragrafo ed elencateli. Nella lista qui sotto sono già presenti gli stessi concetti, ma nascosti. Cliccate sul pulsante per mostrarli sotto forma di parole chiave, quindi confrontateli con la vostra lista. Provate infine a descrivere ciascun concetto con parole vostre.