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?
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
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.
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]
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
Si vous avez besoin de garantir l'unicité, il peut être plus efficace de
n
des nombres flottants aléatoires dans [lo, hi]
à la fois.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)
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)
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.
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)
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())
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]