【问题标题】:Is using `str` the correct idiom for working with digits in Python在 Python 中使用“str”是处理数字的正确习语
【发布时间】:2013-11-15 23:25:08
【问题描述】:

我了解在 Python 中处理数字的数字的一种方法是将数字转换为字符串,然后使用字符串方法将生成的“数字”分割成“数字”组。例如,假设我有一个测试素数的函数prime,我可以用

确认整数n既是左又是右truncatable prime
all(prime(int(str(n)[:-i])) and prime(int(str(n)[i:])) for i in range(1, len(str(n))))

此方法首先将 n 转换为字符串以便对其进行切片,然后将该切片转换回整数以便检查其素数。也许这是我使用静态类型语言的历史,或者是一些模糊的认为字符串“昂贵”的想法,或者是包含用于类似操作的内置功能的语言的经验(例如 Mathematica 的 IntegerDigitsFromDigits);但我想知道这是否是执行此类任务的正确方法。

在字符串和数字之间来回转换是否是在 Python 中访问数字的正确(甚至是唯一)方法。有没有更有效的方法?

【问题讨论】:

  • 出于好奇,这是针对Project Euler 的问题吗?我喜欢那个网站!
  • 对于非常大的数字和/或大量重复,一种减少开支的方法是创建一个变量来保存数字的字符串版本。这样一来,您就不会调用两次str
  • Python 是强类型的——尽管它通常不是显式或静态类型的。
  • @dstromberg:没错。固定。
  • 模数和整数除法有什么问题?

标签: python string indexing type-conversion digits


【解决方案1】:

在您的示例代码中,您可以使用divmod 而不是对数字进行字符串切片。 divmod(x, y) 返回元组 x//y, x%y,对于 y 的值,10**i 正是您想要的数字的左右部分。这不一定更 Pythonic,尽管它可能会更快一些。

sn = str(n)
all(prime(int(sn[:i])) and prime(int(sn[i:])) for i in range(1, len(sn))) # original
all(all(map(prime, divmod(n, 10**i))) for i in range(1, len(sn))) # variant using divmod

我认为对于更一般的数字运算,使用str 可能非常明智,因为对数字基数的幂进行大量数学运算可能比直接对字符串中的数字进行运算更难理解。

编写要读取的代码,除非它对性能非常敏感。

【讨论】:

  • 另一个很好的答案。在短短几分钟内我从这个问题中学到了很多东西,这给我留下了深刻的印象!
  • 我喜欢这个。这是一种快速且惯用的方法,可以根据可用位数将数字划分为右半部分和左半部分。
  • "编写要读取的代码,除非它对性能非常敏感。" - 明智的话!
  • @steveha:请注意,我在 divmod 变体中使用了第一个版本的字符串长度来确定要迭代的范围。您也可以仅通过数学运算找到位数,但这是一件丑陋的事情:int(math.ceil(math.log10(n+1)))(尽管看起来在 Python 3 中不需要 int 调用)。
  • 这就是为什么我提倡在进行数字操作时使用 str() 的原因。在某些特定情况下,您可以使用数学方法在性能方面击败它,它提供了一个更清晰、更通用的解决方案。
【解决方案2】:

Python 的原生整数以 2 的幂为基数存储,因此将它们转换为十进制表示法或从十进制表示法转换需要真正的工作。在许多需要频繁访问非常大整数的十进制数字的“谜题”;-) 问题中,使用decimal 模块可以改变世界。它以 10 的幂为基数存储值,因此“转换”到/从十进制数字是微不足道的费用。

>>> import decimal
>>> x = decimal.Decimal('1e20') - 1
>>> x
Decimal('99999999999999999999')
>>> x.as_tuple().digits
(9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9)

这需要与位数成线性关系的时间。将本机整数转换为十进制所需的时间是位数的二次方。

不过,我可以在这里猜测您的具体应用,但使用 divmod() 确实是最好的方法。

