我需要在Python 3中生成一个符合以下要求的唯一十六进制字符串:
我考虑过uuid4()。但问题是它生成的字符串太多,生成的字符串的任何子字符串都可以包含所有数字(即无字符)。
还有其他方法可以满足这个条件吗?提前谢谢!
修改
我们可以使用散列例如SHA-1来满足上述要求吗?
答案 0 :(得分:4)
这是一个简单的方法,可以从所有允许的字符串中均匀采样。采样统一使冲突尽可能少,不能记录以前的密钥或使用基于计数器的哈希(见下文)。
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
这种方法有0.5的机会产生11或0.25的01或10,所以它会增加11个结果之间碰撞的风险。
您可以尝试按如下方式改进:制作三个随机二进制选项。第一个选项将在字符串中按原样使用。如果第一个选择为0,则第二个选择将转换为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来保存旧密钥,并且可能需要在循环中运行几次才能找到未使用的密钥。
如果您需要生成更多密钥,可以加密计数器,如Generating non-repeating random numbers in Python所述。计数器及其加密版本的整数均为0到15,777,216。计数器将从0开始计数,加密版本看起来像一个随机数。然后,您将使用上面的第三个代码示例将加密版本转换为十六进制。如果这样做,您应该在开始时生成一个随机加密密钥,并在每次计数器超过最大值时更改加密密钥,以避免再次生成相同的序列。
答案 1 :(得分:1)
注意:更新了十六进制唯一字符串的答案。之前我假设用于字母数字字符串。
您可以使用uuid
和random
库
>>> 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 :(得分:1)
以下方法的工作原理如下,首先选择一个随机字母以确保规则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
这种方法避免了必须反复检查结果以确保它满足规则。
答案 3 :(得分:0)
此函数返回符合您要求的第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)
您无法获得最大的随机性以及最小的冲突概率。我建议您使用随机排序的列表跟踪您分发的数字或者以某种方式循环遍历所有数字。