2016-06-08 12 views
3

일부 텍스트를 분할하려고합니다. 기본적으로 나는 "('1','a',NULL),(2,'b')" =>["('1','a',NULL)", "(2,'b')]"과 같이 1 단계 괄호를 분리하고 싶지만 내부에 가능한 인용 문자열을 알아야합니다. 나는 다음과 같은 시도따옴표 붙은 문자열을 포함 할 수있는 꺾쇠 괄호로 구분 된 텍스트

from splitter import split_text 


def test_normal(): 
    assert split_text("('1'),('2')") == ["('1')", "('2')"] 
    assert split_text("(1),(2),(3)") == ["(1)", "(2)", "(3)"] 


def test_complex(): 
    assert split_text("('1','a'),('2','b')") == ["('1','a')", "('2','b')"] 
    assert split_text("('1','a',NULL),(2,'b')") == ["('1','a',NULL)", "(2,'b')"] 


def test_apostrophe(): 
    assert split_text("('\\'1','a'),('2','b')") == ["('\\'1','a')", "('2','b')"] 


def test_coma_in_string(): 
    assert split_text("('1','a,c'),('2','b')") == ["('1','a,c')", "('2','b')"] 


def test_bracket_in_string(): 
    assert split_text("('1','a)c'),('2','b')") == ["('1','a)c')", "('2','b')"] 


def test_bracket_and_coma_in_string(): 
    assert split_text("('1','a),(c'),('2','b')") == ["('1','a),(c')", "('2','b')"] 


def test_bracket_and_coma_in_string_apostrophe(): 
    assert split_text("('1','a\\'),(c'),('2','b')") == ["('1','a\\'),(c')", "('2','b')"] 

: 그것은 최소한 다음 py.tests 만족해야

1) 정규 표현식이 최선의 해결책처럼 보이는

,하지만 불행하게도 내가 그랬어 모든 검사를 만족시키는 것은 없습니다.

def split_text(text): 
    return re.split('(?<=\)),(?=\()', text) 

그러나 분명히, 즉 오히려 단순하고 test_bracket_and_coma_in_stringtest_bracket_and_coma_in_string_apostrophe 실패 :

내 최고의 시도이다.

2) 유한 상태 기계와 같은 솔루션

은 내가 FSM 자신을 코딩하는 시도 :

그것은 작동
OUTSIDE, IN_BRACKETS, IN_STRING, AFTER_BACKSLASH = range(4) 


def split_text(text): 
    state = OUTSIDE 
    read = [] 
    result = [] 

    for character in text: 
     if state == OUTSIDE: 
      if character == ',': 
       result.append(''.join(read)) 
       read = [] 
      elif character == '(': 
       read.append(character) 
       state = IN_BRACKETS 
      else: 
       read.append(character) 

     elif state == IN_BRACKETS: 
      read.append(character) 
      if character == ')': 
       state = OUTSIDE 
      elif character == "'": 
       state = IN_STRING 

     elif state == IN_STRING: 
      read.append(character) 
      if character == "'": 
       state = IN_BRACKETS 
      elif character == '\\': 
       state = AFTER_BACKSLASH 

     elif state == AFTER_BACKSLASH: 
      read.append(character) 
      state = IN_STRING 

    result.append(''.join(read)) # The rest of string 
    return result 

이 모든 테스트를 통과,하지만 매우 느린 입니다.

from pyparsing import QuotedString, ZeroOrMore, Literal, Group, Suppress, Word, nums 

null_value = Literal('NULL') 
number_value = Word(nums) 
string_value = QuotedString("'", escChar='\\', unquoteResults=False) 
value = null_value | number_value | string_value 
one_bracket = Group(Literal('(') + value + ZeroOrMore(Literal(',') + value) + Literal(')')) 
all_brackets = one_bracket + ZeroOrMore(Suppress(',') + one_bracket) 


def split_text(text): 
    parse_result = all_brackets.parseString(text) 
    return [''.join(a) for a in parse_result] 

대한 파싱을

3)의 모든 테스트를 통과하지만, 놀랍게도 용액 # 2보다 느릴이다.

