2012-03-07 4 views
19

현재의 Python 프로젝트는 들어오는 패키지를 처리하기 위해 많은 문자열 분할이 필요합니다. 나는 꽤 느린 시스템에서이를 실행할 것이므로, 이것에 대해 가장 효율적인 방법이 무엇인지 궁금해하고 있었다. 문자열은 다음과 같이 뭔가를 포맷 할 것 :파이썬에서 문자열을 분할하는 가장 효율적인 방법

Item 1 | Item 2 | Item 3 <> Item 4 <> Item 5 

설명 : 항목 5 항목 3 명을 초대 할 것입니다 동안이 특별한 예는, 처음 두 항목은 제목과 날짜입니다 목록에서 올 것입니다 (그 수는 0에서 n까지이며 n은 서버에 등록 된 사용자 수입니다.)

내가 무엇을보고, 나는 다음과 같은 옵션이 있습니다에서 :

  1. 을 반복 split()
  2. 를 사용하여 정규 표현식을 사용하고 정규식이 있습니다 (I 아직 생각하지 않은
  3. 다른 어떤 파이썬 기능을 기능 아마도 일부)

해결책 1은 |에서 분할 한 다음 결과 목록의 마지막 요소를 솔루션이 아마 같은 정규 표현식을 초래하면서이 예를 들어 <>을 :

((.+)|)+((.+)(<>)?)+

좋아,이 정규식은 끔찍, 나는 나 자신을 볼 수 있습니다. 또한 테스트되지 않았습니다. 그러나 당신은 아이디어를 얻습니다.

이제 나는 a)가 가장 적은 시간이 걸리고 b) 이상적으로는 최소 메모리를 사용하는 방법을 찾고 있습니다. 이 둘 중 하나만 가능하다면, 나는 더 적은 시간을 선호합니다. 이상적인 솔루션은 |으로 분리 된 항목이 더 많은 문자열과 <>이 완전히없는 문자열에도 사용할 수 있습니다. 적어도 정규 표현식 기반 솔루션

내 이해 (당신은 기본적으로 두 가지 결과 목록, |<>에 분할 두 번째로 분할 하나를 얻을 수 있기 때문에) split() 더 많은 메모리를 사용하는 것이 될 것이라고 할 것이나, RegEx가 어떻게 수행 할 것인지를 판단하기 위해 정규 표현식을 Python이 구현 한 것에 대해 충분히 알지 못합니다. split()도 다른 수의 항목과 두 번째 분리 자의 부재로 인해 정상적인 표현보다 동적이지 않습니다.

  • 예 내가 할 수있는, 단지 벤치 마크 두 솔루션,하지만 난 : 아직도, 나는 파이썬 정규식없이이 더 잘 할 수 있다는 인상을 흔들 수 없다, 내가

    일부 메모를 요구하고 이유입니다 일반적으로 파이썬에 대해 뭔가 배우려고하고 어떻게 작동하는지, 그리고 내가이 두 가지를 벤치마킹한다면, 나는 아직도 파이썬 함수가 무엇을 놓쳤는 지 모릅니다.

  • 그렇습니다.이 수준에서 최적화하는 것은 고성능 물건에만 꼭 필요한 것이지만 제가 말했던 것처럼 저는 파이썬에 대해 배우려고합니다.
  • 추가 : 원래의 질문에, 나는 완전히 내가합니다 (구분자 <>와 부품에서 |로 구분 된 부분, re.split(\||<>,input)에 의해 생성 된 매우 간단한 단순 목록을 구별 할 수 있어야한다는 얘기를 깜빡 했네요 @obmarg가 제안한대로) 너무 잘 작동하지 않을 것이다. 이 기준에 부합하는 솔루션은 높이 평가됩니다.

질문의 요약 : 어떤 이유로 가장 효율적인 해결책이 될까요?

import timeit 
import re 

def splitit(input): 
    res0 = input.split("|") 
    res = [] 
    for element in res0: 
     t = element.split("<>") 
     if t != [element]: 
      res0.remove(element) 
      res.append(t) 
    return (res0, res) 

def regexit(input): 
    return re.split("\||<>", input) 


