b'\n'在bytesPython3对象中,用替换所有“通用换行符”的最佳(最干净,最快)方法是什么?
编辑:我最终使用b'\n'.join(bytestr.splitlines())它,因为它似乎是最安全的,而且我不介意在最后放置一个潜在的换行符。
但是请@norok2注意以下关于警告,时间安排和更快解决方案的出色答案。
nor*_*ok2 15
派对晚了一点,但让我们看看我能为我们做些什么。
首先,一个披露:我最喜欢的是@JohnHennig的double replace()方法,因为它相当快,而且很清楚发生了什么。
我认为没有其他的简单和快速的内Python的解决方案超出了在其他的答案(其中一些我稍微修改以获得完全相同的结果作为双已经提出replace())。
但是,有可能加快速度。我最喜欢的加速方法numba由于对bytes()和和的支持有限而无法轻松应用bytearray()。接下来的事情是使用Cython并在C / C ++中使用它。由于使用C字符串令人讨厌,所以我求助于C ++字符串。这样,可以通过一个循环编写此任务。根据我的测试,这是最快的方法。为简单起见,我使用Cython魔术师用IPython编写了此代码。
%load_ext Cython
Run Code Online (Sandbox Code Playgroud)
%%cython --cplus -c-O3 -c-march=native -a
from libcpp.string cimport string
cdef extern from *:
"""
#include <string>
std::string & erase(
std::string & s,
std::size_t pos,
std::size_t len) {
return s.erase(pos, len); }
"""
string& erase(string& s, size_t pos, size_t len)
cdef string unl_prealloc(string s):
cdef char nl_lf = b'\n'
cdef char nl_cr = b'\r'
cdef char null = b'\0'
cdef size_t s_size = s.size()
cdef string result = string(s_size, null)
cdef size_t i = 0
cdef size_t j = 0
while i + 1 <= s_size:
if s[i] == nl_cr:
result[j] = nl_lf
if s[i + 1] == nl_lf:
i += 1
else:
result[j] = s[i]
j += 1
i += 1
return erase(result, j, i - j)
def unl_cython(string b):
return unl_prealloc(b)
Run Code Online (Sandbox Code Playgroud)
这些是我的机器上的基准测试图:
(由于在其他计算机上运行,实际时间在EDIT时发生了变化)
为了完整性,以下是其他经过测试的功能:
import re
def unl_replace(s):
return s.replace(b'\r\n', b'\n').replace(b'\r', b'\n')
# EDIT: was originally the commented code, but it is less efficient
# def unl_join(s):
# nls = b'\r\n', b'\r', b'\n'
# return b'\n'.join(s.splitlines()) + (
# b'\n' if any(s.endswith(nl) for nl in nls) else b'')
def unl_join(s):
result = b'\n'.join(s.splitlines())
nls = b'\r\n', b'\r', b'\n'
if any(s.endswith(nl) for nl in nls):
result += b'\n'
return result
# Following @VPfB suggestion
def unl_join_new(s):
return b'\n'.join((s + b'\0').splitlines())[:-1]
def unl_re(s, match=re.compile(b'\r\n?')):
return match.sub(b'\n', s)
def unl_join_naive(s): # NOTE: not same result as `unl_replace()`
return b'\n'.join(s.splitlines())
Run Code Online (Sandbox Code Playgroud)
这是用于生成输入的函数:
def gen_input(num, nl_factor=0.10):
nls = b'\r\n', b'\r', b'\n'
words = (b'a', b'b', b' ')
random.seed(0)
nl_percent = int(100 * nl_factor)
base = words * (100 - nl_percent) + nls * nl_percent
return b''.join([base[random.randint(0, len(base) - 1)] for _ in range(num)])
Run Code Online (Sandbox Code Playgroud)
和产生的数据和图(从脚本这里)被称为有:
funcs = (
unl_replace,
unl_cython,
unl_join,
unl_join_new,
unl_re,
unl_join_naive,
)
runtimes, input_sizes, labels, results = \
benchmark(funcs, gen_input=gen_input)
plot_benchmarks(runtimes, input_sizes, labels)
Run Code Online (Sandbox Code Playgroud)
我还用显式循环测试了其他两种可能的实现,但是由于它们比拟议的解决方案要慢几个数量级,因此我从比较中省略了它们,但我在此报告以供将来参考:
funcs = (
unl_replace,
unl_cython,
unl_join,
unl_join_new,
unl_re,
unl_join_naive,
)
runtimes, input_sizes, labels, results = \
benchmark(funcs, gen_input=gen_input)
plot_benchmarks(runtimes, input_sizes, labels)
Run Code Online (Sandbox Code Playgroud)
(编辑:对假设/潜在问题的评论)
例如,对于“混合换行”文件,b'alpha\nbravo\r\ncharlie\rdelta'在理论上将始终\r\n被视为1行还是2行。上面实现的所有方法将具有相同的行为,并视为\r\n一个换行符。
此外,所有这些方法都会因复杂的编码\r而出现问题和/或\r\n虚假的存在,例如,从@JohnHennig注释中提取马拉雅拉姆字母?编码为b'\ r \ n' UTF-16,bytes.splitlines()似乎不知道它,并且所有经过测试的方法的行为均相同:
def unl_loop(b):
nl_cr = b'\r'[0]
nl_lf = b'\n'[0]
result = b''
i = 0
while i + 1 <= len(b):
if b[i] == nl_cr:
result += b'\n'
i += 2 if b[i + 1] == nl_lf else 1
else:
result += b[i:i + 1]
i += 1
return result
def unl_loop_bytearray(b):
nl_cr = b'\r'[0]
nl_lf = b'\n'[0]
result = bytearray()
i = 0
while i + 1 <= len(b):
if b[i] == nl_cr:
result.append(nl_lf)
i += 2 if b[i + 1] == nl_lf else 1
else:
result.append(b[i])
i += 1
return bytes(result)
def unl_loop_bytearray2(b):
nl_cr = b'\r'[0]
nl_lf = b'\n'[0]
result = bytearray(len(b))
i = j = 0
while i + 1 <= len(b):
if b[i] == nl_cr:
result[j] = nl_lf
i += 2 if b[i + 1] == nl_lf else 1
else:
result[j] = b[i]
i += 1
j += 1
return bytes(result[:j])
def unl_loop_bytearray3(b):
nl_cr = b'\r'[0]
nl_lf = b'\n'[0]
b = bytearray(b)
i = 0
while i + 1 <= len(b):
if b[i] == nl_cr:
if b[i + 1] == nl_lf:
del b[i]
else:
b[i] = nl_lf
i += 1
return bytes(b)
Run Code Online (Sandbox Code Playgroud)
最后,unl_join_naive()仅依赖于Python实现行拆分,这意味着将要发生的事情不太明显,但将来可能会更好地支持此类问题。如果此方法位于字符串的末尾,则该方法还将删除最后一个换行符,因此需要一些额外的代码(这将在时序中添加一个通常很小的常量偏移量)来克服此问题。解决此问题的一些建议包括:
bytes.splitlines()实现,这现在不再是问题,但是如果杂乱无巧地\r\n碰巧是最后一个字符并且bytes.splitlines()行为对此变得敏感,则将来可能会成为问题)unl_join();\0在原始输入中添加任何非换行的ASCII 7位字符(例如),并删除中的最后一个元素join()(该元素似乎比上一个更安全,更快),如中所述unl_join_new()。这是我过去使用的:
>>> bytestr = b'A sentence\rextending over\r\nmultiple lines.\n'
>>> bytestr.replace(b'\r\n', b'\n').replace(b'\r', b'\n')
b'A sentence\nextending over\nmultiple lines.\n'
Run Code Online (Sandbox Code Playgroud)
我不知道这是否是最好的方法,但是它简单明了且易于推理。例如,它首先replace是两个字节序列的键,然后是其余的孤立\r字符的键。
即使上面的示例混合了不同类型的换行符字节序列,也存在一个隐含的假设,即该方法仅在始终使用相同的输入的输入上使用。不管是哪个换行符,它都是不可知的。恰当的例子:b'\r\r\n\n'如果允许混合使用换行符,则没有唯一的解释,因为它可能表示3或4个空行。
正则表达式也可以与bytes对象一起使用。关于什么:
import re
data = b"hello\r\n world\r\naaaaa\rbbbbbb"
print(re.sub(b"\r\n?",b"\n",data))
Run Code Online (Sandbox Code Playgroud)
结果:
b'hello\n world\naaaaa\nbbbbbb'
Run Code Online (Sandbox Code Playgroud)
正则表达式查找\r可选的后跟,\n然后替换为\n。如您所见,它涵盖了所有情况。它也只需要1次通过。从我的长椅上,似乎是一个单纯的双bytes.replace像约翰的回答是要快得多,虽然。
| 归档时间: |
|
| 查看次数: |
599 次 |
| 最近记录: |