如果您的目标是速度,且您的数据容易分类,我建议使用numpy解决方案。
假设您有:
a = np.array(['A', 'B', 'A', 'B', 'A', 'C', 'D', 'E', 'D', 'E', 'D', 'F', 'G', 'A', 'B'])
tolerance = 1
为了检查任何元素是否完全相等,你可以进行类似于 diff 的操作,但是通过判断相等性来实现:
tolerance += 1
mask = a[:-tolerance] == a[tolerance:]
如果你将这个布尔掩码 tolerance
向右移动,每个连续的运行将是你感兴趣的元素。一种简短的方法是使用 np.lib.stride_tricks.as_strided
:
def smear(mask, n):
view = np.lib.stride_tricks.as_strided(mask, shape=(n + 1, mask.size - n),
strides=mask.strides * 2)
view[1:, view[0]] = True
你甚至可以将其转换为一行代码,因为它是原地操作的:
np.lib.stride_tricks.as_strided(mask, shape=(n + 1, mask.size - n),
strides=mask.strides * 2)[1:, mask[:-n]] = True
然后你应用它:
smear(mask, tolerance)
使用np.diff
、np.flatnonzero
和np.split
(参考)的组合可以轻松找到并提取连续的运行:
result = np.split(a, np.flatnonzero(np.diff(m)) + 1)[1 - m[0]::2]
该解决方案唯一的不足之处在于它无法捕获彼此间距小于
tolerance
的匹配元素。为了解决这个问题,我们可以使用
np.lib.stride_tricks.as_strided
来创建一个考虑到公差的遮罩(使用
np.any
):
b = np.lib.stride_tricks.as_strided(np.r_[a, np.zeros(tolerance, dtype=a.dtype)],
shape=(tolerance + 1, a.size),
strides=a.strides * 2)
b
现在是一个 3x15 的数组(其中 a
长度为 15),第二个维度只是跟在开头后面的字符。请记住,这只是对原始数据的一种视图。对于大型数组,该操作基本上是免费的。
现在您可以对第一个维度应用 np.any
,以确定哪些字符在容差范围内重复:
mask = np.any(b[0] == b[1:], axis=0)
从这里开始,我们像以前一样继续。这使得函数相当小:
总结:
def find_patterns(a, tol):
a = np.asanyarray(a)
tol += 1
b = np.lib.stride_tricks.as_strided(np.r_[a, np.zeros(tol, dtype=a.dtype)],
shape=(tol + 1, a.size),
strides=a.strides * 2)
mask = np.any(b[0] == b[1:], axis=0)
np.lib.stride_tricks.as_strided(mask, shape=(tol + 1, mask.size - tol),
strides=mask.strides * 2)[1:, mask[:-tol]] = True
return np.split(a, np.flatnonzero(np.diff(mask)) + 1)[1 - mask[0]::2]
>>> find_patterns(['A', 'B', 'A', 'B', 'A', 'C', 'D', 'E', 'D', 'E', 'D', 'F', 'G', 'A', 'B'], 1)
[array(['A', 'B', 'A', 'B', 'A'], dtype='<U1'),
array(['D', 'E', 'D', 'E', 'D'], dtype='<U1')]
>>> find_patterns(['A', 'B', 'C', 'A', 'D', 'E', 'A'], 1)
[]
>>> find_patterns(['A', 'B', 'C', 'A', 'D', 'E', 'A'], 2)
[array(['A', 'B', 'C', 'A', 'D', 'E', 'A'], dtype='<U1')]
附录
如果您查看下面的参考资料,您会发现上面显示的涂抹掩码和查找掩码部分的方法是为简洁而选择的,而不是为了速度。从这里获取的更快的涂抹掩码的方法是:
def smear(mask, n):
n += 1
mask1 = mask.copy()
len0, len1 = 1, 1
while len0 + len1 < n:
mask[len0:] |= mask1[:-len0]
mask, mask1 = mask1, mask
len0, len1 = len1, len0 + len1
mask1[n - len0:] |= mask[:-n + len0]
return mask1
同样,从数组中提取连续掩码区域的更快方法(取自此处)是:
def extract_masked(a, mask):
mask = np.concatenate(([False], mask, [False]))
idx = np.flatnonzero(mask[1:] != mask[:-1])
return [a[idx[i]:idx[i + 1]] for i in range(0, len(idx), 2)]
参考资料
['B', 'A', 'B']
这样的集合吗? - MazdakABABACDEDEDFGA
?另外,在你的最后一个例子中,当字符串有4个不同于A的字母(B、B、C、D)时,容差如何为2? - Tamas Hegedus