def mgibsonbr(input): # Solution by @mgibsonbr 
    items = re.split(r'\||<>', input) # Split input in items 
    offset = 0 
    result = [] # The result: strings for regular itens, lists for <> separated ones 
    acc = None 
    for i in items: 
     delimiter = '|' if offset+len(i) < len(input) and input[offset+len(i)] == '|' else '<>' 
     offset += len(i) + len(delimiter) 
     if delimiter == '<>': # Will always put the item in a list 
      if acc is None: 
       acc = [i] # Create one if doesn't exist 
       result.append(acc) 
      else: 
       acc.append(i) 
     else: 
      if acc is not None: # If there was a list, put the last item in it 
       acc.append(i) 
      else: 
       result.append(i) # Add the regular items 
      acc = None # Clear the list, since what will come next is a regular item or a new list 
    return result 

def split2(input): # Solution by @duncan 
    res0 = input.split("|") 
    res1, res2 = [], [] 
    for r in res0: 
     if "<>" in r: 
      res2.append(r.split("<>")) 
     else: 
      res1.append(r) 
    return res1, res2 

print "mgibs:", timeit.Timer("mgibsonbr('a|b|c|de|f<>ge<>ah')","from __main__ import mgibsonbr").timeit() 
print "split:", timeit.Timer("splitit('a|b|c|de|f<>ge<>ah')","from __main__ import splitit").timeit() 
print "split2:", timeit.Timer("split2('a|b|c|de|f<>ge<>ah')","from __main__ import split2").timeit() 
print "regex:", timeit.Timer("regexit('a|b|c|de|f<>ge<>ah')","from __main__ import regexit").timeit() 
print "mgibs:", timeit.Timer("mgibsonbr('a|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>ah')","from __main__ import mgibsonbr").timeit() 
print "split:", timeit.Timer("splitit('a|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>ah')","from __main__ import splitit").timeit() 
print "split:", timeit.Timer("split2('a|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>ah')","from __main__ import split2").timeit() 
print "regex:", timeit.Timer("regexit('a|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>ah')","from __main__ import regexit").timeit() 

결과 :

