Python碎碎念(2)——利用Python实现产生无重复随机数字

  • Post author:
  • Post category:python




利用Python实现产生无重复随机数字

今天在和朋友聊天时朋友提出了上面这个问题:如何利用Python产生一串无重复的随机数字,注意所产生的数字必须是

无重复



随机

的。

我们知道产生随机数,Python有一个模块是random,只需要import random然后调用其方法就可以了,但是如何做到无重复呢?下面是我们总结的几种可行的方法,其中最后一种方法的做法十分巧妙,建议读者思考和理解。



实现



random的sample函数

在random模块中,直接调用sample函数即可解决问题,该方法的参数如下

random.sample(population, k)

如上,该方法用于从population序列中随机抽取k个独立的元素,让我们来实践一下:

In [1]: import random

In [2]: random.sample(range(20),5)
Out[2]: [18, 3, 13, 14, 7]

In [3]: random.sample(range(20),5)
Out[3]: [14, 19, 16, 8, 15]

In [4]: random.sample(range(20),5)
Out[4]: [1, 19, 12, 4, 17]

如上结果所示,即可实现产生无重复的随机数字。



set去重

一讲到无重复,我们很容易就想到了set,因为set里的元素是不重复的,set最经常的一个应用就是利用其来进行序列的去重,如下例子:

In [5]: test_list = [1,1,2,2,2,3,4,4,4,4,5]

In [6]: set(test_list)
Out[6]: {1, 2, 3, 4, 5}

In [7]: len(set(test_list))
Out[7]: 5

利用该性质,我们既可以实现列表去重,也可以很方便地统计列表中独立元素的个数。

所以利用set去产生无重复随机数字时,我们可以利用set保存所产生的随机数字,每次产生一个随机数字,检查其是否在set中,如果不在的话再加入set,如果在的话则重新产生随机数字,看如下代码:

In [8]: number = 5

In [9]: result_set = set()

In [10]: while number > 0:
    ...:     pre_random_num = random.randint(0,19)
    ...:     # 检查随机数是否在结果集中
    ...:     if pre_random_num not in result_set:
    ...:         result_set.add(pre_random_num)
    ...:         number -= 1
    ...:

In [11]: result_set
Out[11]: {0, 7, 9, 12, 16}

如上,就可以得到我们想要的number个随机数。

但是此法有一个弊端就是:所需要的随机数个数逼近随机数的可选范围时,可想而知,每次我们产生的随机数重复的概率将大大增加,加大了时间开销,下面将介绍的方法可以比较完美地解决该问题。



利用列表抽取无重复随机数字

我们可以将要抽取的随机数范围先生成一个列表,再随机生成列表下标,pop出对应下标的元素加入结果集即可,具体实现如下:

In [12]: test_list = list(range(20))

In [13]: test_list
Out[13]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

In [14]: result_list = []

In [15]: number = 5

In [16]: list_len = len(test_list)-1

In [17]: while number > 0:
   ...:     random_index = random.randint(0, list_len)
   ...:     result_list.append(test_list[random_index])
   ...:     test_list.pop(random_index)
   ...:     number -= 1
   ...:     list_len -= 1
   ...:

In [18]: result_list
Out[18]: [8, 3, 2, 13, 11]

In [19]: test_list
Out[19]: [0, 1, 4, 5, 6, 7, 9, 10, 12, 14, 15, 16, 17, 18, 19]

如上,我们首先根据随机数的产生范围生成一个列表test_list,然后随机生成test_list的下标,并pop出对应下标的元素加入到结果集result_list中。

此处需要注意的是:每次pop出一个元素,列表的长度会减 1,所以每次循环我们需要减少随即下标的产生范围。

那么有没有一个方法,不需要pop出列表元素以及每次都要将列表长度减去 1 呢?当然是有的。

我们知道,在上面的方法中,在随机数的产生范围表test_list中,如果一个元素被加入到结果集中,该元素便不可能再次出现,我们的法是将其pop出并将下次下标产生的范围减少 1,这提示我们可以将两者结合起来。

