使用i>迭代多个索引j(> k)以pythonic方式

时间:2017-01-07 13:02:18

标签: python python-3.x itertools

我需要迭代一个索引元组。所有指数必须在范围内 条件为[0, N)的{​​{1}}。我在这里介绍的玩具示例涉及 只有两个指数;我需要将其扩展为三个(i > j)或更多。

基本版本是:

i > j > k

它工作得很好;输出是

N = 5
for i in range(N):
    for j in range(i):
        print(i, j)

我不希望每个附加索引都有一个缩进级别, 因此我更喜欢这个版本:

1 0
2 0
2 1
3 0
3 1
3 2
4 0
4 1
4 2
4 3

这非常有效,做它应该做的并且摆脱额外的 缩进级别。

我希望能够拥有更优雅的东西(对于两个指数而言) 并非所有相关的,但对于三个或更多,它变得更相关)。到目前为止我想出的是:

for i, j in ((i, j) for i in range(N) for j in range(i)):
    print(i, j)

这很好地迭代了同一对索引。唯一的是 不同的是对出现的顺序:

from itertools import combinations

for j, i in combinations(range(N), 2):
    print(i, j)

由于我对这些指数的处理顺序是相关的,因此我不能使用它。

是否有一种优雅,简短,pythonic的方式来迭代这些索引的顺序与第一个例子产生的顺序相同?请记住,1 0 2 0 3 0 4 0 2 1 3 1 4 1 3 2 4 2 4 3 会很大,因此排序不是我想要做的事情。

5 个答案:

答案 0 :(得分:17)

你可以解决这个问题,如下所示:

def indices(N, length=1):
    """Generate [length]-tuples of indices.

    Each tuple t = (i, j, ..., [x]) satisfies the conditions 
    len(t) == length, 0 <= i < N  and i > j > ... > [x].

    Arguments:
      N (int): The limit of the first index in each tuple.
      length (int, optional): The length of each tuple (defaults to 1).

    Yields:
      tuple: The next tuple of indices.

    """
    if length == 1:
       for x in range(N):
           yield (x,)
    else:
       for x in range(1, N):
            for t in indices(x, length - 1):
                yield (x,) + t

使用中:

>>> list(indices(5, 2))
[(1, 0), (2, 0), (2, 1), (3, 0), (3, 1), (3, 2), (4, 0), (4, 1), (4, 2), (4, 3)]
>>> list(indices(5, 3))
[(2, 1, 0), (3, 1, 0), (3, 2, 0), (3, 2, 1), (4, 1, 0), (4, 2, 0), (4, 2, 1), (4, 3, 0), (4, 3, 1), (4, 3, 2)]

答案 1 :(得分:6)

如果你不介意丢弃大部分生成的元组的效率低下,你可以使用product中的itertools。 (随着repeat参数的增加,效率低下。)

>>> from itertools import product
>>> for p in ((i,j) for (i,j) in product(range(5), repeat=2) if i > j):
...   print p
...
(1, 0)
(2, 0)
(2, 1)
(3, 0)
(3, 1)
(3, 2)
(4, 0)
(4, 1)
(4, 2)
(4, 3)
>>> for p in ((i,j,k) for (i,j,k) in product(range(5), repeat=3) if i > j > k):
...   print p
...
(2, 1, 0)
(3, 1, 0)
(3, 2, 0)
(3, 2, 1)
(4, 1, 0)
(4, 2, 0)
(4, 2, 1)
(4, 3, 0)
(4, 3, 1)
(4, 3, 2)

更新:使用索引作为过滤器,而不是元组解包。这允许代码更紧凑地编写。对于不同大小的元组,只需要更改my_filter

from itertools import product, ifilter
def my_filter(p):
    return p[0] > p[1] > p[2]

for p in ifilter(my_filter, product(...)):
    print p

答案 2 :(得分:6)

以下是一种itertools.combinations具有通用数量的级别的方法 -

map(tuple,(N-1-np.array(list(combinations(range(N),M))))[::-1])

或者用同样的方法扭曲一下 -

map(tuple,np.array(list(combinations(range(N-1,-1,-1),M)))[::-1])

,其中N:元素数量和M:级别数。

示例运行 -

In [446]: N = 5
     ...: for i in range(N):
     ...:     for j in range(i):
     ...:         for k in range(j):  # Three levels here
     ...:             print(i, j, k)
     ...:             
(2, 1, 0)
(3, 1, 0)
(3, 2, 0)
(3, 2, 1)
(4, 1, 0)
(4, 2, 0)
(4, 2, 1)
(4, 3, 0)
(4, 3, 1)
(4, 3, 2)

In [447]: N = 5; M = 3

In [448]: map(tuple,(N-1-np.array(list(combinations(range(N),M))))[::-1])
Out[448]: 
[(2, 1, 0),
 (3, 1, 0),
 (3, 2, 0),
 (3, 2, 1),
 (4, 1, 0),
 (4, 2, 0),
 (4, 2, 1),
 (4, 3, 0),
 (4, 3, 1),
 (4, 3, 2)]

答案 3 :(得分:2)

这是一种基于观察的方法,它更容易生成所需顺序(反向)的索引的否定它类似于@Divakar的方法,就像那样有一个缺点,就是要求在内存中创建列表:

def decreasingTuples(N,k):
    for t in reversed(list(itertools.combinations(range(1-N,1),k))):
        yield tuple(-i for i in t)

>>> for t in decreasingTuples(4,2): print(t)

(1, 0)
(2, 0)
(2, 1)
(3, 0)
(3, 1)
(3, 2)
>>> for t in decreasingTuples(4,3): print(t)

(2, 1, 0)
(3, 1, 0)
(3, 2, 0)
(3, 2, 1)

答案 4 :(得分:-1)

使用eval进行某种“hacky”尝试(只是为了完整性添加此内容。这里有更好的答案!)。

这个想法是构建一个像

这样的字符串
'((a, b, c) for a in range(5) for b in range(a) for c in range(b))'

并返回该eval

def ijk_eval(n, depth):
    '''
    construct a string representation of the genexpr and return eval of it...
    '''

    var = string.ascii_lowercase
    assert len(var) >= depth > 1  # returns int and not tuple if depth=1

    for_str = ('for {} in range({}) '.format(var[0], n) +
               ' '.join('for {} in range({})'.format(nxt, cur)
                        for cur, nxt in zip(var[:depth-1], var[1:depth])))
    return eval('(({}) {})'.format(', '.join(var[:depth]), for_str))

可以这种方式使用并产生正确的结果。

for i, j in ijk_eval(n=5, depth=2):
    print(i, j)

构造不是很好 - 但结果是:它是常规的genexpr并且效率与那些一样高。