때문에 여러 요청에, 나는 split() -solution에 대한 몇 가지 timeit 및 @obmarg에 의해 처음 제안 된 정규 표현식뿐만 아니라 @mgibsonbr 및 @duncan으로 솔루션을 실행 한 : 죄송합니다 (

순간
mgibs: 14.7349407408 
split: 6.403942732 
split2: 3.68306812233 
regex: 5.28414318792 
mgibs: 107.046683735 
split: 46.0844590775 
split2: 26.5595985591 
regex: 28.6513302646 

, 그것은 (적어도이 제한된 데이터 세트로) 길이에 관계없이, 모든 다른 알고리즘을 친다 @duncan에 의해 split2처럼 보인다, 또한 @ mgibsonbr의 솔루션은 몇 가지 성능 문제가 같습니다 'bout that, b ut 솔루션에 관계없이).

모두에게 감사드립니다.

+3

가 이동, 그냥'timeit'를 사용하고 직접 참조하십시오. 그것은 간단합니다. 내 베팅은'str.split'에 있습니다. – wim

+1

긴 문자열의 경우 '재'가 더 빠를 것이라고 생각합니다. 하지만 반드시 벤치마킹해야합니다. – Dikei

+0

나는 이것이 IO 경계 일 것이라고 확신한다. 내 첫 번째 추측은 모든 유형의 분할이 디스크 또는 네트워크에서받는 입력보다 빠르다는 것입니다. –

답변

12

약간 놀랍게도 split()이 코드에서 그렇게 잘못 수행되어서 좀 더 자세히 살펴보고 내부 루프에서 list.remove()을 호출한다는 사실을 알게되었습니다. 또한 각 문자열에 여분의 시간 인 split()을 부릅니다. 그 (것)들을 제거하고 split()를 사용하여 해결책은 짧은 줄에 정규 표현식 손을 아래로 치고 더 긴 것에 아주 가까운 두번째 온다.

split: 6.14933066757 
split2: 4.09465555192 
regex: 5.10777759588 
split: 47.0092413098 
split2: 26.9300592585 
regex: 25.9168630604 

물론 split2()

가 정규식 솔루션하지 않는 반면에 당신이 원하는 중첩 된 목록을 제공합니다 다음과 같은 결과를 제공

import timeit 
import re 

def splitit(input): 
    res0 = input.split("|") 
    res = [] 
    for element in res0: 
     t = element.split("<>") 
     if t != [element]: 
      res0.remove(element) 
      res.append(t) 
    return (res0, res) 

def split2(input): 
    res0 = input.split("|") 
    res1, res2 = [], [] 
    for r in res0: 
     if "<>" in r: 
      res2.append(r.split("<>")) 
     else: 
      res1.append(r) 
    return res1, res2 

def regexit(input): 
    return re.split("\||<>", input) 


print "split:", timeit.Timer("splitit('a|b|c|de|f<>ge<>ah')","from __main__ import splitit").timeit() 
print "split2:", timeit.Timer("split2('a|b|c|de|f<>ge<>ah')","from __main__ import split2").timeit() 
print "regex:", timeit.Timer("regexit('a|b|c|de|f<>ge<>ah')","from __main__ import regexit").timeit() 
print "split:", timeit.Timer("splitit('a|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>ah')","from __main__ import splitit").timeit() 
print "split2:", timeit.Timer("split2('a|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>ah')","from __main__ import split2").timeit() 
print "regex:", timeit.Timer("regexit('a|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>ah')","from __main__ import regexit").timeit() 

.

+0

잘 잡습니다. 테스트 분할 기능을 구현할 때 전혀 생각하지 않았습니까? 이제는 목록에서 제거하는 것이 "비싸다"는 내용을 제외하고는 내용에 관계없이 모든 것을 나눌 필요가 없습니다 (자바와 유사한'contains'를 찾고 있었지만 파이썬이 이것을합니다. 다르게). 고마워. – malexmave

10

나는 그것이 가장 효율적이지만, 확실히 코드에 대한 가장 쉬운이 같은 것 같다 있는지 확실하지 않습니다 :

>>> input = "Item 1 | Item 2 | Item 3 <> Item 4 <> Item 5" 
>>> re.split("\||<>", input) 
>>> ['Item 1 ', ' Item 2 ', ' Item 3 ', ' Item 4 ', ' Item 5'] 
나는 그것이 일반보다 효율적되는 공정한 기회가 생각

이전 분할 에서뿐만 아니라 (입력 데이터에 따라 다름) 첫 번째 분할에서 출력 된 모든 문자열에 대해 두 번째 분할 작업을 수행해야하므로 메모리 또는 시간에 효율적이지는 않습니다.

내가 쉽게 잘못 될 수 있다고 말했지만, 확실한 유일한 방법은 시간을 정하는 것입니다.

+0

알고리즘에 감사드립니다. 다른 부분과 구별하고 처리하기 쉽도록 중첩 목록으로 두 번째 부분 (항목 3-5)이 필요하다는 점을 잊어 버렸습니다. 하지만 잊어 버린 이후로 나는 해결책을 찾지 못한다고 반박 할 수는 없다. ;-) – malexmave

+1

@malexmave : 일단 목록을 얻으면 쉽다. 'alist = re.split ("\ || <>", input); 결과 = [alist [0], alist [1], alist [2 :]]'. Voila. 중첩 된. –

+0

