Domanda Aiuto script python

ItsReal

Utente Electrum
30 Dicembre 2014
114
30
9
104
Salve a tutti, ho creato uno script per "decifrare" delle stringhe di testo in un file.
Praticamente se in questa stringa ci sono almeno 3 vocali, almeno una doppia lettera e non ci sono uno dei seguenti caratteri 'ab', 'cd', 'pq', 'xy' uno dopo l'altro la stringa risulta valida, altrimenti no.
Io ho provato a fare in questo modo

Python:
with open("input.txt", "r") as f:
    line = f.readline()

final_valid = 0

def decrypt(riga):
    global final_valid
    vocal_counter = 0
    letters_in_a_row = 0
    string_not_allow = 0
    not_allow = ['ab', 'cd', 'pq', 'xy']
    counter = 0
    counter_list = 0
    lista = []

    for letter in riga:
        lista.append(letter)

        if letter in 'aeiou':
            vocal_counter += 1

        counter += 1

    for l in lista:
        try:
            if counter_list < len(lista):
                if lista[counter_list] == lista[counter_list + 1]:
                    letters_in_a_row += 1

                if lista[counter_list] + lista[counter_list + 1] in not_allow:
                    string_not_allow += 1

                counter_list += 1

        except IndexError:
            continue

    if vocal_counter >= 3:
        if letters_in_a_row >= 1:
            if string_not_allow == 0:
                final_valid += 1



with open("input.txt", "r") as f2:
    code = f2.readlines()

for n in code:
    decrypt(n)

print(f"Valid lines: {final_valid}")

C'e' possibilita' per rendere il codice più veloce?
 
Ciao,
ci sono varie cose che puoi fare:
  • Non leggere tutto il file in memoria, ma una riga alla volta
  • Hai una condizione che appena è vera, tutta la linea non vale, quindi puoi semplicemente tornare immediatamente invece che continuare ad analizzare la linea
  • Leggi la linea multiple volte, basta un passaggio solo
  • Quando possibile, usa metodi nativi
  • Puoi considerare l'uso del multithreading, puoi usare un thread per ogni processore (virtuale) che hai, perché è un problema facilmente parallelizzabile
  • Se il codice è nella "critical path", ti conviene riscriverlo in un altro linguaggio ;-)
Immagino che le cose che ti interessino siano le prime tre, il multithreading è interessante ma devi studiartelo prima, e immagino che questo sia un esercizio formativo, e non veramente la critical path di un vero codice

Quindi, ho provato a scrivere il codice tenendo in mente queste considerazioni:

Python:
NOT_ALLOWED = ['ab', 'cd', 'pq', 'xy']

def is_line_valid(line: str) -> bool:
    # Controlliamo subito se nella linea c'è una stringa vietata, cosi
    # possiamo ritornare immediatamente senza controllare il resto
    if any(syllable in line for syllable in NOT_ALLOWED):
        return False
    
    vocal_counter = 0
    found_repeated_letter = False

    # Se siamo arrivati qua, dobbiamo contare il numero di vocali, e il numero
    # di lettere ripetute. Per avere sia l'indice che la lettera, usiamo la
    # funzione enumerate()
    for idx, letter in enumerate(line):
        if letter in 'aeiou':
            vocal_counter += 1
        
        # Il primo check ci permette di andare fuori indice
        if idx + 1 < len(line) and letter == line[idx + 1]:
            found_repeated_letter = True

        if found_repeated_letter and vocal_counter >= 3:
            return True # Appena abbiamo soddisfatto tutte le richieste,
                        # ritorniamo True per evitare di dover analizzare
                        # il resto della linea
    
    # Se arriviamo qua, vuol dire che abbiamo analizzato tutta la linea e non
    # abbiamo trovato abbastanza vocali o lettere ripetute, quindi possiamo
    # ritornare False
    return False

with open("input.txt", "r") as f:
    valid_lines = 0
    for line in f:
        valid_lines += 1 if is_line_valid(line) else 0

    print(f"Valid lines: {valid_lines}")


Ogni volta che si fanno delle ottimizzazioni per le performance, bisogna misurarle per vedere se sono effettive.
Generiamo un file random da 100MB, con linee da 76 caratteri, e usiamolo come input per il mio e per il tuo script (ora, questo non è una maniera particolarmente effettiva per misurare, sopratutto quando le variazioni sono piccole, perché sul mio PC stanno andando molte altre cose, quindi non è un benchmark effettivo. Però, come in questo caso, se le variazioni sono grandi, si notano anche nel rumore di sottofondo).


Bash:
/tmp/test
➜ base64 /dev/urandom | head -c 100000000 > input.txt

/tmp/test
➜ time python3 old_code.py                           
Valid lines: 786899
python3 old_code.py  25,99s user 0,12s system 100% cpu 26,074 total

