2015-04-27 6 views
12

Ho un'attività che richiede un'operazione su ogni elemento di un elenco, con il risultato dell'operazione a seconda degli altri elementi nell'elenco.Operare in un elenco in modo pignolo quando l'output dipende da altri elementi

Ad esempio, potrebbe piacermi per concatenare un elenco di stringhe condizionali sopra partendo da un particolare carattere:

Questo codice risolve il problema:

x = ['*a', 'b', 'c', '*d', 'e', '*f', '*g'] 
concat = [] 
for element in x: 
    if element.startswith('*'): 
     concat.append(element) 
    else: 
     concat[len(concat) - 1] += element 

conseguente:

concat 
Out[16]: ['*abc', '*de', '*f', '*g'] 

Ma questo sembra orribilmente non-Pythonic. Come si dovrebbe operare sugli elementi di un list quando il risultato dell'operazione dipende dai risultati precedenti?

risposta

13

alcuni stralci rilevanti import this (l'arbitro di ciò che è Pythonic):

  • Semplice è meglio che complesso
  • Leggibilità conta
  • Esplicito è meglio che implicita.

Vorrei solo usare un codice come questo, e non preoccuparmi di sostituire il ciclo for con qualcosa di "più piatto".

x = ['*a', 'b', 'c', '*d', 'e', '*f', '*g'] 
partials = [] 
for element in x: 
    if element.startswith('*'): 
     partials.append([]) 
    partials[-1].append(element) 
concat = map("".join, partials) 
+4

Bello. +1 per riportare la discussione su "* pythonic *" e lontano da necklace/compact/fast. – LondonRob

+0

Aggiunta la risposta al mio piccolo punto di riferimento, se siete interessati :) – miradulo

+0

Il mio tempo lento su un grande input non mi sorprende :) Tuttavia, se 'x' fosse davvero così grande, guarderei in costruzione' concat 'come costruisco' x', piuttosto che aspettare che 'x' sia completo per iniziare. – chepner

4
"".join(x).split("*") 

forse sufficiente, di grossolana questo può essere un esempio inventato nel vostro OP che viene semplificato eccessivamente e come tale questo non funzionerà

+2

Concorda sul fatto che questo sembra ottimo in questo specifico (ma un po 'fittizio) esempio. (Anche se non * abbastanza * riproduce l'output.Le stelle nell'OP vengono mantenute.) C'è qualcosa di più generico da apprendere sulla codifica di Pythonic attraverso questo esempio? – LondonRob

+0

che c'è più di un modo per scuoiare un gatto, forse? se ci fosse un esempio meno fittizio sono sicuro che potremmo inventare qualcosa di ugualmente leggibile ... –

+0

Partendo da questo, che ne dite di 'lista (map (lambda v: '*' + v," ".join (x). split ('*') [1:])) '? – onetwothree

7

Si potrebbe utilizzare espressioni regolari per raggiungere questo succintamente. Ciò, tuttavia, aggira la tua domanda su come operare sugli elementi della lista dipendenti. Crediti a mbomb007 per migliorare la funzionalità dei caratteri consentiti.

import re 
z = re.findall('\*[^*]+',"".join(x)) 

Uscite:

['*abc', '*de', '*f', '*g'] 

Piccolo analisi comparativa:

Donkey Kong's answer:

import timeit 
setup = ''' 
import re 
x = ['*a', 'b', 'c', '*d', 'e', '*f', '*g'] 
y = ['*a', 'b', 'c', '*d', 'e', '*f', '*g'] * 100 
''' 
print (min(timeit.Timer('re.findall("\*[^\*]+","".join(x))', setup=setup).repeat(7, 1000))) 
print (min(timeit.Timer('re.findall("\*[^\*]+","".join(y))', setup=setup).repeat(7, 1000))) 

Restituisce 0.00226416693456 e 0.06827958075, rispettivamente.

Chepner's answer:

setup = ''' 
x = ['*a', 'b', 'c', '*d', 'e', '*f', '*g'] 
y = ['*a', 'b', 'c', '*d', 'e', '*f', '*g'] * 100 
def chepner(x): 
    partials = [] 
    for element in x: 
     if element.startswith('*'): 
      partials.append([]) 
     partials[-1].append(element) 
    concat = map("".join, partials) 
    return concat 
''' 
print (min(timeit.Timer('chepner(x)', setup=setup).repeat(7, 1000))) 
print (min(timeit.Timer('chepner(y)', setup=setup).repeat(7, 1000))) 

ritorni rispettivamente 0.00456210269896 e 0.364635824689,.

Saksham's answer

setup = ''' 
x = ['*a', 'b', 'c', '*d', 'e', '*f', '*g'] 
y = ['*a', 'b', 'c', '*d', 'e', '*f', '*g'] * 100 

''' 
print (min(timeit.Timer("['*'+item for item in ''.join(x).split('*') if item]", setup=setup).repeat(7, 1000))) 
print (min(timeit.Timer("['*'+item for item in ''.join(y).split('*') if item]", setup=setup).repeat(7, 1000)))) 

