假设我有一个字符串列表(或NumPy数组),例如,X =“abc”,“def”,“ghi”,“jkl”],一个索引列表,例如,J = [0,3],以及一个随机排列p,例如,[0,3,2,5,1,4]。
我想找到一种时间效率最高的方法来连接X中对应于索引J的字符串,即,对J中的每个j连接X[j]以得到'abcjkl',并将置换p应用于此字符串,即'ajclbk'。将置换p应用于字符串Y会导致另一个字符串Z,使得Z[i] = Y[p[i]]。
现在,我有X,J和p作为NumPy数组,并且
X = numpy.array(['abc', 'def', 'ghi', 'jkl'])
J = numpy.array([0, 3])
p = numpy.array([0, 3, 2, 5, 1, 4])
Y = ''.join(X[J])
Z = numpy.array(list(Y))
res = ''.join(Z[p])
字符串
有没有更快的方法来实现这一点?* 如果存在列表的解决方案,我不必使用NumPy数组 *。在我的应用程序中,列表X可以有1000万个字符串(使用英语字母表形成),每个字符串的长度为1000,索引J可以为500万,排列p可以为50亿。
2条答案
按热度按时间pcww981p1#
TL;DR
在假设原始输入中的字符串长度相等的情况下,您可以解析索引串联,结合
np.ndarray.view()
,可以实现非常快速的仅NumPy实现:字符串
这可以用Numba重新实现,以最大限度地减少临时内存使用:
型
目前还不清楚内部使用
np.uint8
是否会为更优化的方法带来任何速度优势。而如果输入使用
bytes
而不是str
,则此操作通常比TODO快(快得多?更长的答案
实际上,在速度方面,NumPy数组应该是解决这个问题的最方便的容器,特别是对于非常大的输入。
然而,不需要来回地处理字符串(或字节):你可以只在数组本身上应用索引,或者用
np.ndarray.view()
来适当地查看它,这是一个非常便宜的操作。同样的操作也可以用来再次查看str
的数据。假设OP的代码可以由以下函数总结:
型
基于
np.ndarray.view()
的解决方案如下所示:型
上面的代码确实具有处理Unicode字符串(而不仅仅是ASCII)的优点,并且不需要事先进行输入操作。
现在,仍然有一个相对昂贵的(
O(n)
)计算:高级索引,在上面的代码中执行了两次。通过使用字符串具有相同长度
k
的事实,我们可以将单个索引idx_ab
写为两个idx_a
的组合,后跟idx_b
:型
使用NumPy,它看起来像这样:
型
然而,上面仍然创建了一个大的临时数组来存储只使用一次的索引。在旅途中生成和访问索引会更有效,因此不需要大的临时对象。为此,可以编写一些显式循环来使用Numba加速:
型
请注意,原始数组最初被视为
np.int32
(与Unicode数据类型大小匹配),因为Numba仍然不支持创建Unicode数组。由于高级索引是NumPy中的一个高度优化的功能,因此仍然使用它和临时数组来存储索引可能会更有效,但是使用Numba加速代码来创建它们:
型
最终,使用
bytes
而不是str
会更快(但仅限于ASCII字符)。从NumPy字符数组到
bytes
,可以简单地使用np.ndarray.tobytes()
方法。上文提出的办法如下:
型
如果你的字符串只包含ASCII字符,你也可以将输入NumPy数组中大小为
N
的Unicode数据视为np.uint8
,而不是大小为1的Unicode数据。必须注意确保np.uint8
项(1字节)与Unicode项(4字节)对齐,所以需要每4个字符读取一次,但这是一个简单的切片:型
在假设只有ASCII字符串的情况下,可以重写这些字符串以在内部使用
np.uint8
,只要考虑到NumPy中Unicode数据类型与np.uint8
相比的填充不匹配,(Unicode数据类型使用每个字符4个字节),并使用不同的方法将数据转换回字符串(本质上是将np.ndarray.tobytes()
与str.decode()
方法结合起来,以返回str
),例如:型
目前还不清楚在内部使用
np.uint8
是否会为更优化的方法带来任何速度优势。为了完整起见,我还包括两个基于@KarlKnechtel方法的解决方案(一个用于
str
,一个用于bytes
):其具有与
take2b_np()
类似的精神,但确实需要一些相对较重的输入操作。为了检查这些方法是否都是等效的,这里报告应用于OP的输入:
要查看哪种方法在大输入时更快,可以使用用途:
请注意,我使用
N = 500_000
,因为较大的值会耗尽系统内存。更多基准测试
要了解不同的方法如何处理不同的输入大小,可以使用以下函数,其中
n
是arr
的大小,p
是idx_a
的大小,q
是idx_b
的大小:被称为:
得到以下图:
100d 1xx 1c 1d 1x的字符串
指出:
take2*_np()
方法比OP快得多,但比重新索引慢得多np.uint8
用于str
输入是否有益。bytes
似乎通常更快take2s_2d()
似乎比take2s_OP()
稍微快一些take2b_2d()
似乎比take2b_OP()
慢得多take2b_2d()
和take2s_2d()
看起来基本上是一样的。这意味着在
take2*_2d()
中对输入的“准备”给整体方法增加了显著的损失,否则它应该与take2b_np()
具有类似的性能。ymzxtsji2#
是的,字符串的长度相同(大约为1000)。您可以假设它们仅限于英文字母
在这些条件下,我希望Numpy能提供相当大的优势(我还假设
X
可以被预处理,并将以不同的J
和p
值重用。将
X
表示为一个2D字节数组(实际上是8位数值,而不是Python的bytes
类型):字符串
通过适当的切片然后使用
ravel
进行连接:型
像以前一样置换;然后不用
join
来连接字节(bytes
类型有一个等价的类型,但它是多余的),直接将数组传递给bytes
构造函数:型
如果有必要,我们可以将其
.decode
到字符串的末尾,就像我们在创建X
时encode
一样。