/tmp/test took 27s
➜ time python3 count.py                               
Valid lines: 786899
python3 count.py  8,09s user 0,02s system 100% cpu 8,095 total

Come vedi, 3 volte più veloce, più o meno. Se aggiungessimo il multithreading, in base a quanti processori hai potresti vedere miglioramenti notevoli (io ho 8 processori, mi aspetto un ulteriore 5-6 volte di miglioramento)

Fammi sapere se hai domande!
 
Aggiungo solo un paio di cose all'intervento perfetto di @fennek. Il Python per fare multithreading non va bene, è un multithreading simulato, non reale. Consiglio vivamente di usare il C per queste operazioni. È il linguaggio più adatto per eseguire compiti a basso livello ed esistono già API specifiche per il tutto come OpenMP.
 
  • Mi piace
Reazioni: fennek
@CrazyMonk, apri un discorso molto interessante :) Come funziona il Global Interpreter Lock (per gli amici GIL) in Python è importante da sapere, ha le sue motivazioni, e comunque rimane sempre fonte di discussione 🤣

Per quanto riguarda i linguaggi, è vero che con alcuni ottieni cose più performanti, ma nel 99.9% dei casi non è il linguaggio il bottleneck. Con Python ho messo in produzione servizi HTTP con un p99 di risposta sotto al centesimo di secondo per circa 1000 req/s. Chiaramente, pesantemente ottimizzato e studiato apposta. Con Go, p99 sotto il millesimo di secondo (escape analisi fondamentale qua).

Se invece sei nella critical path di qualcosa eseguito centinaia di migliaia di volte al secondo, e devi tenere il p99 sotto i 100 microsecondi, allora ci sta che devi usare C, o più probabilmente codice assembly ottimizzato sui processori che metti in produzione (e li si apre tutto un altro discorso sulle ottimizzazioni di rete e di infrastruttura che devi fare)
 
  • Love
Reazioni: --- Ra ---
Ciao,
ci sono varie cose che puoi fare:
  • Non leggere tutto il file in memoria, ma una riga alla volta
  • Hai una condizione che appena è vera, tutta la linea non vale, quindi puoi semplicemente tornare immediatamente invece che continuare ad analizzare la linea
  • Leggi la linea multiple volte, basta un passaggio solo
  • Quando possibile, usa metodi nativi
  • Puoi considerare l'uso del multithreading, puoi usare un thread per ogni processore (virtuale) che hai, perché è un problema facilmente parallelizzabile
  • Se il codice è nella "critical path", ti conviene riscriverlo in un altro linguaggio ;-)
Immagino che le cose che ti interessino siano le prime tre, il multithreading è interessante ma devi studiartelo prima, e immagino che questo sia un esercizio formativo, e non veramente la critical path di un vero codice

Quindi, ho provato a scrivere il codice tenendo in mente queste considerazioni:

Python:
NOT_ALLOWED = ['ab', 'cd', 'pq', 'xy']

def is_line_valid(line: str) -> bool:
    # Controlliamo subito se nella linea c'è una stringa vietata, cosi
    # possiamo ritornare immediatamente senza controllare il resto
    if any(syllable in line for syllable in NOT_ALLOWED):
        return False
   
    vocal_counter = 0
    found_repeated_letter = False

    # Se siamo arrivati qua, dobbiamo contare il numero di vocali, e il numero
    # di lettere ripetute. Per avere sia l'indice che la lettera, usiamo la
    # funzione enumerate()
    for idx, letter in enumerate(line):
        if letter in 'aeiou':
            vocal_counter += 1
       
        # Il primo check ci permette di andare fuori indice
        if idx + 1 < len(line) and letter == line[idx + 1]:
            found_repeated_letter = True

        if found_repeated_letter and vocal_counter >= 3:
            return True # Appena abbiamo soddisfatto tutte le richieste,
                        # ritorniamo True per evitare di dover analizzare
                        # il resto della linea
   
    # Se arriviamo qua, vuol dire che abbiamo analizzato tutta la linea e non
    # abbiamo trovato abbastanza vocali o lettere ripetute, quindi possiamo
    # ritornare False
    return False

with open("input.txt", "r") as f:
    valid_lines = 0
    for line in f:
        valid_lines += 1 if is_line_valid(line) else 0

    print(f"Valid lines: {valid_lines}")


Ogni volta che si fanno delle ottimizzazioni per le performance, bisogna misurarle per vedere se sono effettive.
Generiamo un file random da 100MB, con linee da 76 caratteri, e usiamolo come input per il mio e per il tuo script (ora, questo non è una maniera particolarmente effettiva per misurare, sopratutto quando le variazioni sono piccole, perché sul mio PC stanno andando molte altre cose, quindi non è un benchmark effettivo. Però, come in questo caso, se le variazioni sono grandi, si notano anche nel rumore di sottofondo).


