pythonfunctional-programmingchinese-remainder-theorem

Functional python -- why does only one of these generators require list() to work?


In computing the Chinese Remainder theorem from a vector of tuples (residue, modulus) the following code fails :

c = ((1,5),(3,7),(11,13),(19,23))

def crt(c):
        residues, moduli = zip(*c)
        N = product(moduli)
        complements = (N/ni for ni in moduli)
        scaled_residues = (product(pair) for pair in zip(residues,complements))
        inverses = (modular_inverse(*pair) for pair in zip(complements,moduli))
        si = (product(u) for u in zip(scaled_residues,inverses))
        result = sum(si) % N
        return result

Giving the result as 0 ( I guess the generated iterables are empty ). Yet the following code works perfectly :

def crt(c):
        residues, moduli = zip(*c)
        N = product(moduli)
        complements = list((N/ni for ni in moduli)) # <-- listed
        scaled_residues = (product(pair) for pair in zip(residues,complements))
        inverses = (modular_inverse(*pair) for pair in zip(complements,moduli))
        si = (product(u) for u in zip(scaled_residues,inverses))
        result = sum(si) % N
        return result

Which yields (a) correct result of 8851.

Why should I have to list( one of the first generators? Adding list to any subsequent generator does not change the fail (0) result. Only listing this first generator produces the correct result. What is going on here ?


Solution

  • You iterate twice over complements. You can only iterate once over a generator expression.

    If you are on Python 2.x, zip(residues,complements) will consume complements and there is nothing left for zip(complements,moduli). On Python 3.x zip is a generator itself and the problem appears later in the code, when sum() actually runs the generators. It would pull two items from complements for each iteration.