如何使关于笛卡尔积的循环的这两种选择减少多余?

时间:2018-05-25 22:03:28

标签: python loops cartesian-product

在以下函数Form中,我们可以先f循环,也可以先a循环。

如何减少代码冗余?

b

我在考虑product,但我们仍然会:

def myfunction(a):
    pass

def f(first_loop_on_a=True):
    if first_loop_on_a:
        for a in range(10):
            A = "%010i" % a               
            myfunction(a)
            for b in range(5):
                print A, b
    else:
        for b in range(5):
            for a in range(10):
                A = "%010i" % a
                myfunction(a)
                print A, b

f(True)
f(False)

仍然有点多余。

3 个答案:

答案 0 :(得分:1)

首先使用product值的来源b时,使用生成器表达式来翻转元组如何:

def f(first_loop_on_a=True):
    if first_loop_on_a:
        gen = product(range(10, range(5))
    else:
        gen = (a, b for b, a in product(range(5), range(10)))

    for a, b in gen:
        myfunction2(a, b)

我会注意到这仍然与原始函数不同,因为在原始函数中,myfunction在两个分支之间被调用了不同的次数(10次或50次)。新函数总是在内循环中调用它(通过myfunction2),因此它总是运行50次。

答案 1 :(得分:1)

如果你的功能过于重复,你可以一步一步地重构。

您想要分解的共性是在某些迭代器上调用myfunction2(a, b)。但是第二个迭代器不仅反转product args,而且每个对的元素也是如此。所以:

def f(first_loop_on_a=True):
    if first_loop_on_a:
        prod = product(range(10), range(5))
    else:
        prod = (b, a for (a, b) in product(range(5), range(10)))
    for a, b in prod:
        myfunction2(a, b)

如果您多次执行此操作,则可以将元组翻转到函数中:

def flippair(p):
    a, b = p
    return b, a
def f(first_loop_on_a=True):
    if first_loop_on_a:
        prod = product(range(10), range(5))
    else:
        prod = map(flippair, product(range(5), range(10)))
    for a, b in prod:
        myfunction2(a, b)

(或者,当然,flippair可以返回p[::-1] - 或者,因为您不需要元组,但只需要任何类型的可迭代,只需使用reversed。但这种方式似乎更明确,而且仍然很容易简洁。)

但我认为最好的解决方案是使用myfunction的关键字参数:

def kwify(order, pairs):
    return (dict(zip(order, pair)) for pair in pairs)
def f(first_loop_on_a=True):
    if first_loop_on_a:
        prod = kwify('ab', product(range(10), range(5)))
    else:
        prod = kwify('ba', product(range(5), range(10)))
    for kwpair in prod:
        myfunction2(**kwpair)

这很明显,您将aab值作为b传递,而不是将它们翻转,以便它们最终成为ba,然后将它们翻转以按相反的顺序传递它们。

虽然我们正在努力,为什么要重复这些范围?

def kwify(order, pairs):
    return (dict(zip(order, pair)) for pair in pairs)
def f(first_loop_on_a=True):
    arange, brange = range(10), range(5)
    if first_loop_on_a:
        prod = kwify('ab', product(arange, brange))
    else:
        prod = kwify('ba', product(brange, arange))
    for kwpair in prod:
        myfunction2(**kwpair)

...此时您还可以提供他们名称:

def kwify(order, pairs):
    return (dict(zip(order, pair)) for pair in pairs)
def f(first_loop_on_a=True):
    ranges = {'a': range(10), 'b': range(5)}
    order = 'ab' if first_loop_on_a else 'ba'
    prod = kwify(order, product(*itemgetter(*order)(ranges)))
    for kwpair in prod:
        myfunction2(**kwpair)

...或者,甚至可能将对range的调用分解为:

def kwify(order, pairs):
    return (dict(zip(order, pair)) for pair in pairs)
def f(first_loop_on_a=True):
    ranges = {'a': 10, 'b': 5}
    order = 'ab' if first_loop_on_a else 'ba'
    prod = kwify(order, product(*map(range, itemgetter(*order)(ranges))))
    for kwpair in prod:
        myfunction2(**kwpair)

这可能只是选择" a-then-b"与" b-then-a",但如果你想扩展到选择三个变量的不同排列,或动态列表中的任意顺序等,那么它可能值得做。

答案 2 :(得分:0)

您可以通过reversed

进行映射
>>> import itertools as it
>>> 
>>> def itr(A, B, a_first=True):
...     return it.product(*map(range, (A, B))) if a_first else map(reversed, it.product(*map(range, (B, A))))
... 
>>> [(a, b) for a, b in itr(2, 3, True)]
[(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2)]
>>> [(a, b) for a, b in itr(2, 3, False)]
[(0, 0), (1, 0), (0, 1), (1, 1), (0, 2), (1, 2)]