Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python limited combination

I have a list with 200 elements. I want to randomly calculate 10% of all the combinations of length k of those elements and store the result in a list.

For example:

Assume 'ABCD' as in ['A', 'B', 'C', 'D'] and I want combinations of length 2. All possible combinations in this case would be 6 (n! / ((n-k)! x k!)). I want to get 10% of that which is 0.6 -> 1 (round up).

I tried itertools.combinations('ABCD', 2) but it gives me all combinations.


Here's some more information about my problem.

I have

all_points_coordinates =  [
    [-1.6339171050450814, 2.5160117038362722], 
    [-1.7207293090531386, 2.4574561328669748], 
    [0.10469849010750323, 2.9981724810572872],
]

and I want to calculate combinations of 3 of them and use

def all_way(points):
    point = len(points)
    allrout = []
    allrout = list(itertools.permutations(points, point))
    return allrout

but it gives me all combinations of my points. When I run it for 100 points it is very time consuming so I want to calculate just a limited number of these combinations.

like image 666
Ozan Tunahan Ismailoglu Avatar asked Jul 26 '26 08:07

Ozan Tunahan Ismailoglu


2 Answers

We can generate the random combinations using random.sample, and use a set to ensure we don't generate any combination more than once. Here's a simple demo.

from random import seed, sample

seed(42)

def random_combinations(seq, size, num):
    combos = set()
    while len(combos) < num:
        item = sample(seq, size)
        combos.add(tuple(item))
    return list(combos)

# test

data = [
    (0, 1), (2, 3), (4, 5), (6, 7), (8, 9), 
    (10, 11), (12, 13), (14, 15), (16, 17), (18, 19),
]

# Make 20 random 3-element combinations
combos = random_combinations(data, 3, 20)
for i, item in enumerate(combos, 1):
    print('{:>2}: {}'.format(i, item))

output

 1: ((2, 3), (12, 13), (8, 9))
 2: ((6, 7), (18, 19), (4, 5))
 3: ((2, 3), (16, 17), (18, 19))
 4: ((0, 1), (4, 5), (12, 13))
 5: ((14, 15), (10, 11), (4, 5))
 6: ((2, 3), (0, 1), (8, 9))
 7: ((6, 7), (16, 17), (0, 1))
 8: ((12, 13), (2, 3), (8, 9))
 9: ((6, 7), (14, 15), (8, 9))
10: ((10, 11), (18, 19), (8, 9))
11: ((0, 1), (14, 15), (2, 3))
12: ((18, 19), (10, 11), (6, 7))
13: ((18, 19), (12, 13), (0, 1))
14: ((10, 11), (8, 9), (4, 5))
15: ((8, 9), (2, 3), (6, 7))
16: ((2, 3), (0, 1), (6, 7))
17: ((16, 17), (6, 7), (12, 13))
18: ((2, 3), (12, 13), (18, 19))
19: ((0, 1), (2, 3), (6, 7))
20: ((6, 7), (10, 11), (2, 3))

As tobias_k mentions in the comments, this code is only suitable when num is not too close to the total number of combinations. If you want < 50% of the total number of combinations it should be fine, but beyond that it will have a high chance of re-generating combinations that it's already generated, which will cause it to loop for a long time.


Note that this code considers ((2, 3), (12, 13), (8, 9)) to be distinct from a tuple containing those 3 pairs in a different order, eg ((2, 3), (8, 9), (12, 13)).

If you don't want that we can make our items into sets. We need to use frozenset for this, since normal sets are mutable and therefore unhashable and hence cannot be set items.

from random import seed, sample

seed(42)

def random_combinations(seq, size, num):
    combos = set()
    while len(combos) < num:
        item = sample(seq, size)
        combos.add(frozenset(item))
    return [tuple(u) for u in combos]

# test

data = [
    (0, 1), (2, 3), (4, 5), (6, 7), (8, 9), 
    (10, 11), (12, 13), (14, 15), (16, 17), (18, 19),
]

# Make 20 random 3-element combinations
combos = random_combinations(data, 3, 20)
for i, item in enumerate(combos, 1):
    print('{:>2}: {}'.format(i, item))

output

 1: ((0, 1), (2, 3), (6, 7))
 2: ((0, 1), (2, 3), (8, 9))
 3: ((16, 17), (6, 7), (0, 1))
 4: ((12, 13), (2, 3), (18, 19))
 5: ((12, 13), (2, 3), (8, 9))
 6: ((12, 13), (18, 19), (0, 1))
 7: ((8, 9), (4, 5), (10, 11))
 8: ((16, 17), (2, 3), (18, 19))
 9: ((8, 9), (6, 7), (14, 15))
10: ((0, 1), (4, 5), (12, 13))
11: ((8, 9), (10, 11), (18, 19))
12: ((10, 11), (6, 7), (2, 3))
13: ((0, 1), (14, 15), (2, 3))
14: ((10, 11), (18, 19), (6, 7))
15: ((8, 9), (2, 3), (6, 7))
16: ((4, 5), (6, 7), (18, 19))
17: ((8, 9), (4, 5), (2, 3))
18: ((16, 17), (4, 5), (6, 7))
19: ((16, 17), (6, 7), (12, 13))
20: ((4, 5), (10, 11), (14, 15))
like image 108
PM 2Ring Avatar answered Jul 28 '26 21:07

PM 2Ring


Another rather simple possibility: Generate all the combinations, but only keep those where some random variable is < 0.1 to get (roughly) 10% of the resulting combinations.

>>> sum(1 for _ in itertools.combinations(range(100), 3)) # total count for comparison
161700
>>> res = [c for c in itertools.combinations(range(100), 3) if random.random() < 0.1]
>>> len(res)
16227

Compared with using random.sample, this has the advantage that it does not need to keep all the combinations in memory, although it will still generate all the combinations, but discard 90% of them immediately. Also, the result will be only roughly 10% of the combinations, but not exactly. For large numbers, that should not be too much of a problem, though.

like image 36
tobias_k Avatar answered Jul 28 '26 20:07

tobias_k



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!