根据长度从列表中选择随机元素的有效方法

时间:2019-05-05 14:39:27

标签: python

很抱歉,如果这是错误的论坛-这是我的第一个问题。我正在学习python,并通过www.practicepython.org

编写了一个密码生成器作为练习

我写了以下内容,但是它可能真的很慢,所以我想我做的效率不高。我想从字典中选择一个随机单词,然后在其中添加ascii字符。我想要至少2个ascii字符输入密码,因此我使用while循环来确保单词元素包含(length-2)。

如果您说希望密码长度为10个字符,则此方法很好,但是如果您限制为5个字符,我认为while循环必须经过如此多次的迭代,这可能需要30秒。

>

我无法通过搜索找到答案-感谢指导!

import string
import random
import nltk
from nltk.corpus import words

word = words.words()[random.randint(1, len(words.words()))]
ascii_str = (string.ascii_letters + string.digits + string.punctuation)

length = int(input("How long do you want the password to be? "))

while len(word) >= (length - 2):
    word = words.words()[random.randint(1, len(words.words()))]

print("The password is: " + word, end="")

for i in range(0, (length - len(word))):
    print(ascii_str[random.randint(1, len(ascii_str) - 1)], end="")

2 个答案:

答案 0 :(得分:2)

只需一次调用words.words()并将其存储在变量中:

allwords = words.words()

这可以节省很多工作,因为现在nltk.corpus库不会在每次尝试获取列表长度或尝试选择带有索引的随机单词时尝试加载整个列表生成。

接下来,使用random.choice()从该列表中选择一个随机元素。这样就无需继续传递列表长度:

word = random.choice(allwords)

# ...

while len(word) >= (length - 2):
    word = random.choice(allwords)

接下来,您可以先按长度将单词分组:

allwords = words.words()
by_length = {}
for word in allwords:
    by_length.setdefault(len(word), []).append(word)

这为您提供了一个字典,其中的键代表单词的长度; nltk语料库的单词长度在1到24个字母之间。字典中的每个值都是一个长度相同的单词列表,因此by_length[12]将为您提供一列长度恰好为12个字符的单词列表。

这使您可以选择特定长度的单词:

# start with the desired length, and see if there are words this long in the
# dictionary, but don’t presume that all possible lengths exist:
wordlength = length - 2
while wordlength > 0 and wordlength not in by_length:
    wordlength -= 1

# we picked a length, but it could be 0, -1 or -2, so start with an empty word
# and then pick a random word from the list with words of the right length.
word = ''
if wordlength > 0:
    word = random.choice(by_length[wordlength])

现在word是符合您条件的最长随机词:比所需长度至少短2个字符,并且是从单词列表中随机抽取的。

更重要的是:我们只选择了一个随机词一次。只要您将by_length词典保留更长时间并在密码生成功能中重新使用它,那将是一个大胜利。

如果您使用bisection,则可以一次从by_length中选择最接近的可用长度,而无需一次一步一步地遍历所有可能的长度,但是我将其留给读者练习。

答案 1 :(得分:1)

您正在查看random.choice
从文档中:

  

random.choice(seq)
  从非空序列seq返回一个随机元素。

In [22]: import random                                                                                                                                

In [23]: random.choice([1,2,3,4,5])                                                                                                                   
Out[23]: 3

In [24]: random.choice([1,2,3,4,5])                                                                                                                   
Out[24]: 5

In [25]: random.choice([1,2,3,4,5])                                                                                                                   
Out[25]: 1

然后可以将代码简化为

import string
import random
import nltk
from nltk.corpus import words

#All words assigned to a list first
words = words.words()

#Get a random word
word = random.choice(words)
ascii_str = string.ascii_letters + string.digits + string.punctuation

length = int(input("How long do you want the password to be? "))

while len(word) >= (length - 2):
    word = random.choice(words)

#Use random.sample to choose n random samples, and join them all to make a string
password = word + ''.join(random.sample(ascii_str, length))
print("The password is: " + password, end="")

可能的输出是

How long do you want the password to be? 10
The password is: heyT{7<XEVc!l
How long do you want the password to be? 8
The password is: hiBk-^8t7]

但是,当然,这不是@MartjinPieters在评论中指出的优化解决方案,但我将尝试以他在answer中指出的方式,以不同的方式提供一些东西,如下所示

  • 我将使用itertools.groupby创建by_length字典,这是一个使用itertools.groupby

  • 的字典,其键为单词长度,值作为该长度的单词列表
  • 我将确保密码长度的最小长度限制

  • 使用random.sample选择pass_len随机样本,并将它们全部连接成一个字符串,并将单词附加在前面!
import string
import random
from itertools import groupby

#All words assigned to a list first
words = ['a', 'c', 'e', 'bc', 'def', 'ghij' , 'jklmn']

#Get a random word
word = random.choice(words)
ascii_str = string.ascii_letters + string.digits + string.punctuation

#Check for minimum length, and exit the code if it is not
min_length = 8
pass_len = int(input("How long do you want the password to be? Minimum length is {}".format(min_length)))

if pass_len <= min_length:
    print('Password is not long enough')
    exit()

#Create the by_length dictionary, a dictionary with key as word length and values as list of words of that length using itertools.groupby
by_length = {}
for model, group in groupby(words, key=len):
    by_length[model] = list(group)

chosen_word = ''
req_len = pass_length - 2

#Iterate till you find the word of required length of pass_len - 2, else reduce the required length by 1
while req_len > 0:
    if req_len in words:
        chosen_word = by_length[req_len]
    else:
        req_len -= 1

#Use random.sample to choose n random samples, and join them all to make a string
password = word + ''.join(random.sample(ascii_str, length))
print("The password is: " + password, end="")