2013-08-06 8 views
18

Gibt es eine Möglichkeit, eine Zeichenfolge zu spalten, ohne Splitting Charakter entkommen? Zum Beispiel habe ich einen String und wollen durch aufzuspalten ‚:‘ und nicht von ‚\:‘Python Split-String ohne Splitting Escape-Zeichen

http\://www.example.url:ftp\://www.example.url 

Das Ergebnis sollte sein, die folgenden:

['http\://www.example.url' , 'ftp\://www.example.url'] 
+2

Ja, aber nicht trivial, ohne die Kriterien weiter einschränken. –

+0

@ IgnacioVazquez-Abrams Welche Einschränkungen müssen angewendet werden, um es einfacher zu machen? –

+0

Ich denke, ein Regex Split könnte damit kein Problem umgehen? http://docs.python.org/2/library/re.html –

Antwort

-4

Beachten Sie, dass: nicht angezeigt ein Charakter zu sein, der fliehen muss.

Der einfachste Weg, den ich mir vorstellen kann dies zu tun ist auf den Charakter zu spalten, und es dann in wieder hinzufügen, wenn sie entkommen ist.

Beispielcode (In viel Not einiger Versäubern.):

def splitNoEscapes(string, char): 
    sections = string.split(char) 
    sections = [i + (char if i[-1] == "\\" else "") for i in sections] 
    result = ["" for i in sections] 
    j = 0 
    for s in sections: 
     result[j] += s 
     j += (1 if s[-1] != char else 0) 
    return [i for i in result if i != ""] 
+0

Dachte, dass es einen einfacheren Weg geben würde, aber scheint nicht der Fall zu sein. Danke für Ihre Hilfe! Sehr geschätzt. –

+1

Wie kann man dem Escape-Zeichen entkommen? Funktioniert es auf 'Hallo \\: world'? –

+2

Lassen Sie sich nicht durch diese Antwort täuschen, als wäre es richtig, wenn es der akzeptierte ist, siehe http://stackoverflow.com/a/18092547/99834, der sich um die entkommenen Doppelpunkte kümmert. – sorin

7

Wie Ignacio sagt, ja, aber nicht trivial in einem Rutsch. Das Problem ist, dass Sie Lookback müssen, um festzustellen, ob Sie an einem entflohenen Begrenzer sind oder nicht, und die grundlegenden string.split bieten nicht die Funktionalität.

Wenn dies nicht innerhalb einer engen Schleife liegt, ist die Leistung kein wesentliches Problem. Sie können dies tun, indem Sie zuerst die maskierten Trennzeichen aufteilen, dann die Teilung durchführen und dann zusammenführen. Hässliche Demo-Code folgt:

# Bear in mind this is not rigorously tested! 
def escaped_split(s, delim): 
    # split by escaped, then by not-escaped 
    escaped_delim = '\\'+delim 
    sections = [p.split(delim) for p in s.split(escaped_delim)] 
    ret = [] 
    prev = None 
    for parts in sections: # for each list of "real" splits 
     if prev is None: 
      if len(parts) > 1: 
       # Add first item, unless it's also the last in its section 
       ret.append(parts[0]) 
     else: 
      # Add the previous last item joined to the first item 
      ret.append(escaped_delim.join([prev, parts[0]])) 
     for part in parts[1:-1]: 
      # Add all the items in the middle 
      ret.append(part) 
     prev = parts[-1] 
    return ret 

s = r'http\://www.example.url:ftp\://www.example.url' 
print (escaped_split(s, ':')) 
# >>> ['http\\://www.example.url', 'ftp\\://www.example.url'] 

Alternativ könnte es einfacher sein, die Logik zu folgen, wenn Sie nur die Zeichenfolge von Hand gespalten.

def escaped_split(s, delim): 
    ret = [] 
    current = [] 
    itr = iter(s) 
    for ch in itr: 
     if ch == '\\': 
      try: 
       # skip the next character; it has been escaped! 
       current.append('\\') 
       current.append(next(itr)) 
      except StopIteration: 
       pass 
     elif ch == delim: 
      # split! (add current to the list and reset it) 
      ret.append(''.join(current)) 
      current = [] 
     else: 
      current.append(ch) 
    ret.append(''.join(current)) 
    return ret 

