2012-10-16 22 views
5

PostgreSQL consente di creare indici su espressioni, ad esempio CREATE INDEX ON films ((lower(title))). Ha anche una funzione di informazione pg_get_expr() che traduce il formato interno dell'espressione in testo, ad esempio, lower(title) nell'esempio precedente. Le espressioni possono diventare piuttosto pelose a volte. Ecco alcuni esempi (in Python):Dividere l'elenco di espressioni di funzione possibilmente nidificate in Python

sample_exprs = [ 
    'lower(c2)', 
    'lower(c2), lower(c3)', 
    "btrim(c3, 'x'::text), lower(c2)", 
    "date_part('month'::text, dt), date_part('day'::text, dt)", 
    '"substring"(c2, "position"(c2, \'_begin\'::text)), "substring"(c2, "position"(c2, \'_end\'::text))', 
    "(((c2)::text || ', '::text) || c3), ((c3 || ' '::text) || (c2)::text)", 
    'f1(f2(arga, f3()), arg1), f4(arg2, f5(argb, argc)), f6(arg3)'] 

L'ultimo elemento non è davvero da Postgres, ma è solo un esempio estremo di ciò che il mio codice dovrebbe gestire.

Ho scritto una funzione Python per dividere le liste di testo nelle espressioni componente. Ad esempio, tale elemento è suddiviso in:

f1(f2(arga, f3()), arg1) 
f4(arg2, f5(argb, argc)) 
f6(arg3) 

ho sperimentato con str metodi come find() e count() e regex anche considerati, ma alla fine ho scritto una funzione che è quello che avrei scritto in C (essenzialmente contando i parenti aperti e chiusi per trovare dove rompere il testo). Ecco la funzione:

def split_exprs(idx_exprs): 
    keyexprs = [] 
    nopen = nclose = beg = curr = 0 
    for c in idx_exprs: 
     curr += 1 
     if c == '(': 
      nopen += 1 
     elif c == ')': 
      nclose += 1 
      if nopen > 0 and nopen == nclose: 
       if idx_exprs[beg] == ',': 
        beg += 1 
       if idx_exprs[beg] == ' ': 
        beg += 1 
       keyexprs.append(idx_exprs[beg:curr]) 
       beg = curr 
       nopen = nclose = 0 
    return keyexprs 

La domanda è se c'è un altro Pythonic o modo elegante per fare questo o di usare espressioni regolari per risolvere questo.

+1

Dai un'occhiata alla [ pyparsing] (http://pyparsing.wikispaces.com/) per qualche ispirazione –

+0

I regexes potrebbero non avere nulla di elegante. Vedi: http://perldoc.perl.org/perlfaq6.html#Can-I-use-Perl-regular-expressions-to-match-balanced-text%3f – Himanshu

+0

Sì, mi sono convinto che le regex non possono essere usato perché le macchine di stato non possono contare l'annidamento di parentesi senza l'aiuto di una pila, cioè è necessario un PDA. –

risposta

1

Se stai cercando di renderlo più pitone, l'unico modo che posso pensare è la leggibilità.

Inoltre, evito un ramo e una variabile contando le pile. L'unico suggerimento pitonico che posso dare è usare la variabile 'index' con la funzione enumerate(...). Come in

for i, j in enumerate(<iterable>)

Ciò creerà una variabile, i, che sarà pari al numero di loop corrente, dove j sarà variabile iterazione previsto.

def split_fns(fns): 
    paren_stack_level = 0 
    last_pos = 0 
    output = [] 
    for curr_pos, curr_char in enumerate(fns): 
     if curr_char == "(": 
      paren_stack_level += 1 
     elif curr_char == ")": 
      paren_stack_level -= 1 
      if not paren_stack_level: 
       output.append(fns[last_pos:curr_pos+1].lstrip(" ,")) 
       last_pos = curr_pos+1 
    return output 

for i in sample_exprs: 
    print(split_fns(i)) 
+0

La singola variabile stack_level rappresenta sicuramente un miglioramento. Tuttavia, l'intervallo (len (fns)) non mi sembra pitone, e non so se l'accesso a ciascun carattere come fns [curr_pos] sia più efficiente (il mio istinto dice no, ma non l'ho benchmarkato) . –

+0

@JoeAbbate 'range (len ())' non è incredibilmente raro in python, nella mia esperienza. E hai ragione, 'fns [curr_pos]' non è efficiente. Modificherò la risposta – jsvk

1

Ecco la mia versione, più divinatorio, meno confusione credo, e funziona su flusso di caratteri, anche se non vedo alcun vantaggio in quanto :)

def split_fns(fns): 
    level = 0 
    stack = [[]] 
    for ch in fns: 
     if level == 0 and ch in [' ',',']: 
      continue   
     stack[-1].append(ch) 

     if ch == "(": 
      level += 1 
     elif ch == ")": 
      level -= 1 
      if level == 0: 
       stack.append([]) 

    return ["".join(t) for t in stack if t] 
+0

Sono impressionato dal pensiero laterale, anche se una cosa da considerare: Per i caratteri esterni allo stack, vengono effettuati 2-5 confronti, 2-3 se sono simboli. 2-4 per parentesi e 4 per determinare che una funzione è completa. Per esempio, il mio, i numeri sono 2, 2, 1-2 e 5-7 (potenzialmente 4 se riscritto in modo intelligente) – jsvk

+0

@jsvk Suppongo più che la chiarezza della velocità è il criterio qui, altrimenti scriverei in C –

+0

ci sono più motivi per scrivere codice efficiente che velocità, ma vedo da dove vieni; la chiarezza è una parte vitale dell'essere pitoni. – jsvk

Problemi correlati