2016-02-02 12 views
12

Se hanno una lista in questo modo:modo veloce di stringhe di attraversamento in una lista

shops=['A','B','C','D'] 

e vorrebbe creare i seguenti nuovi elenchi (io attraverso ogni elemento con tutti gli altri e creare una stringa in cui prima parte è alfanumerico prima del secondo):

['A-B', 'A-C', 'A-D'] 

['A-B', 'B-C', 'B-D'] 

['A-C', 'B-C', 'C-D'] 

['A-D', 'B-D', 'C-D'] 

ho qualcosa di simile a questo:

for a in shops: 
    cons = [] 
    for b in shops: 
     if a!=b: 
      con = [a,b] 
      con = sorted(con, key=lambda x: float(x)) 
      cons.append(con[0]+'-'+con[1]) 
    print(cons) 

Tuttavia, questo è pr lento per grandi elenchi (ad es. 1000 dove ho 1000 * 999 * 0,5 uscite). Stavo cercando un modo più efficiente per farlo?

Avrei potuto usare una clausola if-else per l'ordinamento, ad es.

for a in shops: 
    cons = [] 
    for b in shops: 
     if a<b: 
      cons.append(a+"-"+b) 
     elif a>b: 
      cons.append(b+"-"+a) 
    print(cons) 

Il che, non ho ancora cronometrato - ma ho pensato che il principale rallentamento è stato il doppio ciclo for

+0

Perché non '('a_b', 'A_C', 'B_C')'? – Kasramvd

+2

'key = lambda x: float (x)' è lo stesso - solo più lento - come 'key = float' –

+1

Non so cosa si può ottenere la complessità complessiva di questi algoritmi verso il basso, dato che devi generare tutto quelle combinazioni. Puoi solo eseguire la micro-tuning (come nel non definire inutili lambda). Tuttavia: per cosa hai bisogno di queste combinazioni, in primo luogo? Forse c'è un modo migliore, ad es. solo generando le combinazioni, o senza l'ordinamento. –

risposta

9

È possibile creare una lista annidata-di comprensione con alcuni controlli aggiuntivi:

>>> shops=['A','B','C','D'] 
>>> [["-".join((min(a,b), max(a,b))) for b in shops if b != a] for a in shops] 
[['A-B', 'A-C', 'A-D'], 
['A-B', 'B-C', 'B-D'], 
['A-C', 'B-C', 'C-D'], 
['A-D', 'B-D', 'C-D']] 

Si noti che questo non sarà probabilmente molto più veloce di codice, come si devono ancora generare tutte quelle combinazioni. In pratica, si potrebbe fare un generatore di espressione, quindi gli elementi non vengono generati tutti in una volta, ma solo "se necessario":

gen = (["-".join((min(a,b), max(a,b))) for b in shops if b != a] for a in shops) 
for item in gen: 
    print(item) 

Aggiornamento: Ho fatto qualche analisi temporale mediante IPython di %timeit. La tua seconda implementazione è la più veloce. Testato con un elenco di 100 stringhe (map(str, range(100))) e dopo aver trasformato ciascuno dei metodi in generatori.

In [32]: %timeit list(test.f1())   # your first implementation 
100 loops, best of 3: 13.5 ms per loop 

In [33]: %timeit list(test.f2())   # your second implementation 
1000 loops, best of 3: 1.63 ms per loop 

In [34]: %timeit list(test.g())   # my implementation 
100 loops, best of 3: 3.49 ms per loop 

È possibile accelerarlo utilizzando un semplice if/else invece di min/max, come nel tuo secondo implementazione, allora sono circa altrettanto veloci.

(["-".join((a,b) if a < b else (b,a)) for b in shops if b != a] for a in shops) 
+0

Grazie mille! Qual è la differenza tra l'uso di un'espressione generatore o l'utilizzo di una funzione e il ritorno con rendimento? In genere ho fatto il primo. – mptevsion

