2013-08-06 13 views
18

Czy istnieje sposób na rozdzielenie ciągu bez dzielenia znaku? Na przykład, mam ciąg i chcesz podzielić przez „:”, a nie „\:”Python split string bez dzielenia znaków ze znakiem escape

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

Wynik powinien być następujący:

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

Tak, ale nie trywialnie bez dalszego ograniczenia kryteria. –

+0

@ Ignacio Vaazquez-Abrams jakie ograniczenia należy zastosować, aby było łatwiej? –

+0

Myślę, że podział regex może obsłużyć to nie ma problemu? http://docs.python.org/2/library/re.html –

Odpowiedz

-4

zauważyć, że: nie pojawia być postacią, która potrzebuje ucieczki.

Najprostszy sposób, w jaki mogę to osiągnąć, to podzielić się na postać, a następnie dodać ją ponownie, gdy jest ona chroniona.

kod próbki (w dużo potrzebie neatening.):

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

Pomyślałem, że będzie łatwiejszy sposób, ale nie wydaje się, że tak jest. Dzięki za pomoc! Naprawdę doceniony. –

+1

Jak uciec przed ucieczką? Czy działa na 'Hello \\: world'? –

+2

Nie dajcie się zwieść tej odpowiedzi, ponieważ jest to prawidłowa wigilia, jeśli jest akceptowana, patrz http://stackoverflow.com/a/18092547/99834, która wydaje się zajmować uciekającymi dwukropkami. – sorin

7

Jak mówi Ignacio, tak, ale nie trywialnie za jednym zamachem. Problem polega na tym, że potrzebujesz wznowienia, aby określić, czy jesteś na ograniczniku z odgraniczeniem, czy nie, a podstawowy string.split nie zapewnia tej funkcji.

Jeśli nie jest to wewnątrz ciasnej pętli, więc wydajność nie jest istotnym problemem, możesz to zrobić najpierw dzieląc ograniczniki, a następnie wykonując podział, a następnie łącząc. Brzydki kod demo:

# 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'] 

Alternatywnie, łatwiej byłoby podążać za logiką, jeśli po prostu podzielisz strunę ręcznie.

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 

Zauważ, że ta druga wersja zachowuje się nieco inaczej, gdy napotka podwójnych ucieczek następnie separatora: Funkcja ta pozwala uciec znaków ewakuacyjnych, tak że escaped_split(r'a\\:b', ':') powraca ['a\\\\', 'b'], ponieważ pierwszy \ ucieka drugi, pozostawiając : należy interpretować jako prawdziwy ogranicznik. To jest coś, na co trzeba uważać.

+0

Pierwszy kod kończy się niepowodzeniem po wyjściu z samego znaku ucieczki 'foo \\: bar', ale z drugiego przebiegu. –

+0

@Taha Pytający nie określił wymaganego zachowania w tej sprawie i szczerze mówiąc nie wiem, które zachowanie wolałbym uznać za "poprawne". Dodałem notatkę, aby wyjaśnić różne zachowania, ponieważ zdecydowanie warto o tym wspomnieć. –

24

Jest o wiele łatwiejszy sposób, używając regex z twierdzeniem ujemny lookbehind:

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

Jak uciec przed ucieczką? Nie działa na 'Cześć \\: świat' –

+0

@Taha najlepiej wybrać znak ucieczki, który nie pojawia się nigdzie indziej (lub w tym przypadku, który nie pojawia się przed podziałem": ") – abcdaa

+0

@abcdaa To by rzeczywiście najlepiej, ale czasami otrzymujesz pliki od osób trzecich, nad którymi nie masz kontroli. – physicalattraction

4

Edytowany wersja odpowiedź Henry'ego z Python3 zgodności, badań i rozwiązać niektóre problemy:

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

Jedyne rozwiązanie, które działa poprawnie. – physicalattraction

0

Nie ma na to wbudowanej funkcji. Oto skuteczny, ogólny i przetestowane funkcja, która obsługuje nawet ograniczniki o dowolnej długości:

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

Tutaj jest skutecznym rozwiązaniem, które obsługuje podwójne ucieczek prawidłowo, to znaczy każdy kolejny separator nie uciekł. Ignoruje niepoprawną pojedynczą ucieczkę jako ostatni znak ciągu.

Jest bardzo wydajny, ponieważ wykonuje iteracje na ciągu wejściowym dokładnie jeden raz, manipulując indeksami zamiast kopiować ciągi w pobliżu. Zamiast budować listę, zwraca generator.

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] 

Aby umożliwić ograniczników dłuższe niż jeden znak, po prostu advance I i J o długości separatora w „elif” sprawy.Zakłada się, że pojedynczy znak ucieczki ucieka z całego ogranicznika, a nie z pojedynczego znaku.

Testowane przy pomocy Pythona 3.5.1.

+0

Kiedy używam tego z napisem ''A \ + B'', wynikiem jest' [' A \\ + B '] ', gdzie spodziewam się, że będzie to" [' A + B '] " – physicalattraction

1

Myślę, że proste analizowanie C byłoby znacznie prostsze i mocniejsze.

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

Stworzyłem tę metodę, która jest inspirowana odpowiedź Henry'ego Keiter, ale ma następujące zalety:

  • Zmienny charakter ucieczki i ogranicznik
  • Nie usuwaj znak ucieczki, jeśli jest faktycznie nie uciekając coś

ten kod:

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 

Jest to kod testu wskazuje zachowanie:

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

budynek na użytkownika @ user629923 sugestii, ale jest o wiele prostsze niż w innych odpowiedzi:

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)))