Question

J'ai un trousseau de clés qui ont chacun une variable invraisemblance. Je veux choisir au hasard une de ces touches, mais je veux que ce soit plus improbable improbable (valeurs clés,) à choisir qu'un moins improbable (un plus probable) objet. Je me demande si vous avez des suggestions, de préférence un module python existant que je pourrais utiliser, sinon je vais avoir besoin de faire moi-même.

J'ai vérifié le module aléatoire; il ne semble pas fournir.

Je dois faire de tels choix de millions de fois pour 1000 ensembles d'objets différents contenant chacun 2.455 objets. Chaque ensemble échangera des objets entre eux de sorte que le hasard chooser doit être dynamique. Avec 1000 ensembles de 2.433 objets, qui est 2,433 millions d'objets; faible consommation de mémoire est cruciale. Et puisque ces choix ne sont pas l'essentiel de l'algorithme, j'ai besoin que ce processus soit assez rapide; CPU-temps est limité.

Thx

Mise à jour:

Ok, j'ai essayé d'examiner vos suggestions à bon escient, mais le temps est si limité ...

Je regardais à l'approche de l'arbre de recherche binaire et il semble trop risqué (complexe et compliqué). Les autres suggestions ressemblent toutes la recette ActiveState. Je l'ai pris et un peu modifié dans l'espoir de rendre plus efficace:

def windex(dict, sum, max):
    '''an attempt to make a random.choose() function that makes
    weighted choices accepts a dictionary with the item_key and
    certainty_value as a pair like:
    >>> x = [('one', 20), ('two', 2), ('three', 50)], the
    maximum certainty value (max) and the sum of all certainties.'''
    n = random.uniform(0, 1)
    sum = max*len(list)-sum 
    for key, certainty in dict.iteritems():
        weight = float(max-certainty)/sum
        if n < weight:
            break
        n = n - weight
    return key

Je suis l'espoir d'obtenir un gain d'efficacité de maintenir dynamiquement la somme des certitudes et la certitude maximale. Toutes les autres suggestions sont les bienvenus. Vous les gars me permet de gagner beaucoup de temps et d'efforts, tout en augmentant mon efficacité, il est fou. Merci! Merci! Thx!

Update2:

J'ai décidé de le rendre plus efficace en le laissant choisir plus de choix à la fois. Cela se traduira par une perte acceptable de précision dans mon algo car il est dynamique dans la nature. Quoi qu'il en soit, voici ce que j'ai maintenant:

def weightedChoices(dict, sum, max, choices=10):
    '''an attempt to make a random.choose() function that makes
    weighted choices accepts a dictionary with the item_key and
    certainty_value as a pair like:
    >>> x = [('one', 20), ('two', 2), ('three', 50)], the
    maximum certainty value (max) and the sum of all certainties.'''
    list = [random.uniform(0, 1) for i in range(choices)]
    (n, list) = relavate(list.sort())
    keys = []
    sum = max*len(list)-sum 
    for key, certainty in dict.iteritems():
        weight = float(max-certainty)/sum
        if n < weight:
            keys.append(key)
            if list: (n, list) = relavate(list)
            else: break
        n = n - weight
    return keys
def relavate(list):
    min = list[0]
    new = [l - min for l in list[1:]]
    return (min, new)

Je ne l'ai pas encore essayé. Si vous avez des commentaires / suggestions, s'il vous plaît ne pas hésiter. Thx!

Update3:

Je travaille toute la journée sur une version adapté à la tâche de la réponse de Rex Logan. Au lieu d'un 2 tableaux d'objets et de poids, il est en fait une classe spéciale dictionnaire; ce qui rend les choses assez complexe puisque le code de Rex génère un index aléatoire ... Je codait également un test qui ressemble un peu ce qui se passera dans mon algo (mais je ne peux pas vraiment savoir jusqu'à ce que j'essaie!). Le principe de base est: plus une clé est générée au hasard souvent, le plus improbable il sera généré à nouveau:

import random, time
import psyco
psyco.full()