Beachten Sie, dass diese zweite Version etwas anders verhält, wenn es doppelt entkommen Begegnungen durch ein Trennzeichen gefolgt: Mit dieser Funktion kann Escape-Zeichen entkommen, so dass escaped_split(r'a\\:b', ':') kehrt ['a\\\\', 'b'], weil die ersten \ die zweite entweicht, den Abgang : als reale Trennzeichen interpretiert werden. Das ist etwas, auf das man achten muss.

+0

Der erste Code scheitert beim Entkommen des Escape-Zeichens 'foo \\: bar', aber der zweite Durchlauf. –

+0

@Taha Der Fragesteller hat für diesen Fall kein erforderliches Verhalten angegeben und ehrlich gesagt weiß ich nicht, welches Verhalten ich als "korrekt" bezeichnen würde. Ich habe eine Notiz hinzugefügt, um die unterschiedlichen Verhaltensweisen zu erklären, obwohl es definitiv erwähnenswert ist. –

24

Es gibt eine viel einfachere Weise, einen regulären Ausdruck mit einer negativen Lookbehind Behauptung mit:

re.split(r'(?<!\\):', str) 
+5

Wie kann man dem Escape-Zeichen entkommen? Funktioniert nicht auf 'Hello \\: world' –

+0

@Taha ist es am besten, ein Escape-Zeichen zu wählen, das nirgendwo sonst erscheint (oder in diesem Fall, das nicht vor einer Aufspaltung erscheint ':') – abcdaa

+0

@abcdaa Das würde zwar am besten, aber manchmal erhalten Sie Dateien von einer dritten Partei, die Sie nicht kontrollieren. – physicalattraction

4

Die bearbeitete Version von Henrys Antwort mit Python3 Kompatibilität, Tests und einige Probleme beheben:

def split_unescape(s, delim, escape='\\', unescape=True): 
    """ 
    >>> split_unescape('foo,bar', ',') 
    ['foo', 'bar'] 
    >>> split_unescape('foo$,bar', ',', '$') 
    ['foo,bar'] 
    >>> split_unescape('foo$$,bar', ',', '$', unescape=True) 
    ['foo$', 'bar'] 
    >>> split_unescape('foo$$,bar', ',', '$', unescape=False) 
    ['foo$$', 'bar'] 
    >>> split_unescape('foo$', ',', '$', unescape=True) 
    ['foo$'] 
    """ 
    ret = [] 
    current = [] 
    itr = iter(s) 
    for ch in itr: 
     if ch == escape: 
      try: 
       # skip the next character; it has been escaped! 
       if not unescape: 
        current.append(escape) 
       current.append(next(itr)) 
      except StopIteration: 
       if unescape: 
        current.append(escape) 
     elif ch == delim: 
      # split! (add current to the list and reset it) 
      ret.append(''.join(current)) 
      current = [] 
     else: 
      current.append(ch) 
    ret.append(''.join(current)) 
    return ret 
+0

Die einzige Lösung, die richtig funktioniert. – physicalattraction

0

Dafür gibt es keine eingebaute Funktion. Hier ist eine effiziente, allgemeine und getestet Funktion, die auch Trennzeichen beliebiger Länge unterstützt:

def escape_split(s, delim): 
    i, res, buf = 0, [], '' 
    while True: 
     j, e = s.find(delim, i), 0 
     if j < 0: # end reached 
      return res + [buf + s[i:]] # add remainder 
     while j - e and s[j - e - 1] == '\\': 
      e += 1 # number of escapes 
     d = e // 2 # number of double escapes 
     if e != d * 2: # odd number of escapes 
      buf += s[i:j - d - 1] + s[j] # add the escaped char 
      i = j + 1 # and skip it 
      continue # add more to buf 
     res.append(buf + s[i:j - d]) 
     i, buf = j + len(delim), '' # start after delim 
2

Hier ist eine effiziente Lösung, die richtig Doppel entkommen Griffe, das heißt jedes nachfolgendes Trennzeichen ist nicht entgangen. Es ignoriert eine falsche Single-Escape als das letzte Zeichen der Zeichenfolge.

