web-dev-qa-db-fra.com

Comment générer une liste de flottants aléatoires uniques en Python

Je sais qu'il existe des moyens simples de générer des listes d'entiers aléatoires uniques (par exemple, random.sample(range(1, 100), 10)).

Je me demande s'il existe un meilleur moyen de générer une liste de flottants aléatoires uniques, en dehors de l'écriture d'une fonction qui agit comme une plage, mais accepte les flottants comme ceci:

import random

def float_range(start, stop, step):
    vals = []
    i = 0
    current_val = start
    while current_val < stop:
        vals.append(current_val)
        i += 1
        current_val = start + i * step
    return vals

unique_floats = random.sample(float_range(0, 2, 0.2), 3)

Y a-t-il une meilleure manière de faire cela?

12
Simon Merkelbach

Réponse

Un moyen simple est de conserver un ensemble de toutes les valeurs aléatoires vues jusqu'à présent et de sélectionner à nouveau s'il y a une répétition:

import random

def sample_floats(low, high, k=1):
    """ Return a k-length list of unique random floats
        in the range of low <= x <= high
    """
    result = []
    seen = set()
    for i in range(k):
        x = random.uniform(low, high)
        while x in seen:
            x = random.uniform(low, high)
        seen.add(x)
        result.append(x)
    return result

Remarques

  • Cette technique explique comment le propre random.sample () de Python est implémenté.

  • La fonction utilise un ensemble pour suivre les sélections précédentes, car la recherche d'un ensemble est O(1) alors que la recherche dans une liste est O (n).

  • Calculer la probabilité d'une sélection en double équivaut au fameux problème d'anniversaire

  • Sur 2 ** 53 valeurs possibles distinctes de random (), les doublons sont peu fréquents. En moyenne, vous pouvez vous attendre à un nombre flottant dupliqué d'environ 120 000 000 échantillons.

Variante: gamme de flotteurs limitée

Si la population est limitée à une plage de flottants également espacés, il est alors possible d’utiliser directement random.sample () . La seule exigence est que la population soit une séquence :

from __future__ import division
from collections import Sequence

class FRange(Sequence):
    """ Lazily evaluated floating point range of evenly spaced floats
        (inclusive at both ends)

        >>> list(FRange(low=10, high=20, num_points=5))
        [10.0, 12.5, 15.0, 17.5, 20.0]

    """
    def __init__(self, low, high, num_points):
        self.low = low
        self.high = high
        self.num_points = num_points

    def __len__(self):
        return self.num_points

    def __getitem__(self, index):
        if index < 0:
            index += len(self)
        if index < 0 or index >= len(self):
            raise IndexError('Out of range')
        p = index / (self.num_points - 1)
        return self.low * (1.0 - p) + self.high * p

Voici un exemple de choix de dix échantillons aléatoires sans remplacement dans une plage de 41 flotteurs régulièrement espacés de 10,0 à 20,0.

>>> import random
>>> random.sample(FRange(low=10.0, high=20.0, num_points=41), k=10)
[13.25, 12.0, 15.25, 18.5, 19.75, 12.25, 15.75, 18.75, 13.0, 17.75]
16
Raymond Hettinger

Vous pouvez facilement utiliser votre liste d’entiers pour générer des flottants:

int_list = random.sample(range(1, 100), 10)
float_list = [x/10 for x in int_list]

Consultez cette question de débordement de pile à propos de la génération de flottants aléatoires.

Si vous voulez que cela fonctionne avec python2, ajoutez cette importation:

from __future__ import division
5
Or Duan

Si vous avez besoin de garantir l'unicité, il peut être plus efficace de

  1. Essayez de générer n des nombres flottants aléatoires dans [lo, hi] à la fois.
  2. Si la longueur des flotteurs uniques n’est pas n, essayez de générer le nombre de flotteurs nécessaire, 

et continuez en conséquence jusqu'à ce que vous en ayez assez, au lieu de les générer 1 par 1 dans une vérification de boucle de niveau Python par rapport à un ensemble.

Si vous pouvez vous permettre NumPy cela avec np.random.uniform peut être une énorme accélération.

import numpy as np

def gen_uniq_floats(lo, hi, n):
    out = np.empty(n)
    needed = n
    while needed != 0:
        arr = np.random.uniform(lo, hi, needed)
        uniqs = np.setdiff1d(np.unique(arr), out[:n-needed])
        out[n-needed: n-needed+uniqs.size] = uniqs
        needed -= uniqs.size
    np.random.shuffle(out)
    return out.tolist()

Si vous ne pouvez pas utiliser NumPy , il sera peut-être encore plus efficace, car vos données doivent appliquer le même concept de vérification des dupes par la suite, en maintenant un ensemble.

def no_depend_gen_uniq_floats(lo, hi, n):
    seen = set()
    needed = n
    while needed != 0:
        uniqs = {random.uniform(lo, hi) for _ in range(needed)}
        seen.update(uniqs)
        needed -= len(uniqs)
    return list(seen)

Repère approximatif

Cas extrême dégénéré  

# Mitch's NumPy solution
%timeit gen_uniq_floats(0, 2**-50, 1000)
153 µs ± 3.71 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

# Mitch's Python-only solution
%timeit no_depend_gen_uniq_floats(0, 2**-50, 1000)
495 µs ± 43.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

# Raymond Hettinger's solution (single number generation)
%timeit sample_floats(0, 2**-50, 1000)
618 µs ± 13 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Plus cas "normal" (avec un échantillon plus grand)

# Mitch's NumPy solution
%timeit gen_uniq_floats(0, 1, 10**5)
15.6 ms ± 1.12 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)

# Mitch's Python-only solution
%timeit no_depend_gen_uniq_floats(0, 1, 10**5)
65.7 ms ± 2.31 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

# Raymond Hettinger's solution (single number generation)
%timeit sample_floats(0, 1, 10**5)
78.8 ms ± 4.22 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
5
miradulo

Vous pouvez simplement utiliser random.uniform(start, stop). Avec les flotteurs double précision, vous pouvez être relativement sûr qu'ils sont uniques si votre jeu est petit. Si vous souhaitez générer un grand nombre de flottants aléatoires et éviter que vous ayez un nombre deux fois, vérifiez avant de les ajouter à la liste.

Toutefois, si vous recherchez une sélection de numéros spécifiques, ce n'est pas la solution.

4
C. Nitschke
min_val=-5
max_val=15

numpy.random.random_sample(15)*(max_val-min_val) + min_val

ou utiliser un uniforme

numpy.random.uniform(min_val,max_val,size=15)
1
Joran Beasley

Comme indiqué dans la documentation, Python a la fonction random.random ():

import random
random.random()

Ensuite, vous obtiendrez une valeur float telle que: 0.672807098390448

Il vous suffit donc de créer une boucle for et d’afficher random.random ():

>>> for i in range(10):
print(random.random())
1
aviad

more_itertools a un générique numeric_range qui gère les entiers et les flottants.

import random

import more_itertools as mit

random.sample(list(mit.numeric_range(0, 2, 0.2)), 3)
# [0.8, 1.0, 0.4]

random.sample(list(mit.numeric_range(10.0, 20.0, 0.25)), 10)
# [17.25, 12.0, 19.75, 14.25, 15.25, 12.75, 14.5, 15.75, 13.5, 18.25]
0
pylang