+0

@mptevsion Sono approssimativamente equivalenti. Un'espressione generatore è una funzione con rendimento come la comprensione di una lista è una funzione che restituisce una lista. –

+0

@mptevsion 'yield' è più generale. È un'espressione e puoi passare valori alla funzione usando il metodo 'send'. Questo non può essere fatto in un'espressione di generatore. Provalo: 'def f(): x = yield 0; cedere x' quindi fare 'a = f(); prossimo (a); a.send (1); prossimo (a) '. – Bakuriu

2

In base a quello che hai detto:

io attraverso ogni elemento con ogni altro e creare una stringa in cui prima parte è alfanumerico prima della seconda

è possibile utilizzare 2 combinazione come segue:

>>> form itertools import combinations 
>>> list(combinations(['_'.join(i) for i in combinations(shops,2)],3) 
...) 
[('A_B', 'A_C', 'A_D'), ('A_B', 'A_C', 'B_C'), ('A_B', 'A_C', 'B_D'), ('A_B', 'A_C', 'C_D'), ('A_B', 'A_D', 'B_C'), ('A_B', 'A_D', 'B_D'), ('A_B', 'A_D', 'C_D'), ('A_B', 'B_C', 'B_D'), ('A_B', 'B_C', 'C_D'), ('A_B', 'B_D', 'C_D'), ('A_C', 'A_D', 'B_C'), ('A_C', 'A_D', 'B_D'), ('A_C', 'A_D', 'C_D'), ('A_C', 'B_C', 'B_D'), ('A_C', 'B_C', 'C_D'), ('A_C', 'B_D', 'C_D'), ('A_D', 'B_C', 'B_D'), ('A_D', 'B_C', 'C_D'), ('A_D', 'B_D', 'C_D'), ('B_C', 'B_D', 'C_D')] 
>>> 

In un primo momento si può utilizzare una combinazione all'interno di una lista di comprensione per creare le coppie ordinate e concatenare utilizzando str.join. Quindi usa altre combinazioni per creare i set di trina.

+1

Questo non sembra estendersi bene al caso di 6 elementi nell'elenco. – Alexander

+1

Come ho capito la domanda, la prima sottolista dovrebbe avere A in ogni combinazione, la seconda B, poi C e D, ciascuna con ciascuna delle altre lettere, in ordine alfabetico. –

+0

@tobias_k Sì, questo è ciò che è chiaro nell'output atteso, ma non in quello che l'OP ha detto. – Kasramvd

4

Se la lista è ordinata e non ci sono duplicati, è possibile tenere traccia della posizione nella lista per evitare di dover fare i confronti per ottenere l'ordine.

from itertools import chain, islice 

combos = [] 
for i, s in enumerate(shops): 
    combo = ['{0}-{1}'.format(a, b) for a, b in chain(
     ((c, s) for c in islice(shops, None, i), 
     ((s, c) for c in islice(shops, i+1))] 
    combos.append(combo) 

EDIT: aggiornato per utilizzare generatori

+0

Ho pensato che creare tutte quelle liste temporanee sarebbe orribilmente lento, ma (una volta convertito in un generatore) questo è veloce quanto le seconde implementazioni dell'OP o il mio generatore. Tuttavia, per le liste di grandi dimensioni mi sembra di ottenere un risultato diverso. Per 'shops = map (str, range (10))' i risultati sono uguali, ma con 'map (str, range (100))' il risultato della tua differisce, non so come, però ... –

+0

@ tobias_k: questo presuppone che l'elenco sia ordinato. Quando si hanno numeri> = 10 come stringhe, i negozi non vengono ordinati correttamente all'inizio. (Ho avuto la stessa idea, ma Brendan mi ha battuto :) –

+0

@AleksiTorhamo Hai ragione, hai appena controllato: la differenza viene dai negozi che vengono ordinati in modo diverso nelle nostre implementazioni. –

Problemi correlati