【问题标题】:Nested enumerated for loops to comprehension list嵌套枚举for循环到理解列表
【发布时间】:2021-02-13 03:10:22
【问题描述】:

我正在使用来自 textdistance 库 (https://github.com/life4/textdistance) 的 textdistance.needleman_wunsch.normalized_distance。我将它与来自Scipy 库的cdist 一起使用来计算序列的对距离。但是由于嵌套的枚举for循环,这个过程很长。

在这里您可以找到 textdistance 库中使用的代码,这需要时间,我想知道您是否知道如何加快嵌套的 for 循环,也许使用列表理解?

s1 = "sentence1"
s2 = "sentevfers2"
gap = 1


def sim_func(*elements):
    """Return True if all sequences are equal.
    """
    try:
        # for hashable elements
        return len(set(elements)) == 1
    except TypeError:
        # for unhashable elements
        for e1, e2 in zip(elements, elements[1:]):
            if e1 != e2:
                return False
        return True


dist_mat = numpy.zeros(
    (len(s1) + 1, len(s2) + 1),
    dtype=numpy.float,
)

# DP initialization
for i in range(len(s1) + 1):
    dist_mat[i, 0] = -(i * gap)

# DP initialization
for j in range(len(s2) + 1):
    dist_mat[0, j] = -(j * gap)

""" Possible enhancement with list comprehension ? """
# Needleman-Wunsch DP calculation
for i, c1 in enumerate(s1, 1):
    for j, c2 in enumerate(s2, 1):
        match = dist_mat[i - 1, j - 1] + sim_func(c1, c2)
        delete = dist_mat[i - 1, j] - gap
        insert = dist_mat[i, j - 1] - gap
        dist_mat[i, j] = max(match, delete, insert)
distance = dist_mat[dist_mat.shape[0] - 1, dist_mat.shape[1] - 1]
print(distance)

【问题讨论】:

  • 该嵌套循环不会累积第三个列表,因此理解并不适合直接翻译。您也许可以使用理解来创建matchdeleteinsert 三元组的列表,然后遍历这些元组,但我看不出有什么更好的。

标签: python performance list-comprehension nested-loops


【解决方案1】:

这段代码很慢有几个原因:

  • 它(可能)在 CPython 中执行并用纯 Python 编写,这是一个不适合这种数字代码的慢解释器
  • sim_func 是一种比较各种元素的通用方法,但也非常低效(分配、散列、异常处理和字符串操作)。

代码不能轻易并行化,因此向量化 numpy.但是,您可以使用 Numba 来加快速度。仅当输入字符串很大或此处理执行很多时间时才值得。如果不是这种情况,请使用更合适的编程语言(例如 C、C++、D、Rust 等)或专用于此的原生 Python 模块 .

这是优化后的 Numba 代码:

s1 = "sentence1"
s2 = "sentevfers2"
gap = 1  # Assume this is an integer

@njit
def NeedlemanWunschDP(dist_mat, s1, s2):
    for i in range(1, len(s1)+1):
        for j in range(1, len(s2)+1):
            match = dist_mat[i - 1, j - 1] + (s1[i-1] == s2[j-1])
            delete = dist_mat[i - 1, j] - gap
            insert = dist_mat[i, j - 1] - gap
            dist_mat[i, j] = max(match, delete, insert)

dist_mat = numpy.empty(
    (len(s1) + 1, len(s2) + 1),
    dtype=numpy.int64,
)

# DP initialization
for i in range(len(s1) + 1):
    dist_mat[i, 0] = -(i * gap)

# DP initialization
for j in range(len(s2) + 1):
    dist_mat[0, j] = -(j * gap)

# Transform the strings to fast integer arrays
tmp_s1 = numpy.array([ord(e) for e in s1], dtype=numpy.int64)
tmp_s2 = numpy.array([ord(e) for e in s2], dtype=numpy.int64)
# Needleman-Wunsch DP calculation
NeedlemanWunschDP(dist_mat, tmp_s1, tmp_s2)
distance = dist_mat[dist_mat.shape[0] - 1, dist_mat.shape[1] - 1]
print(distance)

NeedlemanWunschDP 的编译时间在我的机器上大约需要 400 毫秒,但生成的代码在大字符串上比 快 1800 倍

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-07
    • 2017-09-01
    • 2019-08-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多