Returns 0.00104848906006 e 0.0556093171512 rispettivamente.

tl; dr Saksham è leggermente più veloce del mio, quindi Chepner segue entrambi i nostri.

+0

Solo una nota: il '' \ '' prima che il secondo asterisco sia opzionale poiché è contenuto in una classe di caratteri. Non è necessario modificarlo, se non lo si desidera. – mbomb007

6

ne dite di questo:

>>> x = ['*a', 'b', 'c', '*d', 'e', '*f', '*g'] 
>>> print ['*'+item for item in ''.join(x).split('*') if item] 
['*abc', '*de', '*f', '*g'] 
+0

Questa è la migliore risposta, secondo me. +1 – miradulo

+0

heh è quasi la stessa della mia risposta: P sta solo correggendo l'output ma sì +1 buona risposta imho :) –

+1

haha ​​è davvero simile a quello di Joran! –

3

sento che questo è molto Pythonic:

# assumes no empty strings, or no spaces in strings 
"".join(x).replace('*', ' *').split() 

Ecco un approccio funzionale ad esso:

from functools import reduce 

# assumes no empty strings 
def reduction(l, it): 
    if it[0] == '*': 
     return l + [it] 
    else: 
     new_l, last = l[:-1], l[-1] 
     return new_l + [last + it] 

x = ['*a', 'b', 'c', '*d', 'e', '*f', '*g'] 

print reduce(reduction, x, []) 
>>> ['*abc', '*de', '*f', '*g'] 

Se sei un fan di lambda (non molto Pythonic), si potrebbe ottenere via con questo:

# Don't do this, it's ugly and unreadable. 
reduce(lambda l, it: l + [it] if it.startswith('*') else l[:-1] + [l[-1]+it], x, []) 
0

Questo è terribilmente vicino a quello che itertools.groupby fa, e di fatto con un po 'porzione di curry che può fare continua a raggruppare fino a quando non si verifica una condizione di "interruzione", ad esempio startswith('*').

from itertools import groupby 

def new_group_when_true(pred): 
    group_num = 0 
    def group_for_elem(elem): 
     nonlocal group_num 
     if pred(elem): 
      group_num +=1 
     return group_num 
    return group_for_elem 

l = ['*a', 'b', 'c', '*d', 'e', '*f', '*g'] 

test = new_group_when_true(lambda elem: elem.startswith('*')) 

grouped = [list(v) for k,v in groupby(l, test)] 

Con conseguente:

>>> print(grouped) 
[['*a', 'b', 'c'], ['*d', 'e'], ['*f'], ['*g']] 

Il nonlocal parola chiave richiede Python 3, naturalmente. Un'altra possibilità sarebbe quella di creare una classe, sulla falsariga del "codice equivalente" groupby dai documenti itertools.

Non so che questo è più Pythonic che il vostro codice, ma penso che l'idea di andare in libreria standard per vedere se qualcosa quasi adatto alle proprie esigenze è un punto utile.

Problemi correlati