【问题标题】:Is there a faster way of converting a number to a name?有没有更快的方法将数字转换为名称?
【发布时间】:2012-06-18 15:35:29
【问题描述】:

以下代码定义了映射到数字的名称序列。它旨在获取一个数字并检索特定名称。该类通过确保名称存在于其缓存中来操作,然后通过索引到其缓存中返回名称。这里面的问题:如何在不存储缓存的情况下根据数字计算名称?

名称可以被认为是一个以 63 为基数的数字,除了第一个数字始终以 53 为基数。

class NumberToName:

    def __generate_name():
        def generate_tail(length):
            if length > 0:
                for char in NumberToName.CHARS:
                    for extension in generate_tail(length - 1):
                        yield char + extension
            else:
                yield ''
        for length in itertools.count():
            for char in NumberToName.FIRST:
                for extension in generate_tail(length):
                    yield char + extension

    FIRST = ''.join(sorted(string.ascii_letters + '_'))
    CHARS = ''.join(sorted(string.digits + FIRST))
    CACHE = []
    NAMES = __generate_name()

    @classmethod
    def convert(cls, number):
        for _ in range(number - len(cls.CACHE) + 1):
            cls.CACHE.append(next(cls.NAMES))
        return cls.CACHE[number]

    def __init__(self, *args, **kwargs):
        raise NotImplementedError()

以下交互式会话显示了一些预期按顺序返回的值。

>>> NumberToName.convert(0)
'A'
>>> NumberToName.convert(26)
'_'
>>> NumberToName.convert(52)
'z'
>>> NumberToName.convert(53)
'A0'
>>> NumberToName.convert(1692)
'_1'
>>> NumberToName.convert(23893)
'FAQ'

不幸的是,这些数字需要映射到这些确切的名称(以允许反向转换)。


请注意: 接收可变数量的比特并将其明确转换为数字。此数字应明确转换为 Python 标识符命名空间中的名称。最终,有效的 Python 名称将被转换为数字,这些数字将被转换为可变位数。


最终解决方案:

import string

HEAD_CHAR = ''.join(sorted(string.ascii_letters + '_'))
TAIL_CHAR = ''.join(sorted(string.digits + HEAD_CHAR))
HEAD_BASE, TAIL_BASE = len(HEAD_CHAR), len(TAIL_CHAR)

def convert_number_to_name(number):
    if number < HEAD_BASE: return HEAD_CHAR[number]
    q, r = divmod(number - HEAD_BASE, TAIL_BASE)
    return convert_number_to_name(q) + TAIL_CHAR[r]

【问题讨论】:

  • 为什么有这个特殊要求?能否请您详细说明无缓存的目的?
  • 缓存消耗了大量的内存,实际上是不需要的。
  • 接收可变数量的比特并将其明确转换为数字。此数字应明确转换为 Python 标识符命名空间中的名称。最终,有效的 Python 名称将被转换为数字,这些数字将被转换为可变位数。
  • @recursive:这就是问题的原因。 “不存储缓存,如何根据数字计算名称?”
  • @NoctisSkytower:对,我在回答 dan-boa 的问题。

标签: python namespaces converter


【解决方案1】:

这是一个有趣的小问题,充满了 1 个错误。

没有循环:

import string

first_digits = sorted(string.ascii_letters + '_')
rest_digits = sorted(string.digits + string.ascii_letters + '_')

def convert(number):
    if number < len(first_digits):
        return first_digits[number]

    current_base = len(rest_digits)
    remain = number - len(first_digits)
    return convert(remain / current_base) + rest_digits[remain % current_base]

还有测试:

print convert(0)
print convert(26)
print convert(52)
print convert(53)
print convert(1692)
print convert(23893)

输出:

A
_
z
A0
_1
FAQ

【讨论】:

  • 感谢您的帮助!看到你的 remain 变量帮助很大。
  • 最后三行的替代方案:number, remain = divmod(number - len(first_digits), len(rest_digits)); return convert(number) + rest_digits[remain].
  • 使用递归而不是循环不一定更快(不是你说的那样)。不过,它确实减少了代码行数。不错的答案!
  • 现在有个问题是如何完成相反的计算:stackoverflow.com/questions/17045037
【解决方案2】:

您得到的是bijective numeration 的损坏形式(通常的示例是电子表格列名,它是双射base-26)。

一种生成双射计数的方法:

def bijective(n, digits=string.ascii_uppercase):
    result = []
    while n > 0:
        n, mod = divmod(n - 1, len(digits))
        result += digits[mod]
    return ''.join(reversed(result))

您需要做的就是为53 &gt;= n &gt; 0 的情况提供一组不同的数字。您还需要将 n 增加 1,因为双射 0 是空字符串,而不是 "A"

def name(n, first=sorted(string.ascii_letters + '_'), digits=sorted(string.ascii_letters + '_' + string.digits)):
    result = []
    while n >= len(first):
        n, mod = divmod(n - len(first), len(digits))
        result += digits[mod]
    result += first[n]
    return ''.join(reversed(result))

【讨论】:

    【解决方案3】:

    针对前 10,000 个名称进行测试:

    first_chars = sorted(string.ascii_letters + '_')
    later_chars = sorted(list(string.digits) + first_chars)
    
    def f(n):
        # first, determine length by subtracting the number of items of length l
        # also determines the index into the list of names of length l
        ix = n
        l = 1
        while ix >= 53 * (63 ** (l-1)):
            ix -= 53 * (63 ** (l-1))
            l += 1
    
        # determine first character
        first = first_chars[ix // (63 ** (l-1))]
    
        # rest of string is just a base 63 number
        s = ''
        rem = ix % (63 ** (l-1))
        for i in range(l-1):
            s = later_chars[rem % 63] + s
            rem //= 63
    
        return first+s
    

    【讨论】:

      【解决方案4】:

      您可以使用this 中的代码回答“Python 中的 Base 62 转换”问题(或者可能是其他答案之一)。

      使用引用的代码,我认为您的真正问题的答案是“如何在不存储缓存的情况下根据数字计算名称?”将是如果名称的第一个字符是数字(将名称转换回数字时将被忽略),则使名称成为数字的简单 base 62 转换,可能带有前导下划线。

      这是说明我建议的示例代码:

      from base62 import base62_encode, base62_decode
      
      def NumberToName(num):
          ret = base62_encode(num)
          return ('_' + ret) if ret[0] in '0123456789' else ret
      
      def NameToNumber(name):
          return base62_decode(name if name[0] is not '_' else name[1:])
      
      if __name__ == '__main__':
          def test(num):
              name = NumberToName(num)
              num2 = NameToNumber(name)
              print 'NumberToName({0:5d}) -> {1!r:>6s}, NameToNumber({2!r:>6s}) -> {3:5d}' \
                    .format(num, name, name, num2)
      
          test(26)
          test(52)
          test(53)
          test(1692)
          test(23893)
      

      输出:

      NumberToName(   26) ->    'q', NameToNumber(   'q') ->    26
      NumberToName(   52) ->    'Q', NameToNumber(   'Q') ->    52
      NumberToName(   53) ->    'R', NameToNumber(   'R') ->    53
      NumberToName( 1692) ->   'ri', NameToNumber(  'ri') ->  1692
      NumberToName(23893) -> '_6dn', NameToNumber('_6dn') -> 23893
      

      如果数字可能是负数,您可能需要修改引用答案中的代码(并且那里有一些关于如何操作的讨论)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-01-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-06-29
        • 2020-06-22
        • 1970-01-01
        • 2011-05-18
        相关资源
        最近更新 更多