Перебирайте пары в порядке суммы абсолютных значений

#python

Вопрос:

Я хочу перебрать пары целых чисел в порядке суммы их абсолютных значений. Список должен выглядеть так:

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

Это не позволяет мне повторять вечно, а также не дает мне по одной паре за раз.

Комментарии:

1. Можете ли вы показать нам, что вы пробовали до сих пор?

2. Существует бесконечно много пар int с абсолютной суммой 0…

3. @Марат да. Но я хочу, чтобы пары выводились в порядке суммы их абсолютных значений. Так что (-10,10) наступит довольно поздно.

4. Моя вина, я пропустил «абсолютную» часть

5. Вас волнует точный порядок, пока сохраняется сумма абсолютов?

Ответ №1:

Это, кажется, делает свое дело:

 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. Самый внутренний цикл может быть тривиально развернут с двумя выходами

2. @MadPhysicist: Хех, я был в процессе выяснения самого чистого способа сделать это (сейчас опубликовано). Все время пытался что-то переделать или переусердствовать (вычислять два b s независимо, когда они всегда отрицают друг друга , тестировать if b1 != b2: , когда тест проваливается только тогда, когда b1 есть 0 , и т. Д.). Это был долгий день.: -)

Ответ №2:

Это сделает это. Если вы действительно хотите, чтобы он был бесконечным, удалите первое 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)))
 

Комментарии:

1. itertools.count() будет аккуратнее, чем while True

2. В каком смысле аккуратнее? Если они хотят, чтобы это было бесконечно, тогда itertools.count() это не имеет смысла. —— Я вижу. Заменить base=0 и base = 1 с. itertools.count Я снимаю свое возражение.

3. Я дал свой собственный ответ, но он практически такой же, как этот, поэтому не заслуживает отдельного поста. Я просто оставлю его здесь на случай, если кому-то будет не все равно: pastebin.com/5cvGX7WZ

Ответ №3:

Приведенное ниже решение создает суммарный поток с кортежами любой длины:

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

Ответ №4:

Вы могли бы создать бесконечную функцию генератора:

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

Ответ №5:

Во-первых, обратите внимание, что вы можете расположить свои итоги в сетке для неотрицательных значений:

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

Ответ №6:

Для каждой суммы пройдитесь по диагонали в одном квадранте и поверните каждую координату в другие квадранты:

 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
 

Ответ №7:

(Надеюсь, я понял требования) Я использовал продукт 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)
...
 

Комментарии:

1. В этом есть три проблемы. Во-первых, он не будет генерировать бесконечно. Во-вторых, вы напрасно тратите усилия, создавая пары, которые не будут использоваться. В-третьих, ваши диапазоны, вероятно, должны быть (-4,5), а не (-5,4). Ты хочешь (-4,-3,-2,-1,0,1,2,3,4); ваш код этого не делает.

2. Проблема в том, что это не даст мне пар навсегда из-за sorted . Правка: Я согласен с Тимом Робертсом.