class ProbDict():
    """
    Modified version of Rex Logans RandomObject class. The more a key is randomly
    chosen, the more unlikely it will further be randomly chosen. 
    """
    def __init__(self,keys_weights_values={}):
        self._kw=keys_weights_values
        self._keys=self._kw.keys()
        self._len=len(self._keys)
        self._findSeniors()
        self._effort = 0.15
        self._fails = 0
    def __iter__(self):
        return self.next()
    def __getitem__(self, key):
        return self._kw[key]
    def __setitem__(self, key, value):
        self.append(key, value)
    def __len__(self):
        return self._len
    def next(self):
        key=self._key()
        while key:
            yield key
            key = self._key()
    def __contains__(self, key):
        return key in self._kw
    def items(self):
        return self._kw.items()
    def pop(self, key):  
        try:
            (w, value) = self._kw.pop(key)
            self._len -=1
            if w == self._seniorW:
                self._seniors -= 1
                if not self._seniors:
                    #costly but unlikely:
                    self._findSeniors()
            return [w, value]
        except KeyError:
            return None
    def popitem(self):
        return self.pop(self._key())
    def values(self):
        values = []
        for key in self._keys:
            try:
                values.append(self._kw[key][1])
            except KeyError:
                pass
        return values
    def weights(self):
        weights = []
        for key in self._keys:
            try:
                weights.append(self._kw[key][0])
            except KeyError:
                pass
        return weights
    def keys(self, imperfect=False):
        if imperfect: return self._keys
        return self._kw.keys()
    def append(self, key, value=None):
        if key not in self._kw:
            self._len +=1
            self._kw[key] = [0, value]
            self._keys.append(key)
        else:
            self._kw[key][1]=value
    def _key(self):
        for i in range(int(self._effort*self._len)):
            ri=random.randint(0,self._len-1) #choose a random object
            rx=random.uniform(0,self._seniorW)
            rkey = self._keys[ri]
            try:
                w = self._kw[rkey][0]
                if rx >= w: # test to see if that is the value we want
                    w += 1
                    self._warnSeniors(w)
                    self._kw[rkey][0] = w
                    return rkey
            except KeyError:
                self._keys.pop(ri)
        # if you do not find one after 100 tries then just get a random one
        self._fails += 1 #for confirming effectiveness only
        for key in self._keys:
            if key in self._kw:
                w = self._kw[key][0] + 1
                self._warnSeniors(w)
                self._kw[key][0] = w
                return key
        return None
    def _findSeniors(self):
        '''this function finds the seniors, counts them and assess their age. It
        is costly but unlikely.'''
        seniorW = 0
        seniors = 0
        for w in self._kw.itervalues():
            if w >= seniorW:
                if w == seniorW:
                    seniors += 1
                else:
                    seniorsW = w
                    seniors = 1
        self._seniors = seniors
        self._seniorW = seniorW
    def _warnSeniors(self, w):
        #a weight can only be incremented...good
        if w >= self._seniorW:
            if w == self._seniorW:
                self._seniors+=1
            else:
                self._seniors = 1
                self._seniorW = w
def test():
    #test code
    iterations = 200000
    size = 2500
    nextkey = size 


    pd = ProbDict(dict([(i,[0,i]) for i in xrange(size)]))
    start = time.clock()
    for i in xrange(iterations):
        key=pd._key()
        w=pd[key][0]
        if random.randint(0,1+pd._seniorW-w):
            #the heavier the object, the more unlikely it will be removed
            pd.pop(key)
        probAppend = float(500+(size-len(pd)))/1000
        if random.uniform(0,1) < probAppend:
            nextkey+=1
            pd.append(nextkey)
    print (time.clock()-start)*1000/iterations, "msecs / iteration with", pd._fails, "failures /", iterations, "iterations"
    weights = pd.weights()
    weights.sort()
    print "avg weight:", float(sum(weights))/pd._len, max(weights), pd._seniorW, pd._seniors, len(pd), len(weights)
    print weights
test()

Les commentaires sont toujours les bienvenus. @Darius: vos arbres binaires sont trop complexes et compliquées pour moi; et je ne pense pas que ses feuilles peuvent être enlevés efficacement ... Thx tous

Était-ce utile?

La solution

Cette recette Activestate donne une approche facile à suivre, en particulier la version dans la les commentaires qui ne vous oblige pas pré-normalisent votre poids:

import random

def weighted_choice(items):
    """items is a list of tuples in the form (item, weight)"""
    weight_total = sum((item[1] for item in items))
    n = random.uniform(0, weight_total)
    for item, weight in items:
        if n < weight:
            return item
        n = n - weight
    return item

Ce sera lent si vous avez une grande liste des articles. Une recherche binaire serait probablement mieux dans ce cas ... mais serait également plus compliqué à écrire, pour peu de gain si vous avez une petite taille de l'échantillon. Voici un exemple de l'approche de recherche binaire en python si vous voulez suivre cette route.