목록 안에 위치가 고정되어 있으면 고마워요. 내가 [아래로 코멘트] (http://stackoverflow.com/questions/9602856/most-efficient-way-to-split-strings-in-python/9603035#comment12182897_9603443)에서 설명한 문제는 무엇입니까? 당신은 그것에 쉬운 해결책이 있습니까? 또는 적어도 @mgibsonbr이 제안한 것보다 쉬운 하나? 노력해 주셔서 감사합니다! – malexmave

3

여러 번 나누기를 호출하면 불필요한 중간 문자열이 생성 될 수 있기 때문에 비효율적 일 수 있습니다. 당신이 제안한 것과 같은 정규 표현식을 사용하는 것은 작동하지 않을 것입니다. 왜냐하면 캡쳐 그룹은 모든 항목이 아닌 마지막 항목 만 가져올 것이기 때문입니다. obmarg가 제안한 것처럼 정규 표현식을 사용하여 분할하는 것이 가장 좋은 방법 인 것처럼 보입니다. "평평한"목록이 당신이 찾고있는 것이라고 가정합니다. 경우,

items = re.split(r'\||<>', input) 
offset = 0 
for i in items: 
    delimiter = '|' if input[offset+len(i)] == '|' else '<>' 
    offset += len(i) + len(delimiter) 
    # Do something with i, depending on whether | or <> was the delimiter 

을에서 마지막 : 당신이 평평 목록을 원하지 않는 경우

, 당신은 사용 된 구분 확인하기 위해 원래의 입력을 확인, 그 결과를 반복 먼저 다음 정규식을 사용하여 분할 할 수 있습니다 예를 들어 시작 및 끝 인덱스 만 사용하여 공간을 절약하기 위해 하위 문자열을 전혀 사용하지 않으려면 re.finditer이 작업을 수행 할 수 있습니다. 분리 문자를 반복하고 구분 기호 (| 또는 <>)가있는 항목에 따라 텍스트 사이에 무엇인가를하십시오. 많은 복잡한 상황을 처리해야하기 때문에 더 복잡한 작업이지만 필요에 따라 가치가있을 수 있습니다.

업데이트 : 입력 형식이 균일 한 특정 경우에는 obmarg의 솔루션이 가장 좋습니다. 당신이해야하는 경우, 사후 처리는 결과가 중첩 된 목록을 가지고 :

split_result = re.split("\||<>", input) 
result = [split_result[0], split_result[1], [i for i in split_result[2:] if i]] 

(마지막 지능형리스트는 마지막 | 후 항목이 없을 경우 [''][]을 얻을 대신 것입니다 보장하는 것입니다)

업데이트 2 : 업데이트 된 질문을 읽은 후에, 나는 마침내 당신이 달성하고자하는 것을 이해했습니다.다음은 전체 예제는 프레임 워크 이전에 제안하여,이다 :

items = re.split(r'\||<>', input) # Split input in items 
offset = 0 
result = [] # The result: strings for regular itens, lists for <> separated ones 
acc = None 
for i in items: 
    delimiter = '|' if offset+len(i) < len(input) and input[offset+len(i)] == '|' else '<>' 
    offset += len(i) + len(delimiter) 
    if delimiter == '<>': # Will always put the item in a list 
     if acc is None: 
      acc = [i] # Create one if doesn't exist 
      result.append(acc) 
     else: 
      acc.append(i) 
    else: 
     if acc is not None: # If there was a list, put the last item in it 
      acc.append(i) 
     else: 
      result.append(i) # Add the regular items 
     acc = None # Clear the list, since what will come next is a regular item or a new list 
print result 

귀하의 예제와 함께 테스트 결과는했다 : 당신이 <> 다음 다른 문자열에 표시하지 않을 것을 알고 있다면

['a', 'b', 'c', 'de', ['f', 'ge', 'aha'], 
'b', 'c', 'de', ['f', 'ge', 'aha'], 
'b', 'c', 'de', ['f', 'ge', 'aha'], 
'b', 'c', 'de', ['f', 'ge', 'aha'], 
'b', 'c','de', ['f', 'ge', 'aha'], 
'b', 'c', 'de', ['f', 'ge', 'aha'], 
'b', 'c', 'de', ['f', 'ge', 'aha'], 
'b', 'c', 'de', ['f', 'ge', 'aha'], 
'b', 'c', 'de', ['f', 'ge', 'aha'], 
'b', 'c', 'de', ['f', 'ge', 'aha'], 
'b', 'c', 'de', ['f', 'ge', 'ah']] 
+0

입력 해 주셔서 감사합니다. 현재'for'Block이하는 일을 이해하려고합니다. 내가 이해할 때,'|'- 분리 된 블록이 끝나고'<>'- 분리 된 블록이 시작되는 오프셋을 계산한다. 맞습니까? – malexmave

+0

별로 ... 각 반복마다 '오프셋'은 다음 항목입니다. 'offset + len (i)'는 다음 분리 문자 (또는 문자열의 끝)가있는 곳입니다. 필자는 입력 문자열을 다시 검사 할 필요없이 구분 기호를 빠르게 찾을 수 있도록 사용했습니다. 그러나 문자열에 정규 형식이 있으면 실제로 필요하지 않습니다. 업데이트 된 답변을 참조하십시오. – mgibsonbr

+0

감사합니다. 나는'<>'가 시작하기 전에'|'로 분리 된 변수의 숫자로 똑같은 결과를 얻는 비슷한 방법이 없다고 생각합니다. 맞습니까? 'item 1 | 항목 2 <> 항목 3 | Item 4 '로 표시되며, 이는 클라이언트 측에서 포맷팅 기능을 변경함으로써 피할 수 있습니다. – malexmave

2

'<>'을 '|'로 바꿀 수 있습니다. 다음 단일 분할 :

>>> input = "Item 1 | Item 2 | Item 3 <> Item 4 <> Item 5" 
>>> input.replace("<>", "|").split("|") 
['Item 1 ', ' Item 2 ', ' Item 3 ', ' Item 4 ', ' Item 5'] 

이렇게하면 여러 분할보다 훨씬 빠릅니다. re.split을 사용하는 것보다 빠르거나 빨라질 수도 있습니다 - 시간은 친구입니다.

편집 : A와 timeit을 가지고

>>> timeit input.replace("<>", "|").split("|") 
1000000 loops, best of 3: 980 ns per loop 
>>> import re 
>>> timeit re.split(r"\||<>", input) 
100000 loops, best of 3: 3.07 us per loop 

(NB이 ipython 사용 : 당신이 내 버전보다 3 배 이상 빠른 re.split보다 공급 샘플 문자열 내 시스템에서 내장 명령).

+0

아이디어를 제공해 주셔서 감사합니다. 그러나 이렇게하면 OP로 편집 할 때, 결과로 나오는 부분 문자열 중 어느 부분이'| '대신'<>'에 의해 분리되어 있는지를 알 수있는 방법이없는 평면 목록이 생성 될 것입니다. 네스트 된리스트를 필요로하지 않는다면 여전히 좋은 생각이다. – malexmave

1

을 사용하면을 대체 할 수 있습니다. 먼저 <>|으로 바꾼 다음 |으로 나눕니다.

def replace_way(input): 
    return input.replace('<>','|').split('|') 

시간 성능 : 내 컴퓨터에

import timeit 
import re 

def splitit(input): 
    res0 = input.split("|") 
    res = [] 
    for element in res0: 
     t = element.split("<>") 
     if t != [element]: 
      res0.remove(element) 
      res.append(t) 
    return (res0, res) 

def regexit(input): 
    return re.split("\||<>", input) 

def replace_way(input): 
    return input.replace('<>','|').split('|') 


print "split:", timeit.Timer("splitit('a|b|c|de|f<>ge<>ah')","from __main__ import splitit").timeit() 
print "regex:", timeit.Timer("regexit('a|b|c|de|f<>ge<>ah')","from __main__ import regexit").timeit() 
print "replace:",timeit.Timer("replace_way('a|b|c|de|f<>ge<>ah')","from __main__ import replace_way").timeit() 
print "split:", timeit.Timer("splitit('a|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>ah')","from __main__ import splitit").timeit() 
print "regex:", timeit.Timer("regexit('a|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>ah')","from __main__ import regexit").timeit() 
print "replace:",timeit.Timer("replace_way('a|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>ah')","from __main__ import replace_way").timeit() 

결과 :

split: 11.8682055461 
regex: 12.7430856814 
replace: 2.54299265006 
split: 79.2124379066 
regex: 68.6917008003 
replace: 10.944842347 
+0

제안 해 주셔서 감사합니다. 그러나 "내부"항목 ('| '대신'<>'에 의해 분리 된 항목)에 대한 중첩 목록을 반환하지 않는 문제가 있습니다. – malexmave

관련 문제