按绝对值之和的顺序迭代对。

7
我希望能按照两个整数的绝对值之和的顺序迭代这些整数对。列表应该像这样:
(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)
[...]

对于绝对值之和相同的一对数,它们的顺序无关紧要。

理想情况下,我希望能够永远创建这些数对,以便我可以轮流使用每一个。你如何做到这一点?

对于固定范围,我可以用以下方式不太美观地制作数对列表:

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))

这不允许我永远迭代,也不会一次给我一个 pair。

1
你能展示一下你目前尝试过的吗? - sphennings
4
有无限多个整数对,它们的绝对值之和为0... - Marat
1
@Marat 是的。但我希望输出的对按照它们绝对值之和的顺序排列。因此,(-10,10)会相对较晚出现。 - Simd
我的错,我错过了“绝对”的部分。 - Marat
2
只要绝对值的总和保持不变,您是否关心确切的顺序? - Mad Physicist
显示剩余2条评论
7个回答

9

这似乎能解决问题:

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, -b

这将永远运行,并高效地运作(避免不必要的重新计算)。


1
最内层循环可以轻松地展开为两个yield。 - Mad Physicist
@MadPhysicist:嘿,我正在努力找出最简洁的方法来做它(现在已发布)。一直试图重新做或过度做某些事情(独立计算两个“ b”时它们总是彼此否定,测试“ if b1!= b2:”时测试仅在“ b1”为“ 0”时失败等)。今天真的很长。 :-) - ShadowRanger

4
这样就可以了。如果你真想让它无限,删除第一个 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)))

4
itertools.count()while True更加简洁。 - Marat
1
更整洁的方式是什么?如果他们希望它是无限的,那么 itertools.count() 就没有作用了。我明白了。用 itertools.count 替换 base=0base += 1。我撤回我的反对。 - Tim Roberts
我自己写了一个答案,但实际上与这个答案几乎相同,所以不值得单独发布。我会把它放在这里,以防有人关心:https://pastebin.com/5cvGX7WZ - Oli

2
以下解决方案生成一个包含任意长度元组的求和流:
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))

2

您可以创建一个无限生成器函数:

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,s-i),(s-i,i),(i-s,-i),(-i,i-s)} # no duplicates
        s += 1                                             # next target sum

输出:

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)
...

2

首先请注意,您可以将非负值的总数放在网格中:

x
3|3
2|23
1|123
0|0123
-+----
 |0123y

这里我们可以看到一个模式,其中对角线是您的总数。让我们通过一些系统化的线路来跟踪它们。以下是您可以按顺序遍历它们的方式:

x
3|6
2|37
1|148
0|0259
-+----
 |0123y

这里的矩阵包含迭代顺序。

这可以解决x和y为非负数的问题。要获得其余部分,您只需对x和y取相反数,确保它们不为零即可。可以像这样实现:

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})')

这将输出

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)

或成为一对:

(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)
...

1
对于每个总和,沿着一个象限的对角线行走,并将每个坐标旋转到其他象限:
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

-1

(我希望我理解了要求)我使用了itertools product

>>> 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)
...

1
这有三个问题。第一,它不会无限生成。第二,你正在浪费努力生成不会使用的对。第三,你的范围应该是(-4,5),而不是(-5,4)。你想要的是(-4,-3,-2,-1,0,1,2,3,4),但你的代码没有实现这一点。 - Tim Roberts
1
问题在于由于 sorted,这将不会永远给我配对。编辑:我同意Tim Roberts的观点。 - Simd

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接