Python:将字母数字字符串可逆地编码为整数

8
我想把一个由字母数字组成的字符串转换成整数,然后再将这个整数转换回字符串:
字符串 -> 整数 -> 字符串
换句话说,我想用一个整数来表示一个字母数字字符串。
我找到了一种可行的解决方案,并将其包含在答案中,但我不认为这是最好的解决方案,我对其他想法/方法感兴趣。
请不要将此标记为重复,因为已经存在许多类似的问题,我特别想要将字符串轻松转换为整数,反之亦然。
对于包含字母数字字符的字符串,这应该起作用,即包含数字和字母的字符串。
4个回答

10

以下是我目前的翻译:

首先定义一个字符串

m = "test123"

字符串 -> 字节

mBytes = m.encode("utf-8")

bytes -> int

mInt = int.from_bytes(mBytes, byteorder="big")

int -> bytes

mBytes = mInt.to_bytes(((mInt.bit_length() + 7) // 8), byteorder="big")

bytes -> string

m = mBytes.decode("utf-8")

集成在一起

m = "test123"
mBytes = m.encode("utf-8")
mInt = int.from_bytes(mBytes, byteorder="big")
mBytes2 = mInt.to_bytes(((mInt.bit_length() + 7) // 8), byteorder="big")
m2 = mBytes2.decode("utf-8")
print(m == m2)

这里有一个与上述内容完全相同的可重用版本:

class BytesIntEncoder:

    @staticmethod
    def encode(b: bytes) -> int:
        return int.from_bytes(b, byteorder='big')

    @staticmethod
    def decode(i: int) -> bytes:
        return i.to_bytes(((i.bit_length() + 7) // 8), byteorder='big')

如果您使用的是Python <3.6版本,请移除可选类型注释。

测试:

>>> s = 'Test123'
>>> b = s.encode()
>>> b
b'Test123'

>>> BytesIntEncoder.encode(b)
23755444588720691
>>> BytesIntEncoder.decode(_)
b'Test123'
>>> _.decode()
'Test123'

3
这很简单明了。而且它很快,因为所有繁重的算术都是由能以C速度运行的方法执行的。 - PM 2Ring
1
顺便说一下,您可以使用否定来执行向上取整除法。例如,-(-n // 8) - PM 2Ring
1
@A-B-B :) 这是 Python 处理 //% 有符号操作数的惯例带来的一个好处。但如果你不知道发生了什么,它可能有点神秘,所以我通常在使用时添加一个简短的注释,比如 # 向上取整除法 - PM 2Ring
1
这是最节省内存且最直接的解决方案。我很少能找到这样的组合。 ;) - WhiteWood

4
回想一下,字符串可以编码为字节,然后可以将其编码为整数。然后可以反转编码以获取字节,后跟原始字符串。
此编码器使用 binascii 生成与 charel-f 答案中的完全相同的整数编码。我相信它是相同的,因为我进行了广泛的测试。
来源:this answer
from binascii import hexlify, unhexlify

class BytesIntEncoder:

    @staticmethod
    def encode(b: bytes) -> int:
        return int(hexlify(b), 16) if b != b'' else 0

    @staticmethod
    def decode(i: int) -> int:
        return unhexlify('%x' % i) if i != 0 else b''

如果您使用的是Python <3.6版本,则需要删除可选类型注释。
快速测试:
>>> s = 'Test123'
>>> b = s.encode()
>>> b
b'Test123'

>>> BytesIntEncoder.encode(b)
23755444588720691
>>> BytesIntEncoder.decode(_)
b'Test123'
>>> _.decode()
'Test123'

2
假设字符集仅为字母数字,即a-z A-Z 0-9,每个字符需要6位。因此,使用8位字节编码在理论上是对内存的低效利用。
这个答案将输入字节转换为一系列6位整数。它使用位操作将这些小整数编码为一个大整数。是否实际转化为现实世界的存储效率可以通过sys.getsizeof来衡量,并且对于较长的字符串更有可能。
这个实现根据字符集的选择定制了编码方式。例如,如果你只使用string.ascii_lowercase(5位)而不是string.ascii_uppercase + string.digits(6位),那么编码效率会相应提高。
还包括单元测试。
import string


class BytesIntEncoder:

    def __init__(self, chars: bytes = (string.ascii_letters + string.digits).encode()):
        num_chars = len(chars)
        translation = ''.join(chr(i) for i in range(1, num_chars + 1)).encode()
        self._translation_table = bytes.maketrans(chars, translation)
        self._reverse_translation_table = bytes.maketrans(translation, chars)
        self._num_bits_per_char = (num_chars + 1).bit_length()

    def encode(self, chars: bytes) -> int:
        num_bits_per_char = self._num_bits_per_char
        output, bit_idx = 0, 0
        for chr_idx in chars.translate(self._translation_table):
            output |= (chr_idx << bit_idx)
            bit_idx += num_bits_per_char
        return output

    def decode(self, i: int) -> bytes:
        maxint = (2 ** self._num_bits_per_char) - 1
        output = bytes(((i >> offset) & maxint) for offset in range(0, i.bit_length(), self._num_bits_per_char))
        return output.translate(self._reverse_translation_table)


# Test
import itertools
import random
import unittest


class TestBytesIntEncoder(unittest.TestCase):

    chars = string.ascii_letters + string.digits
    encoder = BytesIntEncoder(chars.encode())

    def _test_encoding(self, b_in: bytes):
        i = self.encoder.encode(b_in)
        self.assertIsInstance(i, int)
        b_out = self.encoder.decode(i)
        self.assertIsInstance(b_out, bytes)
        self.assertEqual(b_in, b_out)
        # print(b_in, i)

    def test_thoroughly_with_small_str(self):
        for s_len in range(4):
            for s in itertools.combinations_with_replacement(self.chars, s_len):
                s = ''.join(s)
                b_in = s.encode()
                self._test_encoding(b_in)

    def test_randomly_with_large_str(self):
        for s_len in range(256):
            num_samples = {s_len <= 16: 2 ** s_len,
                           16 < s_len <= 32: s_len ** 2,
                           s_len > 32: s_len * 2,
                           s_len > 64: s_len,
                           s_len > 128: 2}[True]
            # print(s_len, num_samples)
            for _ in range(num_samples):
                b_in = ''.join(random.choices(self.chars, k=s_len)).encode()
                self._test_encoding(b_in)


if __name__ == '__main__':
    unittest.main()

使用示例:

>>> encoder = BytesIntEncoder()
>>> s = 'Test123'
>>> b = s.encode()
>>> b
b'Test123'

>>> encoder.encode(b)
3908257788270
>>> encoder.decode(_)
b'Test123'

2
非常感谢您的回答和付出的时间。祝您有愉快的一天,希望有人能从您的回答中受益! - charelf

1

所以我需要将一个字典按数字转换,可能看起来有些丑陋,但它的效率在于每个字符(英文字母)恰好对应2个数字,而且能够传输任何类型的Unicode字符。

import json

myDict = {
    "le key": "le Valueue",
    2 : {
        "heya": 1234569,
        "3": 4
    },
    'Α α, Β β, Γ γ' : 'שלום'
}
def convertDictToNum(toBeConverted):
    return int(''.join([(lambda c: c if len(c) ==2 else '0'+c )(str(ord(c) - 26)) for c in str(json.dumps(toBeConverted))]))

def loadDictFromNum(toBeDecoded):
    toBeDecoded = str(toBeDecoded)
    return json.loads(''.join([chr(int(toBeDecoded[cut:cut + 2]) + 26) for cut in range(0, len(toBeDecoded), 2)]))

numbersDict = convertDictToNum(myDict)
print(numbersDict)
# 9708827506817595083206088....
recoveredDict = loadDictFromNum(numbersDict)
print(recoveredDict)
# {'le key': 'le Valueue', '2': {'heya': 1234569, '3': 4}, 'Α α, Β β, Γ γ': 'שלום'}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接