0

Suppose I have the following 2 arbitrary classes:

class C1():
    def __init__(self, a):
        self.a = a
class C2():
    def __init__(self, a):
        self.a = a

And the following parameters:

c1_args = [1, 2, 3, 4, 5]
c2_args = [1, 2, 3]

I want to run a function (let's call it do_something()) n times, taking as arguments every possible pair of instances of (C1,C2) based on the parameters provided. To do this, I created a nested loop, as shown below:

for x in c1_args:
    c1_instance = C1(x)
    for y in c2_args:
        c2_instance = C2(y)
        do_something(c1_instance, c2_instance)

Now, I want to be able to generalize it for any number of classes (and parameters). For that, a simple - yet highly inefficient - way to do it would be to use itertools.product(), which I adapted from here:

import itertools
def generalize(classes, args):
    combos = itertools.product(*args)
    for combo in combos:
        instances = [classes[i](args[i]) for i in range(len(combo))]
        do_something(instances)

This is highly inefficient because it must create the same instance multiple times - in my example above, C1(1) must be created 3 times, once for every C2 instance created. That's irrelevant with the simple classes I provided above but with bigger classes it is heavily time consuming.

I suspect one solution would be recursion, based on what I found here. Unfortunately I can't seem to make it work for my case (mostly due to my ignorance on recursion - apologies for that)

Mike
  • 102
  • 6
  • 1
    How would recursion save you from creating new class instances? – inspectorG4dget Sep 29 '20 at 14:49
  • this sounds more like a design problem than anything – gold_cy Sep 29 '20 at 14:51
  • I suspected recursion might allow me to dynamically create the nested loops based on the Anti Earth's question I linked - but I'm honestly not too familiar with the concept of recursion @inspectorG4dget – Mike Sep 29 '20 at 15:00

1 Answers1

1

If you create the instances first before looping throug the combinations, you can insure that the instances are only created once for each item in the lists:

from itertools import product

class C1():
    def __init__(self, a):
        print("making c1:", a)
        self.a = a
    def __repr__(self):
        return f"C1({self.a})"

class C2():
    def __init__(self, a):
        print("making c2:", a)
        self.a = a
        
    def __repr__(self):
        return f"C2({self.a})"

c1_args = map(C1, [1, 2, 3, 4, 5])
c2_args = map(C2, [1, 2, 3])


for comb in product(c1_args, c2_args):
    print(comb)

The prints show each __init__() being called once and then the various combinations:

making c1: 1
making c1: 2
making c1: 3
making c1: 4
making c1: 5
making c2: 1
making c2: 2
making c2: 3
(C1(1), C2(1))
(C1(1), C2(2))
(C1(1), C2(3))
(C1(2), C2(1))
(C1(2), C2(2))
(C1(2), C2(3))
(C1(3), C2(1))
(C1(3), C2(2))
(C1(3), C2(3))
(C1(4), C2(1))
(C1(4), C2(2))
(C1(4), C2(3))
(C1(5), C2(1))
(C1(5), C2(2))
(C1(5), C2(3))
Mark
  • 90,562
  • 7
  • 108
  • 148