【讨论】:

    【解决方案3】:

    这个怎么样?

    def digits(n):
        if n == 0:
            yield 0
        else:
            while n:
                yield n % 10
                n //= 10
    
    for digit in digits(123):
        # do something with digit
    

    这应该比您展示的示例更方便和更有效。

    编辑:我想再添加两件事。

    0) 您可以根据需要扩展该技术。假设您要测试右截断素数。假设你有一个函数is_prime()

    def right_trunc_int_values(n):
        if n == 0:
            yield 0
        else:
            while n:
                yield n
                n //= 10
    
    assert(all(is_prime(n) for n in right_trunc_int_values(317))
    

    1) 为了解决方便使用数字的一般问题,您也许可以使用decimal 模块。我会更多地研究这个。但与此同时,您可以使用我的digits() 函数来制作可索引的数字列表:

    d = list(digits(123))
    print(d[2])  # prints 2
    

    编辑:将一系列数字转换为整数值非常容易。

    def int_from_digits(digits):
        result = 0
        found_a_digit = False
        for digit in reversed(digits):
            result = result * 10 + digit
        return result 
    
    def is_right_trunc_prime(n):
        d = list(digits(n))
        return all(is_prime(int_from_digits(d[:i]) for i in range(len(d), -1, -1)))
    
    # example from question of left and right truncatable check
    d = list(digits(n))
    all(prime(int_from_digits(d[:-i])) and prime(int_from_digits(d[i:])) for i in range(1, len(d)))
    

    【讨论】:

    • 这在任何需要数字自己的情况下显然很有用,但我不清楚如何将其合并到问题中的示例中,例如需要数字序列的示例转换回单个数字。
    • @raxacoricofallapatorius,我是否充分回答了您的问题?附:我不得不在没有测试的情况下发布以上所有内容。如果我犯了任何错误,请告诉我,我会为您改正。
    • 在我的机器上digits“应该”比str更有效率说得很好,但根据timeitstr(1234567890)的速度几乎是list(digits(1234567890))的4倍(0.923 微秒对 3.55 微秒)。大概是因为与用 C 实现的内置函数相比,Python 中的任何循环都可能很慢。当然,您需要在选择使用哪个之前测试真实代码,转换并不是整个性能成本。
    • @SteveJessop,我一点也不惊讶str() 是一个更快的操作。但是,您是否还考虑了对字符串进行切片然后将数字转换回int 的开销?一旦您支付了获取数字列表的前期成本,您可以简单地对其进行索引以获取每个数字值,我预测d[i]d 是数字列表时将比int(s[i]) 更快,其中@ 987654338@ 是str。我预测数字列表不需要太多的处理就可以收回成本。但是,正如您所指出的,在性能方面,我们必须始终衡量而不是猜测。
    • @steveha:到目前为止,我还做了另外两个测试。一个比较转换 int -> str -> int 与仅获取数字。 int -> str -> int 仍然快两倍,即使它解决了更多问题。我没有测试字符串切片,我只是猜测它会与列表切片相同或更好,所以我没有打扰。我做的另一个测试是 Shashank 的代码,所以与你的无关。如果我明天回到这个问题,我可能会做一个“时间枪战”的答案。确实,对于需要它的问题,再次转换每个数字的需要会增加str 的成本:这个恰好不需要。
    【解决方案4】:

    这一直是我的方法,而且效果很好,尽管我从未对速度进行过太多测试。它在需要迭代数字的排列/组合时特别有效,因为您可以使用 itertools 包中的函数构建此类字符串。

    当然还有其他方法涉及不太直接的数学运算,但除非速度绝对至关重要,否则我觉得字符串方法是最 Pythonic 的。


    例如,这里是一种更数学的方法,其中 a 和 b 从右侧开始索引(即个位为 0,十位为 1,等等):

    def getSubdigits(n, a, b):
        n %= 10 ** a
        n //= 10 ** b
        return n
    

    要使用与字符串切片相同的索引,您需要先找到总位数,函数变为:

    def getSubdigits2(n, a, b):
        l = int(math.ceil(math.log10(n)))
        n %= 10 ** (l - a)
        n //= 10 ** (l - b)
        return n
    

    和字符串切片等效:

    def subDigits3(n, a, b):
        return int(str(n)[a:n])
    

    这是计时结果:

    • subDigits: 0.293327726114
    • subDigits2: 0.850861833337
    • subDigits3: 0.990543234267

    我从该结果中得出的结论是,除非您真的关心速度,否则切片方法很好,在这种情况下,您需要使用第一种方法并考虑另一个方向的索引。

    【讨论】:

    • 是的,我假设在某些情况下可能有一些数学会改变问题,但假设已经解决并且我已经减少了数学,这里的问题是这是否是 Pythonic(也许是唯一可行的)方法。
    • 现在你让我真的很好奇(因为我一直在做这种摆弄)。让我用简单的算术提出一个通用的解决方案,看看我发现了什么。
    • 为什么不只是n = n % 1000 而不是n -= (n // 1000) * 1000
    • @Jud:我在编辑中引入了一个小错字——“for”的“fore”(我无法更正)。
    【解决方案5】:

    在没有str() 和切片的情况下测试左截断素数:

    def is_prime(n):
        if n < 2:
            return False
        elif n == 2:
            return True
        elif n % 2 == 0:
            return False
        return all(n % x for x in xrange(3,int(pow(n,0.5))+1,2))
    
    def is_left_truncatable_prime(n):
        largest_power_of_ten = 1
        while largest_power_of_ten < n:
            largest_power_of_ten *= 10
        while True:
            largest_power_of_ten /= 10 # Use // in Python 3
            if is_prime(n):
                n %= largest_power_of_ten
                if n == 0:
                    return True
            else:
                return False
    
    print is_left_truncatable_prime(167) # True
    print is_left_truncatable_prime(173) # True
    print is_left_truncatable_prime(171) # False
    

    我没有对此进行广泛的测试,如果有任何错误,请见谅。如果有,请告诉我,我会修复它们。

    编辑:稍微修正一下代码。

    【讨论】:

    • 嗯。在我的机器上,使用173 作为测试并更改提问者的代码以使用您的is_prime 函数并且只测试左性而不是左性和右性,此代码比提问者的。如果这在其他机器、其他版本的 Python 和其他值上得到证实(当然,我的单一测试远未解决),那么在所有方面都首选str:性能、简洁性、可读性。
    • @Steve 好吧,他的算法是 O((log_10(n))^2) 因为切片是线性时间操作,他切片序列中的每个数字 (1+2+...+log (n))。从time complexity page for Python 您可以看到“获取切片”为 O(k),其中 k 是切片的长度。所以是的,也许我的函数对于 173 慢了 5%,但我认为对于大数来说它会更快,因为它的 O(log_10(n)) 而不是 O((log_10(n))^2)。不过,也许 Python 会挑战我 :)
    • 现在应该快一点了。我修复了一些与n %= largest_power_of_ten 相同的逻辑。
    猜你喜欢
    • 2016-04-09
    • 2015-08-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多