繁体   English   中英

Python中的短唯一十六进制字符串

[英]Short Unique Hexadecimal String in Python

我需要在Python 3中生成一个满足以下要求的唯一十六进制字符串:

  1. 它应该包含6个字符
  2. 它不应仅包含数字。 必须至少有一个字符。
  3. 这些生成的字符串应该是随机的。 它们不应处于任何顺序。
  4. 应该有最小的冲突可能性

我考虑过uuid4()。 但是问题在于它生成的字符串包含太多字符,并且生成的字符串的任何子字符串在某个时候都可能包含所有数字(即无字符)。

还有其他方法可以满足此条件吗? 提前致谢!

编辑

我们可以使用哈希(例如SHA-1)来满足上述要求吗?

这是从所有允许的字符串中均匀采样的简单方法。 采样统一地使冲突尽可能少发生,缺少保持先前键的日志或使用基于计数器的哈希(请参见下文)。

import random
digits = '0123456789'
letters = 'abcdef'
all_chars = digits + letters
length = 6

while True:

   val = ''.join(random.choice(all_chars) for i in range(length))

   # The following line might be faster if you only want hex digits.
   # It makes a long int with 24 random bits, converts it to hex,
   # drops '0x' from the start and 'L' from the end, then pads
   # with zeros up to six places if needed
   # val = hex(random.getrandbits(4*length))[2:-1].zfill(length)

   # test whether it contains at least one letter
   if not val.isdigit():
       break

# now val is a suitable string
print val
# 5d1d81

另外,这是一种较为复杂的方法,该方法也可以统一采样,但不使用任何开放式循环:

import random, bisect
digits = '0123456789'
letters = 'abcdef'
all_chars = digits + letters
length = 6

# find how many valid strings there are with their first letter in position i
pos_weights = [10**i * 6 * 16**(length-1-i) for i in range(length)]
pos_c_weights = [sum(pos_weights[0:i+1]) for i in range(length)]

# choose a random slot among all the allowed strings
r = random.randint(0, pos_c_weights[-1])

# find the position for the first letter in the string
first_letter = bisect.bisect_left(pos_c_weights, r)

# generate a random string matching this pattern
val = ''.join(
    [random.choice(digits) for i in range(first_letter)]
    + [random.choice(letters)]
    + [random.choice(all_chars) for i in range(first_letter + 1, length)]
)

# now val is a suitable string
print val
# 4a99f0

最后,这是一个更复杂的方法,该方法使用随机数r直接索引允许值的整个范围,即,它将0-15,777,216范围内的任何数字转换为合适的十六进制字符串。 这可以用来完全避免冲突(下面将详细讨论)。

import random, bisect
digits = '0123456789'
letters = 'abcdef'
all_chars = digits + letters
length = 6

# find how many valid strings there are with their first letter in position i
pos_weights = [10**i * 6 * 16**(length-1-i) for i in range(length)]
pos_c_weights = [sum(pos_weights[0:i+1]) for i in range(length + 1)]

# choose a random slot among all the allowed strings
r = random.randint(0, pos_c_weights[-1])

# find the position for the first letter in the string
first_letter = bisect.bisect_left(pos_c_weights, r) - 1

# choose the corresponding string from among all that fit this pattern
offset = r - pos_c_weights[first_letter]
val = ''
# convert the offset to a collection of indexes within the allowed strings 
# the space of allowed strings has dimensions
# 10 x 10 x ... (for digits) x 6 (for first letter) x 16 x 16 x ... (for later chars)
# so we can index across it by dividing into appropriate-sized slices
for i in range(length):
    if i < first_letter:
        offset, v = divmod(offset, 10)
        val += digits[v]
    elif i == first_letter:
        offset, v = divmod(offset, 6)
        val += letters[v]
    else:
        offset, v = divmod(offset, 16)
        val += all_chars[v]

# now val is a suitable string
print val
# eb3493

均匀采样

我在上面提到过,此示例在所有允许的字符串上均匀采样。 这里的其他一些答案完全随机选择5个字符,然后在随机位置将一个字母强制插入字符串。 这种方法产生的带有多个字母的字符串比随机产生的字符串更多。 例如,如果为前5个插槽选择了字母,则该方法始终会产生6个字母的字符串; 但是,在这种情况下,第六选择实际上应该只有6/16的机会成为字母。 仅当前5个槽位是数字时,才可以通过将字母强制插入第6个槽位来解决这些问题。 在这种情况下,所有5位数字字符串将自动转换为5位数字加1个字母,从而产生太多5位数字字符串。 如果前5个字符是数字,则采用统一采样时,应该有10/16的机会完全拒绝该字符串。

以下是一些说明这些采样问题的示例。 假设您有一个更简单的问题:您想要一个由两个二进制数字组成的字符串,并且规则中至少有一个必须为1。如果您以相等的概率产生01、10或11,则冲突是最罕见的。 您可以通过为每个插槽选择随机位,然后扔掉00(类似于我上面的方法)来做到这一点。