因为每次下标产生范围会减少 1,及当前下标以后的下标是无法取到的,那么我们就可以无需pop出列表元素,而将pop的元素与最后一个下标的元素交换即可,这样由于下一次下标产生范围会减少 1,所以被置换到后面的元素是取不到的,利用这个思想我们就可以完成

去重

但是我们还没有解决下标产生范围减 1的问题,其实我们可以换一个思路:顺序遍历,然后在当前元素及之后的元素中挑选随机数,将其与当前元素做置换即可,具体实现如下:

In [20]: import random

In [21]: total = 20

In [22]: random_list = list(range(total))

In [23]: random_list
Out[23]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

In [24]: result = []

In [25]: number = 5

In [26]: for i in range(number):
    ...:     random_index = random.randint(i, total-1)
    ...:     result.append(random_list[random_index])
    ...:     # 交换元素到前面
    ...:     random_list[random_index], random_list[i] = random_list[i], ran
    ...: dom_list[random_index]
    ...:

In [27]: result
Out[27]: [7, 19, 10, 4, 8]

如上,我们顺序遍历random_list,然后在当前元素及之后的元素中挑选随机数,将其与当前元素做置换,重复此过程,直到产生足够的随机数即可。



列表取用元素小tips

在产生了随机数之后(假设以列表形式保存),我们如何取用呢?

通常我们的取用方法是顺序遍历过去,对于所有以列表形式保存的数据的取用方法都是如此,可以用Python的 for in 进行遍历,也可以用下标遍历,如下:

In [28]: for meb in result:
    ...:     print(meb)
    ...:
7
19
10
4
8

也可以用下标:

In [32]: index = 0

In [33]: result_len = len(result)

In [34]: while index < result_len:
    ...:     print(result[index])
    ...:     index += 1
    ...:
7
19
10
4
8

如上,如果我们用 for in 进行便利的话,其元素只能是顺序遍历而不能后退。

而如果我们用下标进行遍历,我们可以对下标 index 根据我们的需要进行控制,进而可以实现输出后面的元素。

而如果需要同时取得下标即下标对应的元素(上面所讲的下标遍历同时在输出下标也可以实现),并且不需要往后的话,我们还可以使用 enumerate 方法作用于列表返回其下标及对应的元素,如下:

In [35]: for index, element in enumerate(result):
    ...:     print(f'下标:{index} 元素:{element}')
    ...:
下标:0 元素:7
下标:1 元素:19
下标:2 元素:10
下标:3 元素:4
下标:4 元素:8

那么对于元素的取用,我们有没有其他写法?我们首先联想到的就是:迭代器。

迭代器也是用来保存元素的,而且其对元素的遍历只能往前,不能后退。可以使用 next() 函数进行遍历,也可以用迭代器的 __ next __() 方法取得下一个元素。

所以我们可以将列表包装成 iter 对象,然后用 next 去调用其中的元素,具体如下:

In [35]: result
Out[35]: [7, 19, 10, 4, 8]

In [36]: result_iter = iter(result)

In [37]: next(result_iter)
Out[37]: 7

In [38]: next(result_iter)
Out[38]: 19

也可以用 __ next __() 方法取得下一个元素:

In [39]: result_iter.__next__()
Out[39]: 10

In [40]: result_iter.__next__()
Out[40]: 4

In [41]: result_iter.__next__()
Out[41]: 8

但应用此方法时我们需要特别注意的是:当迭代器取到最后一个元素后,即迭代器已无 next 元素时间,再次取用下一个元素会抛出 StopIteration 异常,如下:

In [42]: result_iter.__next__()
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-44-f61ceafc8386> in <module>
----> 1 result_iter.__next__()

StopIteration:

如上即是列表取用元素的常用方法,而最后一个将列表包装成迭代器看似没有多大用处,但是在某些应用场合会有出其不意的效果。



总结

以上就是本篇的全部内容,其实利用Python实现产生无重复随机数字的方法除了上面所讲,一定还有其他的方法,读者可以自己去思考。

在上面所讲的实现方法中,我们要重点去理解

利用列表抽取无重复随机数字

的方法,其将抽取过的元素与当前元素进行置换的思想是十分巧妙的。

这提示我们合理分配空间,以达到空间的最大利用率,从而节省内存。



版权声明:本文为weixin_46302487原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。