...e così sono partito per la tangente e ho deciso di scrivere un programma che generasse nomi casuali di cittadine basandosi su un elenco di nomi reali: l’obiettivo era sempre il solito, ovvero impratichirmi col Python.
Lo so, apparentemente ricorda un po’ il programma PornoNomi presentato qualche settimana fa (v. Porno nomi (*1)) ma in realtà quello era un programma semplicissimo che io avevo reso arbitrariamente complicato aggiungendovi nuove funzionalità, casi particolari e usando librerie esterne (ad esempio quella per identificare aggettivi, nomi, etc…).
Era un programmino semplice perché sostanzialmente mi limitavo a scegliere un nome e un cognome a caso.
In questo caso ho invece deciso di complicare le cose: ho deciso di creare una struttura dati che, a ogni singola lettera, vi associasse tutte le possibili lettere successive (ovviamente in base alle possibilità date dalla lista di nomi di città reali). Durante la generazione di un nuovo nome, casualmente, il programma avrebbe quindi scelto la lettera successiva in base alla frequenza data dai nomi reali. Se ad esempio una “A” fosse seguita da 3 “S” e 5 “R” allora il programma sceglierebbe la “S” con probabilità 3/8 e la “R” con 5/8.
Inoltre per complicare ulteriormente le cose, ma anche per rendere i nomi generati più verosimili, ho stabilito che le parole precedute da un asterisco avrebbero dovuto essere considerate come singole lettere. Ad esempio nella lista ho inserito nomi del tipo “Montecatini *Terme” o “*Forte_dei Marmi” col risultato che il programma può generarmi “NOME-CASUALE Terme” o “Forte_dei NOME_CASUALE”.
Questo è il programma:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | import random import string class Elemento(): def __init__(self, nome): self.nome=nome self.seguiti=[] self.totSeguiti=0 def __str__(self): if self.nome=="STOP": return "" return self.nome def parsa(p): flag=False for i in range(0, len(p)): if p[i]=="*": flag=True blocco="" continue if flag: if p[i]==" ": flag=False yield blocco yield " " else: blocco=blocco+p[i] continue else: yield p[i] if flag: yield blocco inizio=Elemento("VIA") fine=Elemento("STOP") base=[inizio, fine] with open("ListaPaesi.txt") as f: content3 = f.readlines() lista_paesi=[l.rstrip('\n').upper() for l in content3] for paese in lista_paesi: corr=inizio for c in parsa(paese): val=list(filter(lambda x: x.nome==c , base)) if len(val)==0: nuovo=Elemento(c) base.append(nuovo) corr.seguiti.append((nuovo,1)) corr.totSeguiti=corr.totSeguiti+1 else: valseg=list(filter(lambda x: x[0].nome==c , corr.seguiti)) if len(valseg)==0: nuovo=val.pop() corr.seguiti.append((nuovo,1)) corr.totSeguiti=corr.totSeguiti+1 else: pa, nu =valseg.pop() corr.totSeguiti=corr.totSeguiti+1 corr.seguiti.remove((pa, nu)) corr.seguiti.append((pa, nu+1)) nuovo=pa corr=nuovo val=list(filter(lambda x: x[0].nome=="STOP" , corr.seguiti)) if len(val)==0: corr.seguiti.append((fine,1)) corr.totSeguiti=corr.totSeguiti+1 else: pa, nu =val.pop() corr.totSeguiti=corr.totSeguiti+1 corr.seguiti.remove((pa, nu)) corr.seguiti.append((pa, nu+1)) def genera(): corr=inizio while corr.totSeguiti>0: num=random.randint(1,corr.totSeguiti) for ele in corr.seguiti: num=num-ele[1] if num<=0: print(ele[0],end='') corr=ele[0] break print() for i in range(1,30): genera() |
Mi limiterò a commentare brevemente le novità Python che ho introdotto e imparato a usare.
Linea 4-12 definisco un oggetto Python che sarà l’elemento basilare della struttura dati necessaria per realizzare ciò che avevo in mente. Ogni singolo elemento, oltre al proprionome/codice ha una lista che contiene tutte le possibili continuazioni e una variabile con il loro numero totale. Ridefinisco anche la funzione __str__() perché mi faceva comodo per stampare poi il nome generato…
Linea 14-32 definisco la funzione parsa() che sfrutta il comando “yield”. Difficile spiegare tale comando: è molto “pitonesco” e non è facile spiegarne il significato senza entrare in dettagli noiosi e/o complicati. Userò la funzione per spezzettare i nomi di città negli opportuni frammenti: le singole lettere oppure le parole intere precedute da * (che viene rimosso).
Linea 38-40 inizializzo la mia struttura base con due elementi (“inizio” e “fine”) e carico in memoria la lista delle città.
Linea 42-72 il ciclo principale che per ogni nome di città caricato in memoria usa la funzione parsa() per creare la meta struttura che verrà poi usata per generare i nomi. Da notare nelle linee 45, 52 e 64 l’uso del comando lambda che definisce una funzione in linea che adopero per filtrare le liste (quella generale e quella specifica di ogni elemento).
Ah! Avrei potuto essere più furbo e aggiungere un carattere speciale (tipo “#”) alla fine di ogni parola in maniera da potermi evitare le linee di codice dopo il for più interno (64-72)...
Linea 74-84 genera() è la funzione che, usando la struttura dati costruita precedentemente, la percorre casualmente creando, passo passo, il nome casuale.
Linea 86-87 semplicemente chiama la funzione genera() 30 volte per creare altrettanti nomi casuali.
L’elenco di cittadine usato è il seguente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | Prato Livorno Arezzo Pisa Pistoia Lucca Grosseto Massa Carrara Viareggio Siena Scandicci Sesto *Fiorentino Empoli *Campi Bisenzio Capannori Cascina Piombino Camaiore *San Giuliano *Terme Rosignano *Marittimo Pontedera Poggibonsi Cecina Montelupo *San Casciano Matassino Ghezzano Calci Osmannoro Fiesole Prulli Firenze Rosano Reggello Figline Incisa *Lido_di Camaiore *Marina_di Pietrasanta Capraia Limite *Punta Ala Bibbona *Forte_dei Marmi Radda Gaiole Roselle Capalbio Cecina Barberino Poggibonsi Castiglioncello Piombino Montespertoli Volterra Certaldo Cerreto Guidi Panzano Antella Mercatale Faella Casellina Castiglion *Fiorentino Cortona Montepulciano Chianciano *Terme *Bagni *San Filippo *Terme_di Saturnia Castiglione *della Pescaia Massa *Marittima Montecatini *Terme Poppi Bibbiena Abetone *San Gimignano Montalcino Stia Tirrenia *San Miniato Camaldoli *Ponte_a Elsa |
Il risultato è il seguente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | GIA A PI TERME LLI CIMO CASSANO MO BORESTORANTO CATO SAN FIORENTINO VIOLIENILINO CAMPI TERME CA MI SANE CISISCCA CO MAPALI TERME_DI TERME GLLLDAINO BIBE PA SAN TERME LIMONATI ASA SAN CIUINORELLI FINO CIGINA GIBBIA |
Ad occhio un nome su 5 è decente. Un problema evidente sono i nomi cortissimi di 2 o addirittura 1 lettera: questo è inevitabile per la struttura del programma: qualsiasi finale di un nome di città può essere seguito dalla terminazione del nome. Volendo potrei semplicemente ricalcolare i nomi troppo corti (meno di 4 lettere) ma non è particolarmente interessante.
Un altro problema sono le sequenze impronunciabili di consonanti: vedi “CISISCCA” o “GLLLDAINO”…
Per limitare quest’ultimo problema ho scritto una seconda versione del programma: invece di considerare le singole lettere (o le sequenze precedute dall’asterisco) ho deciso di usare le coppie di lettere. Fortunatamente per implementare questo cambiamento non ho dovuto stravolgere il programma: me la sono cavato introducendo una nuova funzione di parsing, parsa2() che riusa la parsa(), e ho dovuto riscrivere la stampa della funzione di generazione in maniera da eliminare le lettere doppie.
In realtà c’è anche un piccolo baco nel codice: le sequenze precedute da “*” inserite nel mezzo di un nome vengono ripetute due volte: lascio al lettore il divertimento di correggere opportunamente il codice!
Ecco la versione modificata:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | import random import string class Elemento(): def __init__(self, nome): self.nome=nome self.seguiti=[] self.totSeguiti=0 def __str__(self): if self.nome=="STOP": return "" return self.nome def parsa2(p): primo="" secon="" for cc in parsa(p): if primo=="": primo=cc continue secondo=cc yield primo+secondo primo=secondo def parsa(p): flag=False for i in range(0, len(p)): if p[i]=="*": flag=True blocco="" continue if flag: if p[i]==" ": flag=False yield blocco yield " " else: blocco=blocco+p[i] continue else: yield p[i] if flag: yield blocco inizio=Elemento("VIA") fine=Elemento("STOP") base=[inizio, fine] with open("ListaPaesi.txt") as f: content3 = f.readlines() lista_paesi=[l.rstrip('\n').upper() for l in content3] for paese in lista_paesi: corr=inizio for c in parsa2(paese): val=list(filter(lambda x: x.nome==c , base)) if len(val)==0: nuovo=Elemento(c) base.append(nuovo) corr.seguiti.append((nuovo,1)) corr.totSeguiti=corr.totSeguiti+1 else: valseg=list(filter(lambda x: x[0].nome==c , corr.seguiti)) if len(valseg)==0: nuovo=val.pop() corr.seguiti.append((nuovo,1)) corr.totSeguiti=corr.totSeguiti+1 else: pa, nu =valseg.pop() corr.totSeguiti=corr.totSeguiti+1 corr.seguiti.remove((pa, nu)) corr.seguiti.append((pa, nu+1)) nuovo=pa corr=nuovo val=list(filter(lambda x: x[0].nome=="STOP" , corr.seguiti)) if len(val)==0: corr.seguiti.append((fine,1)) corr.totSeguiti=corr.totSeguiti+1 else: pa, nu =val.pop() corr.totSeguiti=corr.totSeguiti+1 corr.seguiti.remove((pa, nu)) corr.seguiti.append((pa, nu+1)) def possibilita(lista): num=0 for l,n in lista: print(l.nome, num, "-", num+n) num=num+n print() def genera(): corr=inizio while corr.totSeguiti>0: num=random.randint(1,corr.totSeguiti) for ele in corr.seguiti: num=num-ele[1] if num<=0: par=ele[0].nome if par!="STOP": print(par[:1] if len(par)==2 else par,end='') prec=par corr=ele[0] break if len(prec)==2: print(prec[-1:],end='') print() for i in range(1,30): genera() |
E un nuovo esempio di risultato:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | PIENA TINA CATO FAELSANZIORENIA CORETONTERTASCAREGGIULIONE DELLADELLA PESOLTELLO CASTIGNA GRO CANO PIO MARINA_DI PESCINA VIANO RASSA POPPISATO OSI CATALDO ROSMAIORNO FIORENTINO SAN GIO CANTECIARRASTO TERME CAMPI BIBONTE ARA GROSENZANO FIORENTINO MONTEDERARBERINO MARITTIMO PANTELLO CASTIA CAMAIANDI FAELLIOREGGIBBONE SCISA PI SCAMASSA |
Decisamente meglio!
Almeno le sequenze illeggibile sono sparite anche se, effettivamente, “CORETONTERTASCAREGGIULIONE DELLADELLA PESOLTELLO” (notare il baco che raddoppia il “DELLA” nel mezzo al nome) non è proprio facile da pronunciare!
Ovviamente restano i nomi cortissimi (adesso il minimo è 2 lettere) ma, come detto, questo non è un problema che mi interessa correggere.
Ah! Dalla linea 85 a 90 mi è rimasta la definizione di una funzione, possibilita(), che ho usato per sbacare il programma ma che nella sua versione finale non è usata...
Conclusione: probabilmente il nome più buffo generato dal mio programma è stato “Ano Fiorentino”: suppongo che i suoi ipotetici abitanti si chiamerebbero “stronzetti”…
Nota (*1): a dire il vero anni fa scrissi un programma ancora diverso per generare nomi casuali basato sulla scomposizione in sillabe. I nomi risultanti erano estremamente realistici e credibili ma, comunque, lo scopo di questo programma è solo prendere familiarità col Python e non che il risultato ottenuto sia particolarmente buono: per questo ho preferito cimentarmi con un metodo completamente nuovo...
Nessun commento:
Posta un commento