Bash:
/tmp/test
➜ base64 /dev/urandom | head -c 100000000 > input.txt

/tmp/test
➜ time python3 old_code.py                          
Valid lines: 786899
python3 old_code.py  25,99s user 0,12s system 100% cpu 26,074 total

/tmp/test took 27s
➜ time python3 count.py                              
Valid lines: 786899
python3 count.py  8,09s user 0,02s system 100% cpu 8,095 total

Come vedi, 3 volte più veloce, più o meno. Se aggiungessimo il multithreading, in base a quanti processori hai potresti vedere miglioramenti notevoli (io ho 8 processori, mi aspetto un ulteriore 5-6 volte di miglioramento)

Fammi sapere se hai domande!
Il tuo codice mi è chiaro, l'unica cosa che mi è nuova è la funzione any che presumo controlli se almeno uno dei valori si trovi all'interno della stringa.
Ho trovato una soluzione alternativa al controllo delle doppie lettere:

Python:
found_repeated_letter = False

for i in range(len(s-1):    # s = stringa
    pair = s[i:i+2]
    if s.count(pair) >= 1:
        found_repeated_letter = True
        break

mi rimane il dubbio di come count possa contare il valore di pair se tale valore cambia ad ogni iterazione.
 
Ultima modifica:
Il tuo codice mi è chiaro, l'unica cosa che mi è nuova è la funzione any che presumo controlli se almeno uno dei valori si trovi all'interno della stringa.
Ho trovato una soluzione alternativa al controllo delle doppie lettere:

Python:
found_repeated_letter = False

for i in range(len(s-1):    # s = stringa
    pair = s[i:i+2]
    if s.count(pair) >= 1:
        found_repeated_letter = True
        break

mi rimane il dubbio di come count possa contare il valore di pair se tale valore cambia ad ogni iterazione.
Ciao, quello che vuoi fare è verificare se due lettere consecutive sono uguali, giusto? In questo caso ti basta fare una verifica globale, che si trasforma facilmente in una verifica locale analizzando le lettere in coppie. La tua idea è giusta, ma il tuo codice non funziona. Puoi fare qualcosa del genere invece ;)
Python:
def contaDoppie(stringa):
    for i in range(len(stringa)-1):
        if (stringa[i] == stringa[i+1]):
            return True
    return False

Fai programmazione non strutturata/greedy e riporti True non appena la condizione viene soddisfatta.
Se vuoi far funzionare il tuo codice, invece, che ha una logica un po' più inutilmente macchinosa devi riscriverlo così:
Python:
found_repeated_letter = False

for i in range(len(s)-1):    # s = stringa
    pair = s[i:i+2]
    if s.count(pair[i]) > 1:
        found_repeated_letter = True
        break
print(found_repeated_letter)

Gli errori che avevi commesso erano tre: lo scorretto utilizzo della funzione len() ,della funzione count() e la condizione dell'if che deve essere strettamente maggiore di 1. Se hai altri dubbi chiedi pure.
 
Ultima modifica:
Visto che i benchmark mi intrippano, posto anche la mia soluzione in due versioni differenti.

Versione che legge la stringa una volta sola:
Python:
def is_valid(line):
    vowels, doubles, previous = 0, 0, ''
    for current in line:
        if previous == 'a' and current == 'b': return False
        if previous == 'c' and current == 'd': return False
        if previous == 'p' and current == 'q': return False
        if previous == 'x' and current == 'y': return False
        vowels += current in "aeiou"
        doubles += previous == current
        previous = current
    return vowels >= 3 and doubles > 0
Versione che fa un test per volta, ma legge la stringa tante volte:
Python:
def is_valid(line):
    if any(pair in line for pair in ['ab', 'cd', 'pq', 'xy']): return False
    if sum(line.count(vowel) for vowel in "aeiou") < 3: return False
    if not any(a == b for a, b in zip(line, line[1:])): return False
    return True

Nota che la seconda versione potrebbe essere più o meno veloce cambiando l'ordine dei tre if.
 
  • Mi piace
Reazioni: --- Ra ---
Visto che i benchmark mi intrippano, posto anche la mia soluzione in due versioni differenti.

Versione che legge la stringa una volta sola:
Python:
def is_valid(line):
    vowels, doubles, previous = 0, 0, ''
    for current in line:
        if previous == 'a' and current == 'b': return False
        if previous == 'c' and current == 'd': return False
        if previous == 'p' and current == 'q': return False
        if previous == 'x' and current == 'y': return False
        vowels += current in "aeiou"
        doubles += previous == current
        previous = current
    return vowels >= 3 and doubles > 0
Versione che fa un test per volta, ma legge la stringa tante volte:
Python:
def is_valid(line):
    if any(pair in line for pair in ['ab', 'cd', 'pq', 'xy']): return False
    if sum(line.count(vowel) for vowel in "aeiou") < 3: return False
    if not any(a == b for a, b in zip(line, line[1:])): return False
    return True

Nota che la seconda versione potrebbe essere più o meno veloce cambiando l'ordine dei tre if.
return vowels >= 3 and doubles > 0
Il return torna True se è vera la condizione?
Messaggio unito automaticamente:

Ciao,
ci sono varie cose che puoi fare:
  • Non leggere tutto il file in memoria, ma una riga alla volta
  • Hai una condizione che appena è vera, tutta la linea non vale, quindi puoi semplicemente tornare immediatamente invece che continuare ad analizzare la linea
  • Leggi la linea multiple volte, basta un passaggio solo
  • Quando possibile, usa metodi nativi
  • Puoi considerare l'uso del multithreading, puoi usare un thread per ogni processore (virtuale) che hai, perché è un problema facilmente parallelizzabile
  • Se il codice è nella "critical path", ti conviene riscriverlo in un altro linguaggio ;-)