Es ist sehr effizient, weil es genau einmal den Eingabestring iteriert, Manipulation Indizes statt Strings kopieren um. Anstatt eine Liste zu erstellen, gibt es einen Generator zurück.

def split_esc(string, delimiter): 
    if len(delimiter) != 1: 
     raise ValueError('Invalid delimiter: ' + delimiter) 
    ln = len(string) 
    i = 0 
    j = 0 
    while j < ln: 
     if string[j] == '\\': 
      if j + 1 >= ln: 
       yield string[i:j] 
       return 
      j += 1 
     elif string[j] == delimiter: 
      yield string[i:j] 
      i = j + 1 
     j += 1 
    yield string[i:j] 

Um delimiters länger als ein einzelnes Zeichen, einfach voraus i und j durch die Länge des Trennzeichens in der „Elif“ Fall zu erlauben.Dies setzt voraus, dass ein einzelnes Escapezeichen das gesamte Begrenzungszeichen und nicht ein einzelnes Zeichen ausschließt.

Getestet mit Python 3.5.1.

+0

Wenn ich dies mit der Zeichenfolge '' A \ + B'' verwende, lautet das Ergebnis '[' A \\ + B '] ', wobei ich erwarte, dass es' ['A + B']' ist – physicalattraction

1

Ich denke, ein einfaches C wie Parsing wäre viel einfacher und robuster.

def escaped_split(str, ch): 
    if len(ch) > 1: 
     raise ValueError('Expected split character. Found string!') 
    out = [] 
    part = '' 
    escape = False 
    for i in range(len(str)): 
     if not escape and str[i] == ch: 
      out.append(part) 
      part = '' 
     else: 
      part += str[i] 
      escape = not escape and str[i] == '\\' 
    if len(part): 
     out.append(part) 
    return out 
0

Ich habe diese Methode erstellt, die von Henry Keiter Antwort inspiriert ist, aber hat folgende Vorteile:

  • Variable Escape-Zeichen und Begrenzer
  • Sie das Escape-Zeichen nicht entfernen, wenn es eigentlich nicht etwas zu entkommen

Dies ist der Code:

def _split_string(self, string: str, delimiter: str, escape: str) -> [str]: 
    result = [] 
    current_element = [] 
    iterator = iter(string) 
    for character in iterator: 
     if character == self.release_indicator: 
      try: 
       next_character = next(iterator) 
       if next_character != delimiter and next_character != escape: 
        # Do not copy the escape character if it is inteded to escape either the delimiter or the 
        # escape character itself. Copy the escape character if it is not in use to escape one of these 
        # characters. 
        current_element.append(escape) 
       current_element.append(next_character) 
      except StopIteration: 
       current_element.append(escape) 
     elif character == delimiter: 
      # split! (add current to the list and reset it) 
      result.append(''.join(current_element)) 
      current_element = [] 
     else: 
      current_element.append(character) 
    result.append(''.join(current_element)) 
    return result 

Dies ist Code-Test zeigt das Verhalten:

def test_split_string(self): 
    # Verify normal behavior 
    self.assertListEqual(['A', 'B'], list(self.sut._split_string('A+B', '+', '?'))) 

    # Verify that escape character escapes the delimiter 
    self.assertListEqual(['A+B'], list(self.sut._split_string('A?+B', '+', '?'))) 

    # Verify that the escape character escapes the escape character 
    self.assertListEqual(['A?', 'B'], list(self.sut._split_string('A??+B', '+', '?'))) 

    # Verify that the escape character is just copied if it doesn't escape the delimiter or escape character 
    self.assertListEqual(['A?+B'], list(self.sut._split_string('A?+B', '\'', '?'))) 
0

Gebäude auf @ user629923 Vorschlag, aber da viel einfacher als andere Antworten:

import re 
DBL_ESC = "!double escape!" 

s = r"Hello:World\:Goodbye\\:Cruel\\\:World" 

map(lambda x: x.replace(DBL_ESC, r'\\'), re.split(r'(?<!\\):', s.replace(r'\\', DBL_ESC)))