Quiero iterar sobre pares de enteros en el orden de la suma de sus valores absolutos. La lista debería verse así:
(0,0) (-1,0) (0,1) (0,-1) (1,0) (-2,0) (-1,1) (-1,-1) (0,2) (0,-2) (1,1) (1,-1) (2,0) [...]Para pares con la misma suma de valores absolutos, no me importa en qué orden vienen.
Idealmente, me gustaría poder crear los pares para siempre para poder usar cada uno por turno. Como puedes hacer eso?
Para un rango fijo puedo hacer la lista de pares de forma fea con:
sorted([(x,y)for x in range(-20,21)for y in range(-20,21)if abs(x)+abs(y)<21],key=lambda x:sum(map(abs,x))Esto no me permite iterar para siempre y tampoco me da un par a la vez.
Esto lo hará. Si realmente desea que sea infinito, elimine la primera instrucción if .
import itertools def makepairs(count=3): yield (0,0) for base in itertools.count(1): if base > count: # optional escape return # optional escape for i in range(base+1): yield (i, base-i) if base != i: yield (i, i-base) if i: yield (-i, base-i) if base != i: yield (-i, i-base) print(list(makepairs(9)))(Espero haber entendido los requisitos) Usé el producto itertools :
>>> for i in sorted(itertools.product(range(-5, 4), range(-5, 4)), key=lambda tup: abs(tup[0]) + abs(tup[1])): print(i) ... (0, 0) (-1, 0) (0, -1) (0, 1) (1, 0) (-2, 0) (-1, -1) (-1, 1) (0, -2) (0, 2) (1, -1) (1, 1) (2, 0) (-3, 0) (-2, -1) (-2, 1) (-1, -2) (-1, 2) (0, -3) (0, 3) (1, -2) (1, 2) (2, -1) ...Esto parece hacer el truco:
from itertools import count # Creates infinite iterator def abs_value_pairs(): for absval in count(): # Generate all possible sums of absolute values for a in range(-absval, absval + 1): # Generate all possible first values b = abs(a) - absval # Compute matching second value (arbitrarily do negative first) yield a, b if b: # If first b is zero, don't output again, otherwise, output positive b yield a, -bEsto se ejecuta para siempre y funciona de manera eficiente (evitando volver a calcular nada innecesariamente).
La siguiente solución produce un flujo de suma con tuplas de cualquier longitud:
from itertools import count def pairs(l = 2): def groups(d, s, c = []): if not d and sum(map(abs, c)) == s: yield tuple(c) elif d: for i in [j for k in d[0] for j in {k, -1*k}]: yield from groups(d[1:], s, c +[i]) for i in count(): yield from groups([range(i+1) for _ in range(l)], i) p = pairs() for _ in range(10): print(next(p))Podrías hacer una función generadora infinita:
def pairSums(s = 0): # base generation on target sum to get pairs in order while True: # s parameter allows starting from a given sum for i in range(s//2+1): # partitions yield from {(i,si),(si,i),(is,-i),(-i,is)} # no duplicates s += 1 # next target sumProducción:
for p in pairSums(): print(p) (0, 0) (0, 1) (0, -1) (1, 0) (-1, 0) (2, 0) (-2, 0) (0, -2) (0, 2) (1, 1) (-1, -1) (3, 0) (0, 3) (0, -3) (-3, 0) (1, 2) (-1, -2) (2, 1) ...Primero observe que puede colocar sus totales en una cuadrícula para valores no negativos:
x 3|3 2|23 1|123 0|0123 -+---- |0123yAquí podemos ver un patrón donde las diagonales son tus totales. Tracemos una línea sistemática a través de ellos. A continuación se muestra un orden en el que podría caminar a través de ellos:
x 3|6 2|37 1|148 0|0259 -+---- |0123yAquí la matriz contiene el orden de las iteraciones.
Esto resuelve su problema para valores no negativos de x e y. Para obtener el resto, simplemente puede negar x e y, asegurándose de no hacerlo cuando sean cero. Algo como esto:
def generate_triplets(n): yield 0, (0, 0) for t in range(1, n + 1): # Iterate over totals t for x in range(0, t + 1): # Iterate over component x y = t - x # Calclulate component y yield t, (x, y) # Default case is non-negative if y > 0: yield t, (x, -y) if x > 0: yield t, (-x, y) if x > 0 and y > 0: yield t, (-x, -y) def generate_pairs(n): yield from (pair for t, pair in generate_triplets(n)) # for pair in generate_pairs(10): # print(pair) for t, (x, y) in generate_triplets(3): print(f'{t} = abs({x}) + abs({y})')Esto produce
0 = abs(0) + abs(0) 1 = abs(0) + abs(1) 1 = abs(0) + abs(-1) 1 = abs(1) + abs(0) 1 = abs(-1) + abs(0) 2 = abs(0) + abs(2) 2 = abs(0) + abs(-2) 2 = abs(1) + abs(1) 2 = abs(1) + abs(-1) 2 = abs(-1) + abs(1) 2 = abs(-1) + abs(-1) 2 = abs(2) + abs(0) 2 = abs(-2) + abs(0) 3 = abs(0) + abs(3) 3 = abs(0) + abs(-3) 3 = abs(1) + abs(2) 3 = abs(1) + abs(-2) 3 = abs(-1) + abs(2) 3 = abs(-1) + abs(-2) 3 = abs(2) + abs(1) 3 = abs(2) + abs(-1) 3 = abs(-2) + abs(1) 3 = abs(-2) + abs(-1) 3 = abs(3) + abs(0) 3 = abs(-3) + abs(0)O en parejas:
(0, 0) (0, 1) (0, -1) (1, 0) (-1, 0) (0, 2) (0, -2) (1, 1) (1, -1) (-1, 1) (-1, -1) (2, 0) (-2, 0) ...Para cada suma, recorra la diagonal en un cuadrante y rote cada coordenada en los otros cuadrantes:
from itertools import count def coordinates(): yield 0, 0 for sum in count(1): for x in range(sum): y = sum - x yield x, y yield y, -x yield -x, -y yield -y, x