(je vous conseille de faire quelques tests de performance rapide des deux méthodes sur votre ensemble de données. Les performances des différentes approches de ce type d'algorithme est souvent un peu unintuitive.)


Edit:. Je pris mes propres conseils, depuis que je suis curieux, et fait quelques tests

Je comparais quatre approches:

La fonction weighted_choice ci-dessus.

Une fonction de choix binaire recherche comme ceci:

def weighted_choice_bisect(items):
    added_weights = []
    last_sum = 0

    for item, weight in items:
        last_sum += weight
        added_weights.append(last_sum)

    return items[bisect.bisect(added_weights, random.random() * last_sum)][0]

Une version de la compilation 1:

def weighted_choice_compile(items):
    """returns a function that fetches a random item from items

    items is a list of tuples in the form (item, weight)"""
    weight_total = sum((item[1] for item in items))
    def choice(uniform = random.uniform):
        n = uniform(0, weight_total)
        for item, weight in items:
            if n < weight:
                return item
            n = n - weight
        return item
    return choice

Une version de compilation 2:

def weighted_choice_bisect_compile(items):
    """Returns a function that makes a weighted random choice from items."""
    added_weights = []
    last_sum = 0

    for item, weight in items:
        last_sum += weight
        added_weights.append(last_sum)

    def choice(rnd=random.random, bis=bisect.bisect):
        return items[bis(added_weights, rnd() * last_sum)][0]
    return choice

Je puis construit une grande liste de choix comme ceci:

choices = [(random.choice("abcdefg"), random.uniform(0,50)) for i in xrange(2500)]

Et une fonction de profilage trop simple:

def profiler(f, n, *args, **kwargs):
    start = time.time()
    for i in xrange(n):
        f(*args, **kwargs)
    return time.time() - start

Les résultats:

(secondes prises pour 1000 appels à la fonction.)

  • Simple décompilé: 0,918624162674
  • Binary décompilé: 1,01497793198
  • Simple compilé: 0,287325024605
  • binaire compilé: ,00327413797379

Les « compilées » résultats comprennent le temps moyen pris pour compiler une fois la fonction de choix. (Je l'ai chronométré 1000 compiles, puis divisé par le temps que 1000, et a ajouté le résultat du temps de fonction de choix.)

. Si vous avez une liste d'éléments + de poids qui changent très rarement, la méthode compilé binaire est de loin le plus rapide

Autres conseils

Dans les commentaires sur le message original, Nicholas Leonard suggère que l'échange et à la fois l'échantillonnage doit être rapide. Voici une idée pour ce cas; Je ne l'ai pas essayé.

Si seulement l'échantillonnage devait être rapide, nous pourrions utiliser un tableau des valeurs ainsi que la somme en cours d'exécution de leurs probabilités, et faire une recherche binaire sur la somme courante (avec clé étant un nombre aléatoire uniforme) - un O fonctionnement (log (n)). Mais un échange nécessiterait la mise à jour toutes les valeurs-somme courante apparaissant après les entrées échangées - une opération O (n). (Pouvez-vous choisir d'échanger des articles que près de la fin de leur liste? Je suppose que non.)

Alors Visons O (log (n)) dans les deux opérations. Au lieu d'un tableau, gardez un arbre binaire pour chaque ensemble d'échantillonner. Une feuille contient la valeur de l'échantillon et son (non normalisée) probabilité. Un noeud de branche possède la probabilité totale de ses enfants.

Pour l'échantillon, générer un nombre aléatoire uniforme entre 0 et x la probabilité totale de la racine, et descendre l'arbre. A chaque branche, choisissez l'enfant gauche si l'enfant gauche a une probabilité totale <= x. Else soustrayez la probabilité de l'enfant de gauche x et allez à droite. Renvoie la valeur de la feuille que vous atteignez.

Pour échanger, retirer la feuille de son arbre et d'ajuster les branches qui mènent à elle (en diminuant leur probabilité totale et découper tous les nœuds de branche unique enfant). Insérez la feuille dans l'arbre de destination: vous avez le choix de l'endroit où le mettre, donc garder l'équilibre. Le choix d'un enfant au hasard à chaque niveau est probablement assez bon - c'est là que je commencerais. Augmenter la probabilité de chaque nœud parent, le dos jusqu'à la racine.

Maintenant, les deux échantillons et d'échange sont O (log (n)) en moyenne. (Si vous avez besoin d'équilibre garanti, un moyen simple est d'ajouter un autre champ aux nœuds de branche maintenant le nombre de feuilles dans le sous-arbre entier. Lorsque vous ajoutez une feuille, à chaque niveau ramasser l'enfant avec moins de feuilles. Cela laisse la possibilité d'une arbre devient déséquilibrée uniquement par des suppressions,. cela ne peut pas être un problème s'il y a raisonnablement même le trafic entre les jeux, mais si elle est, puis choisissez la rotation lors de la suppression en utilisant les informations de comptage de feuilles sur chaque nœud dans votre traversal)

Mise à jour: Sur demande, voici une implémentation de base. Je n'ai pas accordé du tout. Utilisation:

>>> t1 = build_tree([('one', 20), ('two', 2), ('three', 50)])
>>> t1
Branch(Leaf(20, 'one'), Branch(Leaf(2, 'two'), Leaf(50, 'three')))
>>> t1.sample()
Leaf(50, 'three')
>>> t1.sample()
Leaf(20, 'one')
>>> t2 = build_tree([('four', 10), ('five', 30)])
>>> t1a, t2a = transfer(t1, t2)
>>> t1a
Branch(Leaf(20, 'one'), Leaf(2, 'two'))
>>> t2a
Branch(Leaf(10, 'four'), Branch(Leaf(30, 'five'), Leaf(50, 'three')))

Code:

import random

def build_tree(pairs):
    tree = Empty()
    for value, weight in pairs:
        tree = tree.add(Leaf(weight, value))
    return tree

def transfer(from_tree, to_tree):
    """Given a nonempty tree and a target, move a leaf from the former to
    the latter. Return the two updated trees."""
    leaf, from_tree1 = from_tree.extract()
    return from_tree1, to_tree.add(leaf)

class Tree:
    def add(self, leaf):
        "Return a new tree holding my leaves plus the given leaf."
        abstract
    def sample(self):
        "Pick one of my leaves at random in proportion to its weight."
        return self.sampling(random.uniform(0, self.weight))
    def extract(self):
        """Pick one of my leaves and return it along with a new tree
        holding my leaves minus that one leaf."""
        return self.extracting(random.uniform(0, self.weight))        

class Empty(Tree):
    weight = 0
    def __repr__(self):
        return 'Empty()'
    def add(self, leaf):
        return leaf
    def sampling(self, weight):
        raise Exception("You can't sample an empty tree")
    def extracting(self, weight):
        raise Exception("You can't extract from an empty tree")

class Leaf(Tree):
    def __init__(self, weight, value):
        self.weight = weight
        self.value = value
    def __repr__(self):
        return 'Leaf(%r, %r)' % (self.weight, self.value)
    def add(self, leaf):
        return Branch(self, leaf)
    def sampling(self, weight):
        return self
    def extracting(self, weight):
        return self, Empty()

def combine(left, right):
    if isinstance(left, Empty): return right
    if isinstance(right, Empty): return left
    return Branch(left, right)

class Branch(Tree):
    def __init__(self, left, right):
        self.weight = left.weight + right.weight
        self.left = left
        self.right = right
    def __repr__(self):
        return 'Branch(%r, %r)' % (self.left, self.right)
    def add(self, leaf):
        # Adding to a random branch as a clumsy way to keep an
        # approximately balanced tree.
        if random.random() < 0.5:
            return combine(self.left.add(leaf), self.right)
        return combine(self.left, self.right.add(leaf))
    def sampling(self, weight):
        if weight < self.left.weight:
            return self.left.sampling(weight)
        return self.right.sampling(weight - self.left.weight)
    def extracting(self, weight):
        if weight < self.left.weight:
            leaf, left1 = self.left.extracting(weight)
            return leaf, combine(left1, self.right)
        leaf, right1 = self.right.extracting(weight - self.left.weight)
        return leaf, combine(self.left, right1)

Mise à jour 2: Dans répondre à une autre problème, Jason Orendorff souligne que les arbres binaires peuvent être conservés parfaitement équilibré en les représentant dans un tableau tout comme la structure de tas classique. (Cela permet d'économiser l'espace consacré à des pointeurs, aussi.) Voir mes commentaires à cette réponse pour savoir comment adapter son code à ce problème.

Je vous suggère le port cette implémentation PHP de aléatoire pondéré pour Python. En particulier, le deuxième algorithme basé recherche binaire permettant de soulager vos problèmes de vitesse.

Voici une façon classique de le faire, dans pseudocode, où random.random () vous donne un flotteur aléatoire de 0 à 1.

let z = sum of all the convictions
let choice = random.random() * z 
iterate through your objects:
    choice = choice - the current object's conviction
    if choice <= 0, return this object
return the last object

Pour un exemple: imaginez que vous avez deux objets, l'un avec le poids 2, une autre avec un poids 4. Vous générez un nombre de 0 à 6. Si choice est compris entre 0 et 2, ce qui va se passer avec 1 = 2/6 / 3 probabilité, alors il se soustrait par deux et le premier objet est choisi. Si le choix se situe entre 2 et 6, qui se produira avec 4/6 = 2/3 probabilité, la première soustraction aura toujours le choix d'être> 0, et la deuxième soustraction fera l'objet 2 se choisi.

Vous voulez donner à chaque objet un poids. Plus le poids plus il est probable que cela se produira. Plus précisément probx = poids / sum_all_weights.

Ensuite, générer un nombre aléatoire dans la plage 0 à sum_all_weights et la carte à chaque objet.

Ce code vous permet de générer un index aléatoire et il est mis en correspondance lorsque l'objet est créé pour la vitesse. Si tous vos ensembles d'objets ont la même distribution, vous pouvez obtenir avec un seul objet RandomIndex.

import random

class RandomIndex:
    def __init__(self, wlist):
        self._wi=[]
        self._rsize=sum(wlist)-1
        self._m={}
        i=0
        s=wlist[i]
        for n in range(self._rsize+1):
            if n == s:
                i+=1
                s+=wlist[i]
            self._m[n]=i    

    def i(self):
        rn=random.randint(0,self._rsize)
        return self._m[rn]


sx=[1,2,3,4]


wx=[1,10,100,1000] #weight list
ri=RandomIndex(wx)

cnt=[0,0,0,0]

for i in range(1000):
    cnt[ri.i()] +=1  #keep track of number of times each index was generated

print(cnt)  

Environ 3 ans plus tard ...

Si vous utilisez numpy, peut-être l'option la plus simple est d'utiliser np.random.choice , qui prend une liste de valeurs possibles, et en option une séquence de probabilités associées à chaque valeur:

import numpy as np

values = ('A', 'B', 'C', 'D')
weights = (0.5, 0.1, 0.2, 0.2)

print ''.join(np.random.choice(values, size=60, replace=True, p=weights))
# ACCADAACCDACDBACCADCAAAAAAADACCDCAADDDADAAACCAAACBAAADCADABA

La chose la plus simple à faire est d'utiliser random.choice (qui utilise une distribution uniforme) et faire varier la fréquence d'occurrence de l'objet dans la collection source.

>>> random.choice([1, 2, 3, 4])
4

... vs:

>>> random.choice([1, 1, 1, 1, 2, 2, 2, 3, 3, 4])
2

Ainsi, vos objets pourraient avoir un taux d'occurrence de base (n) et entre 1 et n objets sont ajoutés à la collection source en fonction du taux de condamnation. Cette méthode est très simple; cependant, il peut avoir une surcharge significative si le nombre d'objets distincts est grand ou le taux de condamnation doit être à grain très fin.

Par ailleurs, si vous produisez plus d'un nombre aléatoire avec une distribution uniforme et leur somme, un nombre qui se produisent près de la moyenne sont plus probables que ceux qui se produisent près des extrêmes (pensez à rouler deux dés et la probabilité d'obtenir 7 contre 12 ou 2). Vous pouvez alors commander les objets par le taux de condamnation et de générer un numéro à l'aide de multiples jets de dés que vous utilisez pour calculer et indexer les objets. Utilisez des chiffres près de la moyenne pour indexer les objets à faible conviction et des nombres à proximité des extrêmes à l'index des articles de grande conviction. Vous pouvez varier la probabilité précise qu'un objet donné sera sélectionné en changeant le « nombre de côtés » et le nombre de vos « dés » (il peut être plus simple de mettre les objets dans des seaux et utiliser dés avec un petit nombre de côtés plutôt que en essayant d'associer chaque objet à un résultat spécifique):

