noExplorer
noExplorer

Reputation: 23

Generating a random number with specific amount of digits and sum_of_digits (Python 3.8)

I want to generate a random number with a specific amount of digits (specified by user) and those digits should have a specific sum that has been specified by the user. I have tried assigning a variable to the command random.randint(x,y) and then checking the len() and sum() of that variable if it is equal to the one specified by the user but it hasn't resulted in something useful. The code that I have tried looks something like this:

required_length = input()
required_sum = input()
z = random.randint(0 , 99999)
number = z
length = len(z)
sum1 = sum(int(digit) for digit in str(number))
if length == required_length and sum1 == required_sum:
     print(z)

To help if I haven't been specific enough, let's say I want to generate a number that should have 7 digits, and the sum of those 7 digits should be 7. One of those random numbers could be 1121101. I hope this example helped.

See you soon all, and thanks a lot for your help.

Upvotes: 2

Views: 796

Answers (2)

norok2
norok2

Reputation: 26896

The way I would go on this would be to generate a number on a digit by digit basis, where each digit is random within the given constraints.

The idea is that one should pick for a given digit a random number (between 0 and 9 included) except when the outcome could result in a violation of the constraints, in which case the range from which to pick the random number shall be reduced accordingly.

In other words, each digit is picked among the allowed values given the "remaining" sum and number of digits.

To simplify the computation I work on lists of digits and I provide some function to convert from list of digits to integer and viceversa. Note that for incompatible num of digits and sum value, an empty list is produced.

import random


def seq_to_int(seq, base=10):
    """Convert sequence of digits to an integer."""
    result = 0
    for i, x in enumerate(reversed(seq)):
        if 0 <= x < base:
            result += x * base ** i
        else:
            msg = f"Invalid value in `seq` for given `base`"
            raise ValueError(msg)
    return result


def int_to_seq(n, base=10):
    """Convert an integer to a sequence of digits."""
    if n == 0:
        return [0]
    elif n < 0:
        raise ValueError("`n` must be non-negative.")
    else:
        result = []
        while n:
            result.append(n % base)
            n //= base
        return result[::-1]


def random_digits_given_sum(num_digits, sum_value, base=10):    
    digits = []
    max_digit = base - 1
    if sum_value > max_digit * num_digits:
        return digits
    sum_left = sum_value
    num_left = num_digits
    while num_left > 0:
        if sum_left > max_digit:
            min_rand = max(0, sum_left - max_digit * (num_left - 1))
            max_rand = max_digit
            digit = random.randint(min_rand, max_rand)
        elif sum_left >= 0 and num_left > 1:
            min_rand = 0
            max_rand = sum_left
            digit = random.randint(min_rand, max_rand)
        elif sum_left >= 0 and num_left == 1:
            digit = sum_left
        else:
            raise ValueError        
        digits.append(digit)
        sum_left -= digit
        num_left -= 1
    # ensure first digit is not 0
    if digits[0] == 0:
        non_zero_indices = [i for i, digit in enumerate(digits) if digit]
        i = random.choice(non_zero_indices)
        digits[0], digits[i] = digits[i], digits[0]
    # shuffle remaining digits
    shuffled = digits[1:]
    random.shuffle(shuffled)
    return digits[0:1] + shuffled

Below some code to test that it is actually working:

random.seed(0)
n = 2  # number of random inputs to gets
k = 4  # number of different generations
for _ in range(n):
    num_digits = random.randint(1, 16)
    sum_value = random.randint(1, num_digits * 9)
    for _ in range(k):
        digits = random_digits_given_sum(num_digits, sum_value)
        print(digits, seq_to_int(digits), sum(digits), sum(digits) == sum_value)

with the following output:

[6, 8, 8, 9, 8, 8, 0, 9, 9, 9, 9, 6, 9] 6889880999969 98 True
[5, 9, 4, 7, 9, 5, 6, 9, 9, 9, 8, 9, 9] 5947956999899 98 True
[8, 1, 9, 8, 2, 9, 9, 7, 9, 9, 9, 9, 9] 8198299799999 98 True
[4, 2, 9, 7, 8, 9, 9, 9, 9, 9, 9, 9, 5] 4297899999995 98 True
[6, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 4, 9, 9] 699999999999499 127 True
[4, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 6, 9] 499999999999969 127 True
[3, 9, 9, 9, 9, 7, 9, 9, 9, 9, 9, 9, 9, 9, 9] 399997999999999 127 True
[9, 7, 5, 9, 9, 9, 9, 9, 8, 9, 8, 9, 9, 9, 9] 975999998989999 127 True

showing that it is working as expected.

Upvotes: 1

Ratery
Ratery

Reputation: 2917

Simple solution

You can create a list of numbers which can be generated and then use random.choice() function to choose 1 element from list.

import random

n = int(input("Amount of digits: "))
x = int(input("Sum of digits: "))
numbers = [num for num in range(10 ** (n - 1), 10 ** n) if sum(map(int, str(num))) == x]
print(random.choice(numbers))

Fast solution

import random

t = []

def f(s, i):
    r = sum(map(int, s))
    if i == n:
        if r == x:
            t.append(s)
    else:
        for q in range(10) if len(s) else range(1, 10):
            if r + q <= x:
                f(s + str(q), i + 1)

n = int(input("Amount of digits: "))
x = int(input("Sum of digits: "))

f("", 0)
print(random.choice(t))

Upvotes: 1

Related Questions