Jak utworzyć listę losowych liczb całkowitych, których suma wynosi x

Tworzenie losowego wektora, którego suma wynosi X (np. X = 1000), jest dość proste:

import random
def RunFloat():
    Scalar = 1000
    VectorSize = 30
    RandomVector = [random.random() for i in range(VectorSize)]
    RandomVectorSum = sum(RandomVector)
    RandomVector = [Scalar*i/RandomVectorSum for i in RandomVector]
    return RandomVector
RunFloat()

Powyższy kod tworzy wektor, którego wartości są zmiennoprzecinkowe, a suma wynosi 1000.

Mam problem z utworzeniem prostej funkcji do tworzenia wektora, którego wartości są liczbami całkowitymi, a suma to X (np. X = 1000 * 30)

import random
def RunInt():
    LowerBound = 600
    UpperBound = 1200
    VectorSize = 30
    RandomVector = [random.randint(LowerBound,UpperBound) for i in range(VectorSize)]
    RandomVectorSum = 1000*30
    #Sanity check that our RandomVectorSum is sensible/feasible
    if LowerBound*VectorSize <= RandomVectorSum and RandomVectorSum <= UpperBound*VectorSum:
        if sum(RandomVector) == RandomVectorSum:
            return RandomVector
        else:
            RunInt()  

Czy ktoś ma jakieś sugestie dotyczące ulepszenia tego pomysłu? Mój kod może nigdy się nie skończyć ani nie napotkać problemów z głębokością rekurencji.

Edytuj (9 lipca 2012 r.)

Podziękowania dla Olivera, mgilsona i Dougala za ich wkład. Moje rozwiązanie przedstawiono poniżej.

Oliver był bardzo kreatywny w idei dystrybucji wielomianowejMówiąc prościej, (1) jest prawdopodobne, że niektóre rozwiązania będą bardziej wydajne niż inne. Dougal wykazał, że wielomianowy rozkład przestrzeni rozwiązania nie jest jednolity ani normalny dzięki prostemu przykładowi testu / licznika prawa dużych liczb. Dougal zasugerował również użycie funkcji wielomianowej numpy, która oszczędza mi wiele problemów, bólu i bólów głowy.Aby przezwyciężyć (2) problem wyjściowy, używam RunFloat (), aby dać to, co się pojawia (nie przetestowałem tego, więc jest to tylko powierzchowny wygląd), aby był bardziej jednolity. Ile to robi w porównaniu z (1)? Naprawdę nie wiem od ręki. Jest jednak wystarczająco dobry do mojego użytku.Jeszcze raz dziękuję mgilson za alternatywną metodę, która nie używa numpy.

Oto kod, który zrobiłem dla tej edycji:

Edytuj # 2 (lipiec 11,2012)

Zrozumiałem, że normalna dystrybucja nie jest poprawnie zaimplementowana, od tego czasu zmodyfikowałem ją do następujących:

import random
def RandFloats(Size):
    Scalar = 1.0
    VectorSize = Size
    RandomVector = [random.random() for i in range(VectorSize)]
    RandomVectorSum = sum(RandomVector)
    RandomVector = [Scalar*i/RandomVectorSum for i in RandomVector]
    return RandomVector

from numpy.random import multinomial
import math
def RandIntVec(ListSize, ListSumValue, Distribution='Normal'):
    """
    Inputs:
    ListSize = the size of the list to return
    ListSumValue = The sum of list values
    Distribution = can be 'uniform' for uniform distribution, 'normal' for a normal distribution ~ N(0,1) with +/- 5 sigma  (default), or a list of size 'ListSize' or 'ListSize - 1' for an empirical (arbitrary) distribution. Probabilities of each of the p different outcomes. These should sum to 1 (however, the last element is always assumed to account for the remaining probability, as long as sum(pvals[:-1]) <= 1).  
    Output:
    A list of random integers of length 'ListSize' whose sum is 'ListSumValue'.
    """
    if type(Distribution) == list:
        DistributionSize = len(Distribution)
        if ListSize == DistributionSize or (ListSize-1) == DistributionSize:
            Values = multinomial(ListSumValue,Distribution,size=1)
            OutputValue = Values[0]
    elif Distribution.lower() == 'uniform': #I do not recommend this!!!! I see that it is not as random (at least on my computer) as I had hoped
        UniformDistro = [1/ListSize for i in range(ListSize)]
        Values = multinomial(ListSumValue,UniformDistro,size=1)
        OutputValue = Values[0]
    elif Distribution.lower() == 'normal':
        """
        Normal Distribution Construction....It's very flexible and hideous
        Assume a +-3 sigma range.  Warning, this may or may not be a suitable range for your implementation!
        If one wishes to explore a different range, then changes the LowSigma and HighSigma values
        """
        LowSigma    = -3#-3 sigma
        HighSigma   = 3#+3 sigma
        StepSize    = 1/(float(ListSize) - 1)
        ZValues     = [(LowSigma * (1-i*StepSize) +(i*StepSize)*HighSigma) for i in range(int(ListSize))]
        #Construction parameters for N(Mean,Variance) - Default is N(0,1)
        Mean        = 0
        Var         = 1
        #NormalDistro= [self.NormalDistributionFunction(Mean, Var, x) for x in ZValues]
        NormalDistro= list()
        for i in range(len(ZValues)):
            if i==0:
                ERFCVAL = 0.5 * math.erfc(-ZValues[i]/math.sqrt(2))
                NormalDistro.append(ERFCVAL)
            elif i ==  len(ZValues) - 1:
                ERFCVAL = NormalDistro[0]
                NormalDistro.append(ERFCVAL)
            else:
                ERFCVAL1 = 0.5 * math.erfc(-ZValues[i]/math.sqrt(2))
                ERFCVAL2 = 0.5 * math.erfc(-ZValues[i-1]/math.sqrt(2))
                ERFCVAL = ERFCVAL1 - ERFCVAL2
                NormalDistro.append(ERFCVAL)  
            #print "Normal Distribution sum = %f"%sum(NormalDistro)
            Values = multinomial(ListSumValue,NormalDistro,size=1)
            OutputValue = Values[0]
        else:
            raise ValueError ('Cannot create desired vector')
        return OutputValue
    else:
        raise ValueError ('Cannot create desired vector')
    return OutputValue
#Some Examples        
ListSize = 4
ListSumValue = 12
for i in range(100):
    print RandIntVec(ListSize, ListSumValue,Distribution=RandFloats(ListSize))

Powyższy kod można znaleźć nagithub. Jest częścią klasy, którą zbudowałem dla szkoły. user1149913, opublikował również ładne wyjaśnienie problemu.

questionAnswers(7)

yourAnswerToTheQuestion