>>> die = lambda sides : random.randint(1, sides)
>>> die(6)
3
>>> die(6) + die(6) + die(6)
10

Une façon très facile et simple de le faire est de fixer des poids pour chacune des valeurs, et il ne nécessite pas beaucoup de mémoire.

Vous pouvez probablement utiliser un hachage / dictionnaire pour le faire.

Qu'est-ce que vous voulez faire est d'avoir le nombre aléatoire, x , multiplié et sommé sur l'ensemble des choses que vous voulez sélectionné et diviser le résultat par le nombre d'objets dans votre ensemble.

Pseudo-code:

objectSet = [(object1, weight1), ..., (objectN, weightN)]
sum = 0
rand = random()
for obj, weight in objectSet
    sum = sum+weight*rand
choice = objectSet[floor(sum/objectSet.size())]

EDIT : Je viens de penser à la lenteur de mon code serait avec des ensembles très importants (il est O (n)). Le pseudo-code suivant est O (log (n)), et utilise essentiellement une recherche binaire.

objectSet = [(object1, weight1), ..., (objectN, weightN)]
sort objectSet from less to greater according to weights
choice = random() * N # where N is the number of objects in objectSet
do a binary search until you have just one answer

Il existe des implémentations de recherche binaire en Python sur tout le net, donc pas besoin de répéter ici.

