0

Let's say I have 3 different lists

A = [1, 2, 3, 4, 5]
B = [11,12, 13, 14, 15]
C = [21, 22, 23, 24, 25]

I want to select 5 random numbers(repeating numbers are fine) from the above lists but it must contain at least one number from each of the lists. Here is my attempted implementation.

from random import choice
for i in range(5): # total numbers selected can be variable
    print(choice(choice([A, B, C]))) #inside choice chooses the list and outside one the number

The above code doesn't necessarily select numbers from each of the lists. How can I select the numbers from the lists ensuring at least one number is chosen from each list?

  • 2
    first select 1 from each, then select 2 more from the combined set of all 3? – Dave S Jan 09 '23 at 22:48
  • 1
    To pick N numbers from M lists, pick one number from each of the lists, then (N-M) numbers from the concatenation of all lists, then shuffle the result. – jasonharper Jan 09 '23 at 22:48
  • 1
    "How can I select the numbers from the lists ensuring at least one number is chosen from each list?" Well, if you first choose one number from each list, then that satisfies that part of the requirement, right? Would there be any more restrictions on the rest of the numbers to choose? How many more such numbers would there be? How would you choose them? If you put these steps together, does it solve the problem? What actually is the difficulty? – Karl Knechtel Jan 09 '23 at 22:56
  • If for example A and B both contain the same number would you want that number to have a higher chance of being picked when generating a random number from all of the lists combined? – CodeCop Jan 09 '23 at 22:59
  • Sorry for late reply and thanks for the suggestions. Picking one number from the 3 lists and than selecting randomly from the combined lists would indeed solve the problem. I didn't think of that before. Thanks again. – Kunjan Rana Jan 17 '23 at 21:32

4 Answers4

2

Choose one item from each list (to ensure that requirement is satisfied); then choose the rest of the items from a flat list that is the concatenation of all lists:

import random

n = 5
lol = [A, B, C]
flat = [x for sub in lol for x in sub]
a = [random.choice(sub) for sub in lol] + random.choices(flat, k=n - len(lol))
Pierre D
  • 24,012
  • 7
  • 60
  • 96
  • Do *not* use `sum(abc, [])` to flatten a list of lists. This is a well-known anti-pattern. Indeed, `sum` will prevent you from doing this with other sequences, and may fail with lists in the future. – juanpa.arrivillaga Jan 09 '23 at 22:54
  • I edited the question. `sum` will have quadratic behavior, whereas doing the naive thing `result = []; for sub in abc: result += sub` will be linear. If you want a one liner, `[x for sub in abc for x in sub]` works as well – juanpa.arrivillaga Jan 09 '23 at 22:56
  • or in this case, just `[*A, *B, *C]`. For these sizes it doesn't matter of course, but `sum(nested, [])` should just be avoided – juanpa.arrivillaga Jan 09 '23 at 22:57
  • "What is the correct pattern then?" Please see [How do I make a flat list out of a list of lists?](https://stackoverflow.com/questions/952914) and [How can I get a flat result from a list comprehension instead of a nested list?](https://stackoverflow.com/questions/1077015), as appropriate to the input specification. – Karl Knechtel Jan 09 '23 at 22:58
  • 1
    `[value for sub in nested for value in sub]` is the common idiom. – juanpa.arrivillaga Jan 09 '23 at 22:59
  • Right. Thanks. `[*A, *B, *C]` is quite cute. – Pierre D Jan 09 '23 at 23:01
  • @PierreD thanks for the solution, it works and is pretty concise. – Kunjan Rana Jan 17 '23 at 21:50
1

You can use itertools.cycle with random.choice to cycle through the lists until you have as many results as you need:

from itertools import cycle
from random import choice

def pick_values(groups, count):
    results = []
    for group in cycle(groups):
        results.append(choice(group))
        if len(results) == count:
            break
    return results

An example of how to call it and the results:

>>> pick_values([A, B, C], 5)
[3, 15, 23, 1, 15]

The len(...) call could be skipped by using enumerate:

def pick_values(groups, count):
    results = []
    for i, group in enumerate(cycle(groups), 1):
        results.append(choice(group))
        if i == count:
            break
    return results
dskrypa
  • 1,004
  • 2
  • 5
  • 17
  • Thanks for your response @dskrypa, it does solve the problem at hand, but the results returned becomes predictable as the total numbers picked from each list can be predetermined while I want my selection to be as random as possible. – Kunjan Rana Jan 17 '23 at 22:12
1

You want to ensure picking one from each list, so:

import random

lists = [A,B,C]
number_of_lists = len(lists)
randoms_from_lists = list([random.choice(lst) for lst in lists])

Then you have two options:

  1. unite the lists to one big list with repetitions and use the random.choices method that returns k amount of random samples from the first parameters, as such:
lists = A + B + C
N=10 # <-- can be changed to a parameter in a function
rest_of_random_integers = list(random.choices(lists, k=N-number_of_lists))

# Add the random numbers from the lists to the "general" random from all lists
randoms = randoms_from_lists  + rest_of_random_integers
  1. Without repetitions, giving each number the same chance of being picked when uniting the lists - we can use set for that:
lists = A + B + C
rest_of_random_integers = list(random.choices(list(set(lists)), k=N-3)))
randoms = randoms_from_lists  + rest_of_random_integers

finally to randomize the order we can shuffle for both cases:

randoms.shuffle(randoms)

If you don't need to hold states of one of the lists it's better to use A.extend(B) (and also A.extend(C)) - in order to skip the A + B + C part which creates a new list for you, from a new fresh memory which is time (and of-course memory) consuming.

CodeCop
  • 1
  • 2
  • 15
  • 37
0
import random

a = [1, 2, 3, 4, 5]
b = [11, 12, 13, 14, 15]
c = [21, 22, 23, 24, 25]


def get_number():
    return random.choice([num for sub in zip(a, b, c) for num in sub])


def get_uniq_numbers(amount=1):
    result = []
    while len(result) < amount:
        number = get_number()
        if number not in result:
            result.append(number)
    return result


print(get_uniq_numbers(amount=5))

result

[4, 24, 22, 11, 25]
Ronin
  • 1,811
  • 1
  • 16
  • 17