2012-11-01 13 views
23

Ho bisogno di dividere stringhe di dati usando ogni carattere da string.punctuation e string.whitespace come separatore.Divide in modo efficiente una stringa utilizzando più separatori e conservando ciascun separatore?

Inoltre, ho bisogno che i separatori rimangano nella lista di output, tra gli elementi separati nella stringa.

Per esempio,

"Now is the winter of our discontent" 

dovrebbe uscita:

['Now', ' ', 'is', ' ', 'the', ' ', 'winter', ' ', 'of', ' ', 'our', ' ', 'discontent'] 

non sono sicuro come fare questo senza ricorrere a un'orgia di cicli annidati, che è troppo lenta. Come posso farlo?

+0

Sto indovinando dal momento che avete accettato la risposta di DSM si intende per i caratteri di punteggiatura consecutivi per rimanere raggruppati insieme? – John

+0

@johnthexiii, l'ho accettato perché non usava 're'. L'opzione per raggruppare i separatori consecutivi è un ulteriore vantaggio, anche se sono sicuro che può essere fatto facilmente anche con regex. – blz

risposta

21

un approccio non-regex diverso dagli altri:

>>> import string 
>>> from itertools import groupby 
>>> 
>>> special = set(string.punctuation + string.whitespace) 
>>> s = "One two three tab\ttabandspace\t end" 
>>> 
>>> split_combined = [''.join(g) for k, g in groupby(s, lambda c: c in special)] 
>>> split_combined 
['One', ' ', 'two', ' ', 'three', ' ', 'tab', '\t', 'tabandspace', '\t ', 'end'] 
>>> split_separated = [''.join(g) for k, g in groupby(s, lambda c: c if c in special else False)] 
>>> split_separated 
['One', ' ', 'two', ' ', 'three', ' ', 'tab', '\t', 'tabandspace', '\t', ' ', 'end'] 

potrebbe usare dict.fromkeys e .get al posto del lambda, immagino.

[modifica]

Qualche spiegazione:

groupby accetta due argomenti, un iterabile ed un keyfunction (opzionale). Esso scorre iterable le raggruppa con il valore del keyfunction:

>>> groupby("sentence", lambda c: c in 'nt') 
<itertools.groupby object at 0x9805af4> 
>>> [(k, list(g)) for k,g in groupby("sentence", lambda c: c in 'nt')] 
[(False, ['s', 'e']), (True, ['n', 't']), (False, ['e']), (True, ['n']), (False, ['c', 'e'])] 

dove termini con valori contigui del keyfunction sono raggruppati insieme. (Questa è una fonte comune di bug, in realtà - le persone dimenticano che devono prima ordinare il keyfunc se vogliono raggruppare termini che potrebbero non essere sequenziali.)

Come ho intuito @JonClements, cosa avevo in mente era

>>> special = dict.fromkeys(string.punctuation + string.whitespace, True) 
>>> s = "One two three tab\ttabandspace\t end" 
>>> [''.join(g) for k,g in groupby(s, special.get)] 
['One', ' ', 'two', ' ', 'three', ' ', 'tab', '\t', 'tabandspace', '\t ', 'end'] 

per il caso in cui stavamo combinando i separatori. .get restituisce None se il valore non è nel dict.

+3

Oppure un'altra opzione invece di lambda (anche se brutta) 'groupby (s, speciale .__ contiene __)' ... –

+0

@JonClements: sì, penso che userei un dict prima di usare un metodo speciale. : ^) – DSM

+1

'partial (contains, special)' allora? ;) –

7
import re 
import string 

p = re.compile("[^{0}]+|[{0}]+".format(re.escape(
    string.punctuation + string.whitespace))) 

print p.findall("Now is the winter of our discontent") 

Io non sono un grande fan di utilizzare espressioni regolari per tutti i problemi, ma non credo di avere molta scelta in questo se lo vuoi e veloce brevi.

mi spiego l'espressione regolare in quanto non hai familiarità con esso:

  • [...]: qualsiasi dei caratteri all'interno delle parentesi quadre
  • [^...]: qualsiasi dei personaggi non all'interno del quadrato staffe
  • + dietro significa una o più della cosa precedente
  • x|y mezzi per realizzare le sia x o y

Quindi l'espressione regolare corrisponde a 1 o più caratteri in cui o tutti deve essere punteggiatura e spazi bianchi, o nessuno deve essere. Il metodo findall trova tutte le corrispondenze non sovrapposte del modello.

+0

Probabilmente vorrai usare 're.escape (string.punctuation + string.whitespace)', altrimenti penso che le tue classi di caratteri finiranno presto su ']'. –

+0

Non penso che funzioni per "..Ora è l'inverno del nostro scontento" – John