Immagino che le cose che ti interessino siano le prime tre, il multithreading è interessante ma devi studiartelo prima, e immagino che questo sia un esercizio formativo, e non veramente la critical path di un vero codice

Quindi, ho provato a scrivere il codice tenendo in mente queste considerazioni:

Python:
NOT_ALLOWED = ['ab', 'cd', 'pq', 'xy']

def is_line_valid(line: str) -> bool:
    # Controlliamo subito se nella linea c'è una stringa vietata, cosi
    # possiamo ritornare immediatamente senza controllare il resto
    if any(syllable in line for syllable in NOT_ALLOWED):
        return False
   
    vocal_counter = 0
    found_repeated_letter = False

    # Se siamo arrivati qua, dobbiamo contare il numero di vocali, e il numero
    # di lettere ripetute. Per avere sia l'indice che la lettera, usiamo la
    # funzione enumerate()
    for idx, letter in enumerate(line):
        if letter in 'aeiou':
            vocal_counter += 1
       
        # Il primo check ci permette di andare fuori indice
        if idx + 1 < len(line) and letter == line[idx + 1]:
            found_repeated_letter = True

        if found_repeated_letter and vocal_counter >= 3:
            return True # Appena abbiamo soddisfatto tutte le richieste,
                        # ritorniamo True per evitare di dover analizzare
                        # il resto della linea
   
    # Se arriviamo qua, vuol dire che abbiamo analizzato tutta la linea e non
    # abbiamo trovato abbastanza vocali o lettere ripetute, quindi possiamo
    # ritornare False
    return False

with open("input.txt", "r") as f:
    valid_lines = 0
    for line in f:
        valid_lines += 1 if is_line_valid(line) else 0

    print(f"Valid lines: {valid_lines}")


Ogni volta che si fanno delle ottimizzazioni per le performance, bisogna misurarle per vedere se sono effettive.
Generiamo un file random da 100MB, con linee da 76 caratteri, e usiamolo come input per il mio e per il tuo script (ora, questo non è una maniera particolarmente effettiva per misurare, sopratutto quando le variazioni sono piccole, perché sul mio PC stanno andando molte altre cose, quindi non è un benchmark effettivo. Però, come in questo caso, se le variazioni sono grandi, si notano anche nel rumore di sottofondo).


Bash:
/tmp/test
➜ base64 /dev/urandom | head -c 100000000 > input.txt

/tmp/test
➜ time python3 old_code.py                          
Valid lines: 786899
python3 old_code.py  25,99s user 0,12s system 100% cpu 26,074 total

/tmp/test took 27s
➜ time python3 count.py                              
Valid lines: 786899
python3 count.py  8,09s user 0,02s system 100% cpu 8,095 total

Come vedi, 3 volte più veloce, più o meno. Se aggiungessimo il multithreading, in base a quanti processori hai potresti vedere miglioramenti notevoli (io ho 8 processori, mi aspetto un ulteriore 5-6 volte di miglioramento)

Fammi sapere se hai domande!
Questo è un codice scritto nel terminale?

Bash:
Codice:
/tmp/test
➜ base64 /dev/urandom | head -c 100000000 > input.txt

/tmp/test
➜ time python3 old_code.py                          
Valid lines: 786899
python3 old_code.py  25,99s user 0,12s system 100% cpu 26,074 total

/tmp/test took 27s
➜ time python3 count.py                              
Valid lines: 786899
python3 count.py  8,09s user 0,02s system 100% cpu 8,095 total