Итерация по нескольким индексам с i> j (> k) в питоническом виде

Мне нужно перебирать кортеж индексов. все индексы должны находиться в диапазоне [0, N) с условием 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) 

Это итерации по одной и той же паре индексов просто прекрасны. Единственное отличие – порядок, в котором появляются пары:

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

Поскольку порядок того, что я делаю с этими индексами, уместен, поэтому я не могу это использовать.

Есть ли элегантный, короткий, питонический способ перебора этих индексов в том же порядке, что и в первом примере? Имейте в виду, что N будет большим, поэтому сортировка – это не то, что я хотел бы сделать.

5 Solutions collect form web for “Итерация по нескольким индексам с i> j (> k) в питоническом виде”

Вы можете решить это в целом следующим образом:

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

Вот такой подход с 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)] 

Вы можете использовать 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 

Это подход, основанный на наблюдении, что легче генерировать отрицательные значения индексов в (обратном) желаемом порядке. Он похож на подход @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) 

несколько «взломанная» попытка использования eval (просто добавив это для полноты. здесь есть более приятные ответы!).

идея состоит в том, чтобы построить строку типа

 '((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 и такой же эффективный, как и те, что есть.

Python - лучший язык программирования в мире.