Voici une meilleure réponse pour une distribution spéciale de probabilité, la réponse de celui Rex Logan semble être orientée à. La répartition est comme suit: chaque objet a un poids entier compris entre 0 et 100, et sa probabilité est proportionnelle à son poids. Puisque c'est la réponse actuellement acceptée, je suppose que cela vaut la peine de penser à.

Alors gardez un tableau de 101 bacs. Chaque casier contient une liste de tous les objets avec son poids particulier. Chaque bac connaît aussi le total poids de tous ses objets.

Pour exemple: choisissez un bac au hasard en proportion de son poids total. (Utilisez l'une des recettes standard pour cela -. Recherche linéaire ou binaire). Ensuite, choisissez un objet dans le bac uniformément au hasard

Pour transférer un objet: le retirer de son bac, le mettre dans son bac dans la cible, et mettre à jour les poids des deux bacs. (Si vous utilisez la recherche binaire pour l'échantillonnage, vous devez également mettre à jour les cumuls qui utilise. Ceci est encore assez rapide car il n'y a pas beaucoup poubelles.)

je avais besoin des fonctions plus rapides, pour les numéros de très grandes non. Donc, ici, il est, dans Visual C ++:

#undef _DEBUG // disable linking with python25_d.dll
#include <Python.h>
#include <malloc.h>
#include <stdlib.h>

static PyObject* dieroll(PyObject *, PyObject *args)
{
    PyObject *list;
    if (!PyArg_ParseTuple(args, "O:decompress", &list))
        return NULL;

    if (!PyList_Check(list)) 
        return PyErr_Format(PyExc_TypeError, "list of numbers expected ('%s' given)", list->ob_type->tp_name), NULL;

    int size = PyList_Size(list);

    if (size < 1)
        return PyErr_Format(PyExc_TypeError, "got empty list"), NULL;

    long *array = (long*)alloca(size*sizeof(long));

    long sum = 0;
    for (int i = 0; i < size; i++) {
        PyObject *o = PyList_GetItem(list, i);

        if (!PyInt_Check(o))
            return PyErr_Format(PyExc_TypeError, "list of ints expected ('%s' found)", o->ob_type->tp_name), NULL;
        long n = PyInt_AsLong(o);
        if (n == -1 && PyErr_Occurred())
            return NULL;
        if (n < 0)
            return PyErr_Format(PyExc_TypeError, "list of positive ints expected (negative found)"), NULL;

        sum += n; //NOTE: integer overflow
        array[i] = sum;
    }

    if (sum <= 0)
        return PyErr_Format(PyExc_TypeError, "sum of numbers is not positive"), NULL;

    int r = rand() * (sum-1) / RAND_MAX; //NOTE: rand() may be too small (0x7fff).    rand() * sum may result in integer overlow.

    assert(array[size-1] == sum);
    assert(r < sum && r < array[size-1]);
    for (int i = 0; i < size; ++i)
    {
        if (r < array[i])
            return PyInt_FromLong(i);
    }
    return PyErr_Format(PyExc_TypeError, "internal error."), NULL;
}

static PyMethodDef module_methods[] = 
{
    {"dieroll", (PyCFunction)dieroll, METH_VARARGS, "random index, beased on weights" },
    {NULL}  /* Sentinel */
};

PyMODINIT_FUNC initdieroll(void) 
{
    PyObject *module = Py_InitModule3("dieroll", module_methods, "dieroll");
    if (module == NULL)
        return;
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top