솔루션을 빠르고 강력하게 만드는 방법에 대한 아이디어가 있으십니까? 나는 명백한 무언가를 놓치고 있다는 느낌을 가지고있다.

답변

0

나는 이것을 요리했고 주어진 테스트에서 작동합니다.

tests = ["('1'),('2')", 
"(1),(2),(3)", 
"('1','a'),('2','b')", 
"('1','a',NULL),(2,'b')", 
"('\\'1','a'),('2','b')", 
"('1','a,c'),('2','b')", 
"('1','a)c'),('2','b')", 
"('1','a),(c'),('2','b')", 
"('1','a\\'),(c'),('2','b')"] 

for text in tests: 
    tmp = '' 
    res = [] 
    bracket = 0 
    quote = False 

    for idx,i in enumerate(text): 
     if i=="'": 
      if text[idx-1]!='\\': 
       quote = not quote 
      tmp += i 
     elif quote: 
      tmp += i 
     elif i==',': 
      if bracket: tmp += i 
      else: pass 
     else: 
      if i=='(':  bracket += 1 
      elif i==')': bracket -= 1 

      if bracket: tmp += i 
      else: 
       tmp += i 
       res.append(tmp) 
       tmp = '' 

    print res 

출력 :

["('1')", "('2')"] 
['(1)', '(2)', '(3)'] 
["('1','a')", "('2','b')"] 
["('1','a',NULL)", "(2,'b')"] 
["('\\'1','a')", "('2','b')"] 
["('1','a,c')", "('2','b')"] 
["('1','a)c')", "('2','b')"] 
["('1','a),(c')", "('2','b')"] 
["('1','a\\'),(c')", "('2','b')"] 

코드는 개선의 여지를 가지고 있으며, 편집 환영합니다. :)

+0

솔루션 # 2 및 # 3보다 빠르다고 생각하십니까? – Fenikso

+0

그래서 IMHO는 FSM이며 실제 데이터의 속도는 솔루션 # 2와 비슷합니다. – Fenikso

+0

해결책은 각 문자열에 대해 O (n)입니다. 여기서 n은 문자열의 길이입니다. 그래서, 적어도 한 번 이상 문자열을 스캔해야하기 때문에 얻을 수있는 가장 빠른 속도입니다. – Rahul

3

한 가지 방법은 (*SKIP)(*FAIL) 기능을 지원하는 새로운 regex 모듈을 사용하는 것입니다 : 그것은 말한다 세분화

import regex as re 

def split_text(text): 
    rx = r"""'.*?(?<!\\)'(*SKIP)(*FAIL)|(?<=\)),(?=\()""" 
    return re.split(rx, text) 

:

'.*?(?<!\\)'  # look for a single quote up to a new single quote 
       # that MUST NOT be escaped (thus the neg. lookbehind) 
(*SKIP)(*FAIL)| # these parts shall fail 
(?<=\)),(?=\() # your initial pattern with a positive lookbehind/ahead 

succeeds on all your examples합니다.

+0

흥미 롭다. 나는 도서관에 대해 몰랐다. 몇 가지 이유로 나는 그것을 사용할 수 없지만 그것을 계속 지켜 볼 것입니다. 감사! +1 – Fenikso

0

이것은 모든 테스트를 통과 한 것으로 보이는 정규 표현식입니다. 실제 데이터에서 실행하면 파이썬으로 구현 된 유한 상태 머신보다 약 6 배 빠릅니다.

PATTERN = re.compile(
    r""" 
     \( # Opening bracket 

      (?: 

      # String 
      (?:'(?: 
       (?:\\')|[^'] # Either escaped apostrophe, or other character 
       )*' 
      ) 
      | 
      # or other literal not containing right bracket 
      [^')] 

      ) 

      (?:, # Zero or more of them separated with comma following the first one 

      # String 
      (?:'(?: 
       (?:\\')|[^'] # Either escaped apostrophe, or other character 
       )*' 
      ) 
      | 
      # or other literal 
      [^')] 

      )* 

     \) # Closing bracket 
    """, 
    re.VERBOSE) 


def split_text(text): 
    return PATTERN.findall(text)