Python shuffle():种子数/ shuffle()的粒度结果多样性

时间:2018-01-20 16:33:13

标签: python random

我的任务是我想指定一个种子来洗牌项目列表,以便我可以使用下次输入的相同种子重新创建混洗结果。但是,我尝试了一些" 关闭但不同的数字" (回想起来一个毫无意义的陈述)作为种子,它们都产生了相同的结果。

这是我编写的一段代码,试图用Python来检查种子数的行为random.shuffle()

from random import shuffle

seed_list = [   0.0,    0.05,   0.1,    0.15,   0.2,    0.25,   0.3,    0.35,
                0.4,    0.45,   0.5,    0.55,   0.6,    0.65,   0.7,    0.75,
                0.8,    0.85,   0.9,    0.95
]

last_list = list(range(0, 10))

for seed in seed_list:
    num_list = list(range(0, 10))
    shuffle(num_list, lambda:seed)
    print("Seed", str(seed)+":\t", num_list, num_list==last_list)
    last_list = num_list

输出看起来像这样:

:~$ python3 test_shuffle.py 
Seed 0.0:    [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] False
Seed 0.05:   [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] True
Seed 0.1:    [9, 2, 3, 4, 5, 6, 7, 8, 0, 1] False
Seed 0.15:   [6, 2, 3, 4, 5, 0, 7, 8, 9, 1] False
Seed 0.2:    [4, 9, 3, 0, 5, 6, 7, 8, 1, 2] False
Seed 0.25:   [3, 7, 0, 4, 5, 6, 1, 8, 9, 2] False
Seed 0.3:    [9, 6, 0, 4, 5, 1, 7, 8, 2, 3] False
Seed 0.35:   [5, 0, 8, 4, 1, 6, 7, 2, 9, 3] False
Seed 0.4:    [9, 0, 7, 1, 5, 6, 2, 8, 3, 4] False
Seed 0.45:   [8, 0, 6, 1, 5, 2, 7, 3, 9, 4] False
Seed 0.5:    [0, 9, 1, 7, 2, 6, 3, 8, 4, 5] False
Seed 0.55:   [0, 9, 1, 7, 2, 6, 3, 8, 4, 5] True
Seed 0.6:    [0, 9, 1, 2, 8, 3, 7, 4, 5, 6] False
Seed 0.65:   [0, 9, 1, 2, 7, 3, 4, 8, 5, 6] False
Seed 0.7:    [0, 1, 9, 2, 3, 8, 4, 5, 6, 7] False
Seed 0.75:   [0, 1, 2, 9, 3, 4, 5, 8, 6, 7] False
Seed 0.8:    [0, 1, 2, 3, 9, 4, 5, 6, 7, 8] False
Seed 0.85:   [0, 1, 2, 3, 4, 9, 5, 6, 7, 8] False
Seed 0.9:    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] False
Seed 0.95:   [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] True

根据打印输出和我在间隔之间尝试的一些细粒种子,触发"将第一个元素移动到最后一个" 的种子间隔至少为0.05-0.0 = 0.05(与#34相同;什么都不做,#34; [0.9,0.95])。

我觉得这个行为有两个方面很麻烦:
1)总间隔的二十分之一是一个很大的比例。其余的shuffle行为如何在其余的间隔中公平分配? (不是"#34;随机性"或" shuffle"传达"任何顺序都可能?"如何在大间隔时填写其余的随机性被某些行为所占据。)
2)"从头到尾转移" &安培; "什么都不做"对于改组函数来说似乎是非常糟糕/无用的行为。我的实施有问题吗?

编辑:我故意挑选种子,以便我可以在我的任务中重现结果。

更多信息:我的印象是我使用的是post种子。由于random.random()在[0.,1。]中产生一些浮动,我假设我可以自己挑选一个,并通过在不同的相同距离间隔中选择它们来进一步测试不同种子的行为。

请告诉我是否做了错误的假设,逻辑错误或编码错误。谢谢。

3 个答案:

答案 0 :(得分:5)

您传入的函数返回固定数字

shuffle(num_list, lambda: seed)

此处seed是您的浮点值之一。这与默认的random()功能非常不同;你将永远重复返回相同的数字。来自文档:

  

可选参数 random 是一个0参数函数[0.0, 1.0) 中返回一个随机浮点数;默认情况下,这是函数random()

您在此处制作了Dilbert会计部门随机数生成器:

enter image description here

当您传入另一个random()函数作为第二个参数时,它返回的值用于选择前面的索引以交换“当前”#39; index with(从结尾开始); source code that is run基本上是这样做的:

x = list_to_shuffle
for i in reversed(range(1, len(x))):
    # pick an element in x[:i+1] with which to exchange x[i]
    j = int(random() * (i+1))
    x[i], x[j] = x[j], x[i]

因此,您的固定号码将始终选择相同的相对索引以与交换。对于该固定值的足够小的差异,向下舍入到最接近的整数将导致用于交换的完全相同的索引。

例如,0.50.55会发生这种情况;在这两种情况下,所选择的指数是(5, 4, 4, 3, 3, 2, 2, 1, 1),而不是随机的'洗牌。当您使用索引00.05以及0交换所有内容时,0.90.95同上,当您将每个索引与自身交换时。

如果您想测试种子的工作方式,请使用种子创建random.Random()类的实例,并在该对象上调用shuffle()

from random import Random

seed_list = [   0.0,    0.05,   0.1,    0.15,   0.2,    0.25,   0.3,    0.35,
                0.4,    0.45,   0.5,    0.55,   0.6,    0.65,   0.7,    0.75,
                0.8,    0.85,   0.9,    0.95
]

last_list = ten_digits = list(range(10))

for seed in seed_list:
    num_list = ten_digits[:]
    Random(seed).shuffle(num_list)
    print("Seed {}:\t {} {}".format(seed, num_list, num_list==last_list))
    last_list = num_list

输出

Seed 0.0:    [7, 8, 1, 5, 3, 4, 2, 0, 9, 6] False
Seed 0.05:   [3, 8, 5, 4, 2, 1, 9, 7, 0, 6] False
Seed 0.1:    [0, 4, 8, 7, 1, 9, 5, 6, 2, 3] False
Seed 0.15:   [6, 1, 8, 7, 9, 5, 2, 4, 3, 0] False
Seed 0.2:    [9, 6, 8, 2, 7, 4, 5, 0, 1, 3] False
Seed 0.25:   [2, 8, 0, 3, 1, 6, 5, 9, 7, 4] False
Seed 0.3:    [7, 4, 5, 1, 2, 3, 8, 9, 6, 0] False
Seed 0.35:   [0, 7, 6, 2, 8, 3, 9, 5, 1, 4] False
Seed 0.4:    [3, 5, 7, 1, 9, 4, 6, 0, 8, 2] False
Seed 0.45:   [4, 3, 6, 8, 1, 7, 5, 2, 9, 0] False
Seed 0.5:    [8, 9, 3, 5, 0, 6, 1, 2, 7, 4] False
Seed 0.55:   [3, 0, 4, 6, 2, 8, 7, 1, 9, 5] False
Seed 0.6:    [3, 4, 7, 2, 9, 1, 6, 5, 8, 0] False
Seed 0.65:   [9, 1, 8, 2, 4, 0, 7, 3, 6, 5] False
Seed 0.7:    [1, 6, 2, 4, 8, 5, 7, 9, 3, 0] False
Seed 0.75:   [8, 3, 6, 1, 9, 0, 4, 5, 7, 2] False
Seed 0.8:    [4, 7, 5, 2, 0, 3, 8, 1, 9, 6] False
Seed 0.85:   [2, 4, 6, 5, 7, 8, 0, 3, 9, 1] False
Seed 0.9:    [3, 6, 5, 0, 8, 9, 1, 4, 7, 2] False
Seed 0.95:   [1, 5, 2, 6, 4, 9, 3, 8, 0, 7] False

或者您可以只调用random.seed()每个测试,传入seed值,但这会更改影响使用它的其他模块的全局Random()实例。

random.seed()的第二个论点应该被遗忘,你永远不需要它。它只是在first revision of the function中作为性能改进,以确保在紧密循环中使用本地名称而不是全局名称。但是因为它被添加到没有前导下划线的函数签名中,所以它永远成为公共API的一部分,偶然。没有真正的用例需要使用它。

答案 1 :(得分:2)

您可以查看它的作用:

def shuffle(self, x, random=None):
    """Shuffle list x in place, and return None.

    Optional argument random is a 0-argument function returning a
    random float in [0.0, 1.0); if it is the default None, the
    standard random.random will be used.

    """

    if random is None:
        randbelow = self._randbelow
        for i in reversed(range(1, len(x))):
            # pick an element in x[:i+1] with which to exchange x[i]
            j = randbelow(i+1)
            x[i], x[j] = x[j], x[i]
    else:
        _int = int
        for i in reversed(range(1, len(x))):
            # pick an element in x[:i+1] with which to exchange x[i]
            j = _int(random() * (i+1))
            x[i], x[j] = x[j], x[i]

else分支适用于此处,特别是j = _int(random() * (i+1))行。这是经典Fisher-Yates shuffle
因此,随机值的“使用粒度”一般取决于列表的长度,特别是当前元素的索引。

哦,顺便说一下:你没有提供种子,你提供的是随机值 - 最终成为每次迭代的单个值。登记/> 你可以做的是使用随机值进行单次洗牌:

from random import shuffle

random_list = [ 0.0,    0.05,   0.1,    0.15,   0.2,    0.25,   0.3,    0.35,
                0.4,    0.45,   0.5,    0.55,   0.6,    0.65,   0.7,    0.75,
                0.8,    0.85,   0.9,    0.95
]

num_list = list(range(0, 10))
shuffle(num_list, lambda:random_list.pop())
print(num_list)

或者,您可以使用seed-s,但实际上并不期望它们是0到1之间的浮点数,请参阅

def seed(self, a=None, version=2):
    """Initialize internal state from hashable object.

    None or no argument seeds from current time or from an operating
    system specific randomness source if available.

    If *a* is an int, all bits are used.

    For version 2 (the default), all of the bits are used if *a* is a str,
    bytes, or bytearray.  For version 1 (provided for reproducing random
    sequences from older versions of Python), the algorithm for str and
    bytes generates a narrower range of seeds.

    """

    if version == 1 and isinstance(a, (str, bytes)):
        x = ord(a[0]) << 7 if a else 0
        for c in a:
            x = ((1000003 * x) ^ ord(c)) & 0xFFFFFFFFFFFFFFFF
        x ^= len(a)
        a = -2 if x == -1 else x

    if version == 2 and isinstance(a, (str, bytes, bytearray)):
        if isinstance(a, str):
            a = a.encode()
        a += _sha512(a).digest()
        a = int.from_bytes(a, 'big')

    super().seed(a)
    self.gauss_next = None

和父类(其中a结束,有或没有这些修改过程)是本机代码:

NUMBA_EXPORT_FUNC(PyObject *)
_numba_rnd_seed(PyObject *self, PyObject *args)
{
    unsigned int seed;
    rnd_state_t *state;

    if (!PyArg_ParseTuple(args, "O&I:rnd_seed",
                          rnd_state_converter, &state, &seed)) {
        /* rnd_seed_*(bytes-like object) */
        Py_buffer buf;

        PyErr_Clear();
        if (!PyArg_ParseTuple(args, "O&s*:rnd_seed",
                              rnd_state_converter, &state, &buf))
            return NULL;

        if (rnd_seed_with_bytes(state, &buf))
            return NULL;
        else
            Py_RETURN_NONE;
    }
    else {
        /* rnd_seed_*(int32) */
        numba_rnd_init(state, seed);
        Py_RETURN_NONE;
    }
}

甚至可能发生传递浮点值导致在这里运行最终分支,这里使用未初始化的seed值,导致一致的行为仅仅因为函数调用在此之前调用_numba_rnd_seed使堆栈处于相同状态。

答案 2 :(得分:2)

种子应该初始化随机数生成器。您可以使用 随机数生成器。

您正在做:

shuffle(num_list, lambda: seed)

正确的方法是:

shuffle(num_list, Random(seed).random)

更好的方法是:

Random(seed).shuffle(num_list)