如何使这两个关于循环笛卡尔积的替代方案不那么冗余?

How to make these two alternatives about looping on cartesian product less redundant?

在下面的函数f中,我们可以先循环a,也可以先循环b

如何让它的代码不那么冗余?

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)

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

def myfunction2(a, b):
    A = "%010i" % a               
    myfunction(a)
    print A, b

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

还是有点多余。

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

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 次。

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

您要排除的共性是在某个迭代器上调用 myfunction2(a, b)。但是第二个迭代器不仅反转了 product 参数,而且反转了每一对的元素。所以:

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 可以只是 return 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)

这很明显,您将 a 值作为 a 传递,将 b 值作为 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",这可能是可怕的矫枉过正,但如果您希望将其扩展到选择三个变量的不同排列,或动态列表中的任意顺序等., 这可能是值得的。

你可以映射到 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)]