+0

@ F.J Risolto. E "" Ora è l'inverno del nostro scontento "' funziona per me. –

0
from string import punctuation, whitespace 

s = "..test. and stuff" 

f = lambda s, c: s + ' ' + c + ' ' if c in punctuation else s + c 
l = sum([reduce(f, word).split() for word in s.split()], []) 

print l 
4

Prova questo:

import re 
re.split('(['+re.escape(string.punctuation + string.whitespace)+']+)',"Now is the winter of our discontent") 

Spiegazione da the Python documentation:

Se sono presenti delle parentesi nel modello, allora il testo di tutti i gruppi nel modello vengono restituiti anche come parte di la lista risultante.

+1

Comportamento brutto con spazi consecutivi: 're.split (r '()', '' * 2)' risulta in '['', '', '', '', '']'. –

+0

@ F.J spazi consecutivi/separatori dovrebbero essere gestiti meglio ora. – Bula

1

A seconda del testo che si sta trattando, si può essere in grado di semplificare il concetto di delimitatore a "qualsiasi cosa che non sia lettere e numeri". Se questo lavoro, è possibile utilizzare la seguente soluzione espressione regolare:

re.findall(r'[a-zA-Z\d]+|[^a-zA-Z\d]', text) 

Questo presuppone che si desidera dividere su ogni carattere individuale delimitatore anche se si verificano consecutivamente, quindi 'foo..bar' sarebbe diventato ['foo', '.', '.', 'bar']. Se invece ci si aspetta ['foo', '..', 'bar'], utilizzare [a-zA-Z\d]+|[^a-zA-Z\d]+ (l'unica differenza è aggiungere + alla fine).

+0

Questo non funzionerà con caratteri al di fuori dell'intervallo ASCII. – DzinX

3

Soluzione in lineare (O(n)) tempo:

Diciamo che si dispone di una stringa:

original = "a, b...c d" 

prima convertire tutti i separatori allo spazio:

splitters = string.punctuation + string.whitespace 
trans = string.maketrans(splitters, ' ' * len(splitters)) 
s = original.translate(trans) 

Ora s == 'a b c d'. Ora è possibile utilizzare itertools.groupby di alternare tra gli spazi e non-spazi:

result = [] 
position = 0 
for _, letters in itertools.groupby(s, lambda c: c == ' '): 
    letter_count = len(list(letters)) 
    result.append(original[position:position + letter_count]) 
    position += letter_count 

Ora result == ['a', ', ', 'b', '...', 'c', ' ', 'd'], che è quello che serve.

1

mio prendere:

from string import whitespace, punctuation 
import re 

pattern = re.escape(whitespace + punctuation) 
print re.split('([' + pattern + '])', 'now is the winter of') 
+0

+1 ha scritto esattamente la stessa cosa un minuto dopo;) – DzinX

+3

Comportamento brutto con delimitatori consecutivi: 're.split ('([' + pattern + '])', '..')' risulta in '['', '.', '', '.', ''] '. –

-1
from itertools import chain, cycle, izip 

s = "Now is the winter of our discontent" 
words = s.split() 

wordsWithWhitespace = list(chain.from_iterable(izip(words, cycle([" "])))) 
# result : ['Now', ' ', 'is', ' ', 'the', ' ', 'winter', ' ', 'of', ' ', 'our', ' ', 'discontent', ' '] 
+0

-1: funziona solo per gli spazi come separatori. – DzinX

0

Per qualsiasi raccolta arbitraria di separatori:

def separate(myStr, seps): 
    answer = [] 
    temp = [] 
    for char in myStr: 
     if char in seps: 
      answer.append(''.join(temp)) 
      answer.append(char) 
      temp = [] 
     else: 
      temp.append(char) 
    answer.append(''.join(temp)) 
    return answer 

In [4]: print separate("Now is the winter of our discontent", set(' ')) 
['Now', ' ', 'is', ' ', 'the', ' ', 'winter', ' ', 'of', ' ', 'our', ' ', 'discontent'] 

In [5]: print separate("Now, really - it is the winter of our discontent", set(' ,-')) 
['Now', ',', '', ' ', 'really', ' ', '', '-', '', ' ', 'it', ' ', 'is', ' ', 'the', ' ', 'winter', ' ', 'of', ' ', 'our', ' ', 'discontent'] 

Spero che questo aiuti

+0

Questo potrebbe iniziare ad essere lento quando tu usi 'string.punctuation + string.whitespace' come argomento' seps' - per ogni personaggio, stai cercando attraverso un elenco di separatori in tempo lineare. – DzinX

+0

Non se li passi come un 'set' – inspectorG4dget

Problemi correlati