但是,假设您改为遵循此规则:做出两个随机的二进制选择。 首选将在字符串中原样使用。 第二个选择将确定要插入其他位置1的位置。 这类似于此处其他答案使用的方法。 然后,您将获得以下可能的结果,其中前两列代表两个二进制选择:

0 0 -> 10
0 1 -> 01
1 0 -> 11
1 1 -> 11

这种方法产生11的概率为0.5,或者01或10的概率为0.25,因此它将增加11个结果之间发生冲突的风险。

您可以尝试如下进行改进:进行三个随机二进制选择。 首选将在字符串中原样使用。 如果第一个选择为0,则第二个选择将转换为1;否则,第二个选择将转换为1。 否则,它将原样添加到字符串中。 第三选择将确定第二选择将插入的位置。 然后,您将获得以下可能的结果:

0 0 0 -> 10 (second choice converted to 1)
0 0 1 -> 01 (second choice converted to 1)
0 1 0 -> 10
0 1 1 -> 01
1 0 0 -> 10
1 0 1 -> 01
1 1 0 -> 11
1 1 1 -> 11

这给01或10带来0.375的机会,给11带来0.25的机会。因此,这将稍微增加重复的10或01值之间发生冲突的风险。

减少冲突

如果您愿意使用所有字母,而不只是使用“ a”至“ f”(十六进制数字),则可以按照注释中的说明更改letters的定义。 这将提供更多不同的字符串,并且冲突的机会也要少得多。 如果生成了1,000个允许所有大写和小写字母的字符串,则只有0.0009%的机会生成任何重复字母,而只有十六进制的字符串有3%的机会。 (这实际上还将消除循环中的两次通过。)

如果您确实想避免字符串之间的冲突,则可以将先前生成的所有值存储在一个set并在退出循环之前对其进行检查。 如果您要生成的密钥少于500万个,那将是很好的选择。 除此之外,您还需要大量RAM来保存旧密钥,并且可能需要花费大量时间遍历循环才能找到未使用的密钥。

如果您需要生成更多的密钥,则可以加密计数器,如在Python生成非重复随机数所述 计数器及其加密版本的整数都在0到15,777,216之间。 计数器仅从0开始计数,加密版本看起来像一个随机数。 然后,您可以使用上面的第三个代码示例将加密版本转换为十六进制。 如果这样做,则应在开始时生成一个随机加密密钥,并在每次计数器超过最大值时都更改该加密密钥,以避免再次产生相同的序列。

注意:更新了十六进制唯一字符串的答案。 之前我假设使用字母数字字符串。

您可以使用uuidrandom库创建自己的独特函数

>>> import uuid
>>> import random
# Step 1: Slice uuid with 5 i.e. new_id = str(uuid.uuid4())[:5] 
# Step 2: Convert string to list of char i.e. new_id = list(new_id)
>>> uniqueval = list(str(uuid.uuid4())[:5])
# uniqueval = ['f', '4', '4', '4', '5']

# Step 3: Generate random number between 0-4 to insert new char i.e.
#         random.randint(0, 4)
# Step 4: Get random char between a-f (for Hexadecimal char) i.e.
#         chr(random.randint(ord('a'), ord('f')))
# Step 5: Insert random char to random index
>>> uniqueval.insert(random.randint(0, 4), chr(random.randint(ord('a'), ord('f'))))
# uniqueval = ['f', '4', '4', '4', 'f', '5']

# Step 6: Join the list
>>> uniqueval = ''.join(uniqueval)
# uniqueval = 'f444f5'

以下方法的工作原理如下:首先选择一个随机字母以确保规则2,然后从所有可用字符列表中选择4个随机条目。 随机排列结果列表。 最后,从所有条目的列表中添加一个值( 0以确保该字符串包含6个字符。

import random

all = "0123456789abcdef"
result = [random.choice('abcdef')] + [random.choice(all) for _ in range(4)]
random.shuffle(result)
result.insert(0, random.choice(all[1:]))
print(''.join(result))

给你类似的东西:

3b7a4e

这种方法避免了必须重复检查结果以确保其满足规则。

此函数返回符合您要求的第n个字符串,因此您可以简单地生成唯一的整数并使用此函数进行转换。

def inttohex(number, digits):
    # there must be at least one character:
    fullhex = 16**(digits - 1)*6
    assert number < fullhex
    partialnumber, remainder = divmod(number, digits*6)
    charposition, charindex = divmod(remainder, digits)
    char = ['a', 'b', 'c', 'd', 'e', 'f'][charposition]
    hexconversion = list("{0:0{1}x}".format(partialnumber, digits-1))
    hexconversion.insert(charposition, char)

    return ''.join(hexconversion)

现在您可以使用来获得特定的

import random

digits = 6
inttohex(random.randint(0, 6*16**(digits-1)), digits)

您不能同时具有最大的随机性和最小的冲突概率。 我建议使用随机排序的列表来跟踪您分发了哪些数字,或者是否正在以某种方式遍历所有数字。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM