mardi 20 avril 2021

random.sample giving inconsistent results after providing a consistent random.seed

I'm building an elaborate Enigma machine, and when creating my plugs, I want to be get a random sample of even length from the characters available to my machine's rotors. Easy enough. The issue here is that I'm using seed(rng_seed) just before used_keys = set(sample(free_keys, 2 * plug_count)), which I would expect to give the same set every time. It does not. I do not understand why I'm seeing this behavior.

Here are the relevant bits:

from random import random, sample, seed
from typing import Set, Tuple
from enigma.rotor import StandardRotor
# from enigma.plug import  Plug


class EnigmaMachine:
    STEP = 1

    def __init__(self, rotor_count: int, plug_count: int, rng_seed: int, step: int = None):
        if rotor_count < 1:
            raise ValueError('rotor_count expected be an integer '
                             'greater than zero, got {}'.format(rotor_count))

        # Generate rotors
        seed(rng_seed)
        seeds = tuple(random() for _ in range(rotor_count))
        self.__rotors = tuple(StandardRotor(rng_seed=seeds[i]) for i in range(rotor_count))

        # Generate plugs
        free_keys = self.keys
        if plug_count > 2 * len(free_keys):
            raise ValueError('Maximum plugs allowed is {}, got {}'.format(len(free_keys) // 2,
                                                                          plug_count))
        seed(rng_seed)
        used_keys = set(sample(free_keys, 2 * plug_count))
        # TODO: why won't seed affect sample?
        free_keys -= used_keys
        print(free_keys)
        print(used_keys)

        self.__step = step or EnigmaMachine.STEP

    @property
    def keys(self) -> Set[str]:
        return set(self.__rotors[0].cipher)

Inside StandardRotor.__init__, I'm also using seed, which you can see is potentially called many times.

In summary, here's all the places I'm using anything from random:

  • seed before creating rotors
  • random() when creating a tuple of length rotor_count
  • seed inside each StandardRotor.__init__
  • seed before creating used keys
  • sample when creating a subset of free_keys

After I get this working, I expect to use random.sample to pull pairs out of used_keys and create Plugs from them. I haven't shown them here, but they're very basic and simply take two different characters as inputs.

FYI, I've also tried refactoring this to import Random, create an instance of random inside init with r = Random(), then prefixing all my random commands with r., such as r.seed and r.sample. This made no difference.




Aucun commentaire:

Enregistrer un commentaire