Python 如何在空白处拆分字符串并保留单词的偏移量和长度
我需要将字符串拆分为单词,但也需要得到单词的开始和结束偏移量。例如,如果输入字符串为:Python 如何在空白处拆分字符串并保留单词的偏移量和长度,python,string,Python,String,我需要将字符串拆分为单词,但也需要得到单词的开始和结束偏移量。例如,如果输入字符串为: input_string = "ONE ONE ONE \t TWO TWO ONE TWO TWO THREE" 我想得到: [('ONE', 0, 2), ('ONE', 5, 7), ('ONE', 9, 11), ('TWO', 17, 19), ('TWO', 21, 23), ('ONE', 25, 27), ('TWO', 29, 31), ('TWO', 33, 35), ('THR
input_string = "ONE ONE ONE \t TWO TWO ONE TWO TWO THREE"
我想得到:
[('ONE', 0, 2), ('ONE', 5, 7), ('ONE', 9, 11), ('TWO', 17, 19), ('TWO', 21, 23),
('ONE', 25, 27), ('TWO', 29, 31), ('TWO', 33, 35), ('THREE', 37, 41)]
我有一些工作代码,可以使用input_string.split和调用.index来实现这一点,但速度很慢。我试图通过手动迭代字符串来编写代码,但速度仍然较慢。有人对此有快速算法吗
以下是我的两个版本:
def using_split(line):
words = line.split()
offsets = []
running_offset = 0
for word in words:
word_offset = line.index(word, running_offset)
word_len = len(word)
running_offset = word_offset + word_len
offsets.append((word, word_offset, running_offset - 1))
return offsets
def manual_iteration(line):
start = 0
offsets = []
word = ''
for off, char in enumerate(line + ' '):
if char in ' \t\r\n':
if off > start:
offsets.append((word, start, off - 1))
start = off + 1
word = ''
else:
word += char
return offsets
通过使用timeit,“使用分割”是最快的,其次是“手动迭代”,那么到目前为止最慢的是使用re.finditer,如下所示。以下方法可以做到这一点:
def split_span(s):
for match in re.finditer(r"\S+", s):
span = match.span()
yield match.group(0), span[0], span[1] - 1
import re
s = 'ONE ONE ONE \t TWO TWO ONE TWO TWO THREE'
ret = [(m.group(0), m.start(), m.end() - 1) for m in re.finditer(r'\S+', s)]
print(ret)
这将产生:
[('ONE', 0, 2), ('ONE', 5, 7), ('ONE', 9, 11), ('TWO', 17, 19), ('TWO', 21, 23),
('ONE', 25, 27), ('TWO', 29, 31), ('TWO', 33, 35), ('THREE', 37, 41)]
以下运行速度稍快-节省约30%。我所做的只是提前定义函数:
def using_split2(line, _len=len):
words = line.split()
index = line.index
offsets = []
append = offsets.append
running_offset = 0
for word in words:
word_offset = index(word, running_offset)
word_len = _len(word)
running_offset = word_offset + word_len
append((word, word_offset, running_offset - 1))
return offsets
以下想法可能会加快速度:
from collections import deque
def using_split(line):
MAX_WORD_LENGTH = 10
line_index = line.index
words = line.split()
offsets = deque()
offsets_append = offsets.append
running_offset = 0
for word in words:
word_offset = line_index(word, running_offset, running_offset+MAX_WORD_LENGTH)
running_offset = word_offset + len(word)
offsets_append((word, word_offset, running_offset - 1))
return list(offsets)
这里有一些面向c的方法,只在整个字符串上迭代一次。 您还可以定义自己的分隔符。 测试和工作,但可能更清洁
def mySplit(myString, mySeperators):
w = []
o = 0
iW = False
word = [None, None,None]
for i,c in enumerate(myString):
if not c in mySeperators:
if not iW:
word[1]=i
iW = True
if iW == True and c in mySeperators:
word[2]=i-1
word[0] = myString[word[1]:i]
w.append(tuple(word))
word=[None,None,None]
iW = False
return w
mySeperators = [" ", "\t"]
myString = "ONE ONE ONE \t TWO TWO ONE TWO TWO THREE"
splitted = mySplit(myString, mySeperators)
print splitted
这似乎很快就能奏效:
tuple_list = [(match.group(), match.start(), match.end()) for match in re.compile("\S+").finditer(input_string)]
警告,此解决方案的速度受光速限制:
def get_word_context(input_string):
start = 0
for word in input_string.split():
c = word[0] #first character
start = input_string.find(c,start)
end = start + len(word) - 1
yield (word,start,end)
start = end + 2
print list(get_word_context("ONE ONE ONE \t TWO TWO ONE TWO TWO THREE"))
[('1',0,2),('1',5,7),('1',9,11),('2',17,19),('2',21,23),('1',25,27),('2',29,31),('2',33,35),('3',37,41)]
以下是一些想法,您可以分析它们是否足够快:
input_string = "".join([" ","ONE ONE ONE \t TWO TWO ONE TWO TWO THREE"," "])
#pre processing
from itertools import chain
stuff = list(chain(*zip(range(len(input_string)),range(len(input_string)))))
print stuff
stuff = iter(stuff)
next(stuff)
#calculate
switches = (i for i in range(0,len(input_string)-1) if (input_string[next(stuff)] in " \t\r\n") ^ (input_string[next(stuff)] in " \t\r\n"))
print [(word,next(switches),next(switches)-1) for word in input_string.split()]
#pre processing
from itertools import chain
stuff = list(chain(*zip(range(len(input_string)),range(len(input_string)))))
print stuff
stuff = iter(stuff)
next(stuff)
#calculate
switches = (i for i in range(0,len(input_string)-1) if (input_string[next(stuff)] in " \t\r\n") ^ (input_string[next(stuff)] in " \t\r\n"))
print [(input_string[i:j+1],i,j-1) for i,j in zip(switches,switches)]
通过直接作弊,我在几分钟内获得了大约35%的加速:我使用cython将您的using_split()函数转换为基于C的python模块。这是我尝试cython的第一个借口,我发现这很容易,也很有价值——见下文 使用C语言是万不得已的选择:首先,我花了几个小时胡思乱想,试图找到一种比使用_split()版本更快的算法。问题是,本机python str.split()速度惊人,比我使用numpy或re尝试的任何东西都快。因此,即使要扫描字符串两次,str.split()的速度也足够快,似乎不重要,至少对于这个特定的测试数据来说不重要 为了使用cython,我将您的解析器放在一个名为parser.pyx的文件中:
===================== parser.pyx ==============================
def using_split(line):
words = line.split()
offsets = []
running_offset = 0
for word in words:
word_offset = line.index(word, running_offset)
word_len = len(word)
running_offset = word_offset + word_len
offsets.append((word, word_offset, running_offset - 1))
return offsets
===============================================================
然后我运行这个来安装cython(假设是debian ish Linux系统):
然后我从这个python脚本调用了解析器:
================== using_cython.py ============================
#!/usr/bin/python
import pyximport; pyximport.install()
import parser
input_string = "ONE ONE ONE \t TWO TWO ONE TWO TWO THREE"
def parse():
return parser.using_split(input_string)
===============================================================
为了测试,我运行了以下命令:
python -m timeit "import using_cython; using_cython.parse();"
在我的机器上,使用_split()函数的纯python大约
8.5USEC运行时,而我的cython版本平均约为5.5USEC
更多详情参见我意识到python循环在这里是一个缓慢的操作,因此我开始使用位图,我走了这么远,它仍然很快,但我无法找到一种无循环的方法来获得开始/停止索引:
import string
table = "".join([chr(i).isspace() and "0" or "1" for i in range(256)])
def indexed6(line):
binline = string.translate(line, table)
return int(binline, 2) ^ int(binline+"0", 2)
返回的整数为每个开始位置和每个停止+1位置设置位
p.S.zip()的速度相对较慢:快到可以使用一次,慢到不能使用三次。回答很好,很优雅。但事实证明速度较慢:(@xorsyst,此解决方案与使用_split的
之间的速度差异有多大?此解决方案的速度大约是使用_split@xorsyst:你用编译的re
表达式试过了吗?优雅比…其他任何东西都好!哇-我很惊讶!对我来说,CPython只打了15%的折扣,而PyPy it ma更糟一点。我会暂缓悬赏,给其他人一些时间做一些更令人印象深刻的事情:)它很可能是特定于实现的,pypy并不是为速度而设计的。我的例子是0.020秒,你的例子是0.013秒。最大的开销是for循环,这是目前最短的。根据你使用它的方式,你可以通过把它变成一个生成器来提高整体性能,但如果你真的这样做的话我想从中找出一个只会让它慢下来的列表。我喜欢使用生成器的想法,因为调用代码可能不会使用所有结果,但我需要更长的时间来测试。如果这只是一个更大问题的一部分,我建议使用探查器,并尝试找出问题所在。使函数更快会有帮助,但也会减少对它的调用次数。如果有任何可能它不会使用所有的结果,那么我肯定会建议一个生成器!好主意,但它们没有帮助我担心这与我上面的手动迭代方法类似,只是出于某些原因,速度要慢得多。但是谢谢你的尝试。可能是贝卡使用我使用了一个列表作为分隔符。考虑到这一点,在python中迭代可能不是一个好主意,因为更高级别的字符串函数是用高效的C代码实现的。如果您有任何带有重复字符的长单词,line.index(word[0],running_offset)比line.index(word,running_offset)快(除非你有很多空白)。你可以(在c==word[0]的enumerate(word)中,i代表i,c。)下一步()恐怕这个测试比我的使用_split()稍微慢一点我可以看一下您的测试数据吗?我正在使用给定的字符串,并使用计时器进行测试。您是否尝试过对代码进行Cythonization?我会的,当cython.org从当前发生的任何不好的事情中恢复过来时。:-}
import string
table = "".join([chr(i).isspace() and "0" or "1" for i in range(256)])
def indexed6(line):
binline = string.translate(line, table)
return int(binline, 2) ^ int(binline+"0", 2)