当前位置: 首页 > 面试题库 >

指向结果的Python字符串比较

祁承望
2023-03-14
问题内容

我试图比较2个1000字节的字符串,并想知道差异的确切位置,即;字符串与哪个字节不同。.是否有任何函数可以确定它?


问题答案:

我试图测试此处给出的答案,并且提出了另一个 更快 (通常情况下)的解决方案,尽管它不太优雅。

首先,让我们看看所提出的解决方案有多快:

In [15]: def check_genexp(a, b):
    ...:     return next(idx for idx, c in enumerate(a) if c != b[idx])

In [16]: %timeit check_genexp("a"*9999 + "b", "a"*9999 + "c")
1000 loops, best of 3: 1.04 ms per loop

In [17]: from difflib import SequenceMatcher

In [18]: def check_matcher(a, b):
    ...:     return next(SequenceMatcher(a=a, b=b).get_matching_blocks())
    ...:

In [19]: %timeit check_matcher("a"*9999+"b", "a"*9999+"c")
100 loops, best of 3: 11.5 ms per loop

如您所见,genexp比快很多difflib,但这可能是由于SequenceMatcher它比查找第一个非等号字符要好得多。

现在,我们如何加快速度?好吧,我们可以使用“二进制搜索”
!!!这个想法是,如果两个字符串不相等,则前半部分是不同的,或者后半部分是不同的(或两者都不同),但是在那种情况下,我们只关心前半部分,因为我们想要第一个不同的索引

因此,我们可以执行以下操作:

def binary_check(a, b):
    len_a, len_b = len(a), len(b)
    if len_a == len_b:
        return binary_check_helper(a, b)
    min_length, max_length = min(len_a, len_b), max(len_a, len_b)
    res = binary_check_helper(a[:min_length], b[:min_length])
    return res if res >= 0 else min_length

def binary_check_helper(a, b):
    if a == b:
        return -1
    length = len(a)

    if length == 1:
        return int(a[0] == b[0])
    else:
        half_length = length // 2
        r = binary_check_helper(a[:half_length], b[:half_length])
        if r >= 0:
            return r
        r = binary_check(a[half_length:], b[half_length:])
        if r >= 0:
            return r + half_length
        return r

结果:

In [34]: %timeit binary_check("a"*9999 + "b", "a"*9999 + "c")
10000 loops, best of 3: 28.4 µs per loop

这比genexp快 三十五倍

为什么这样有效?比较显然需要花费线性时间,因此看起来我们要比以前做更多的工作……这确实是正确的,但它是在“ C级别”完成的,因此结果是该方法实际上更快。

请注意,这某种程度上是“特定于实现的”,因为诸如PyPy之类的实现可能会将genexp优化为单个C-
for循环,这将击败任何事物。在Jython或IronPython之类的实现上也 可能 比CPython慢​​很多。

该方法具有与其他方法相同的渐近复杂度,即O(n)。字符串最多会分成两半,log_2(n)并且每次进行相等性测试时都需要花费线性时间。乍一看,它似乎是Θ(n * logn)算法,但事实并非如此。递归公式为:

T(n) = T(n//2) + Θ(n) = Σ_{i=0}^{logn}(n/2^i)
     = Θ(n(1 + 1/2 + 1/4 + ...)) <= Θ(2n) = Θ(n)

更多结果:

In [37]: %timeit binary_check("a"*10**6 + "b", "a"*10**6 + "c")
100 loops, best of 3: 2.84 ms per loop

In [38]: %timeit check_genexp("a"*10**6 + "b", "a"*10**6 + "c")
10 loops, best of 3: 101 ms per loop

In [39]: %timeit binary_check(15 * "a"*10**6 + "b", 15 * "a"*10**6 + "c")
10 loops, best of 3: 53.3 ms per loop

In [40]: %timeit check_genexp(15 * "a"*10**6 + "b", 15 * "a"*10**6 + "c")
1 loops, best of 3: 1.5 s per loop

如您所见,即使使用巨大的字符串,此方法仍然快30倍。

注意: 该解决方案的缺点是它是ϴ(n)而不是O(n),即,它 始终 读取 整个 字符串以返回结果。即使第一个字符已经不同。事实上:

In [49]: a = "b" + "a" * 15 * 10**6
    ...: b = "c" + "a" * 15 * 10**6
    ...:

In [50]: %timeit binary_check(a, b)
100 loops, best of 3: 10.3 ms per loop

In [51]: %timeit check_genexp(a, b)
1000000 loops, best of 3: 1.3 µs per loop

这是预料之中的。但是,与显式循环相比,此解决方案只需花费很少的精力即可变得更加高效:

In [59]: a = "a" * 2 * 10**5 + "b" + "a" * 15*10**6
    ...: b = "a" * 2 * 10**5 + "c" + "a" * 15*10**6

In [60]: %timeit check_genexp(a, b)
10 loops, best of 3: 20.3 ms per loop

In [61]: %timeit binary_check(a, b)
100 loops, best of 3: 17.3 ms per loop

根据这个简单的基准,对于大字符串,如果差异远大于总长度的 1.3% ,则二进制检查会更好。

也可以引入一些启发式方法。例如,如果两个字符串的最小长度大于某个截断值,则首先仅检查该截断处的前缀是否不同,如果是,则不能忽略此后的所有内容,从而避免比较整个字符串。这可以轻松实现:

def binary_check2(a, b, cutoff=1000):
    len_a, len_b = len(a), len(b)
    if min(len_a, len_b) > cutoff:
        small_a, small_b = a[:cutoff], b[:cutoff]
        if small_a != small_b:
            return binary_check_helper(a[:cutoff], b[:cutoff])
    # same as before

根据应用程序,您可以选择一个使平均时间最短的截止时间。在任何情况下,这都是一种临时启发式方法,可能会奏效,也可能无法奏效,因此,如果您处理的字符串 很长
且仅带有短的公共前缀,则应使用“快速失败”算法genexp

在python3.4上执行的计时。使用字节代替unicode字符串不会显着改变结果



 类似资料:
  • 我有两个向量: 我试图使用比较它们。不幸的是,给出了一个意想不到的结果。 虽然我希望: 那么,这是什么原因造成的呢?怎样才能达到预期的效果呢? 这个问题似乎与这样一个事实有关,即两个向量的最后一个元素与将更改为例如确实给出了预期的结果相同,并且还因为将设置为给出而不是。 编辑 换句话说,我希望丢失的元素(当长度不同时)作为零传递(只有似乎给出

  • 主要内容:到底使用字符数组还是字符串常量C语言中没有特定的字符串类型,我们通常是将字符串放在一个字符数组中,这在《 C语言字符数组和字符串》中已经进行了详细讲解,这里不妨再来演示一下: 运行结果: https://www.xnip.cn https://www.xnip.cn 字符数组归根结底还是一个数组,上节讲到的关于 指针和数组的规则同样也适用于字符数组。更改上面的代码,使用指针的方式来输出字符串: 运行结果: https://ww

  • 4、输入字符串指令(Input String Instruction) 该指令是从某一指定的端口接受一个字符串,并存入一片存储单元之中。输入端口由DX指定,存储单元的首地址和读入数据的个数分别由ES:DI和CX来确定。在指令的执行过程中,还根据标志位DF对寄存器DI作相应增减。该指令不影响任何标志位。 与指令有关的操作数ES、DI、DX和CX等都是隐含操作数。 指令的格式:INS 地址表达式 IN

  • 问题内容: 我注意到我正在编写的Python脚本表现得很松散,并将其追溯到无限循环,其中循环条件为。在调试器中运行它,结果发现那条线实际上是。当我将其更改为!=’‘而不是时,它工作正常。 另外,即使比较或值,通常还是最好还是默认使用吗?我一直喜欢使用,因为我发现它在美学上更令人愉悦和pythonic(这就是我陷入这个陷阱的方式…),但是我想知道是否打算仅在你关心找到两个对象时才保留它?具有相同ID

  • 问题内容: 为什么当我使用以下操作对字符求和时,它返回数字而不是字符?它不应该给出相同的结果吗? 下面的代码复制了字符: doubleChar(“ The”)→“ TThhee” 问题答案: 以下表达式的结果 是String连接的结果。Java语言规范说明 字符串串联的结果是对String对象的引用,该对象是两个操作数字符串的串联。在新创建的字符串中,左侧操作数的字符位于右侧操作数的字符之前。 的

  • 问题内容: 我对Java中的字符串比较有一点疑问,请考虑以下代码: 上面的代码始终打印为,就像我尝试这样: 它会永远打印我。是的,我知道应该使用方法进行字符串比较。但这是面试中提出的问题之一,我很困惑。谁能指导我这种行为? 据我所知,在代码片段1中,返回对象,因此对象比较返回false。我对吗? 问题答案: “ String.replace(’t’,’T’)返回对象,因此对象比较返回false。对