Генерация циклических перестановок / уменьшенных латинских квадратов в Python

просто интересно, какой самый эффективный способ генерации всех циклических перестановок списка в Python. В любом направлении. Например, задан список [1, 2, 3, 4], Я хочу создать либо:

[[1, 2, 3, 4],
 [4, 1, 2, 3],
 [3, 4, 1, 2],
 [2, 3, 4, 1]]

где следующая перестановка генерируется путем перемещения последнего элемента вперед, или:

[[1, 2, 3, 4],
 [2, 3, 4, 1],
 [3, 4, 1, 2],
 [4, 1, 2, 3]]

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

второй случай немного интереснее для меня потому что это приводит к уменьшению латинского квадрата (первый случай также дает латинский квадрат, просто не уменьшается), который я пытаюсь использовать для экспериментального проектирования блоков. На самом деле это не так уж отличается от первого случая, поскольку они просто переупорядочивают друг друга, но порядок все еще имеет значение.

текущая реализация, которую я имею для первого случая:

def gen_latin_square(mylist):
    tmplist = mylist[:]
    latin_square = []
    for i in range(len(mylist)):
        latin_square.append(tmplist[:])
        tmplist = [tmplist.pop()] + tmplist
    return latin_square

для второго случая его:

def gen_latin_square(mylist):
    tmplist = mylist[:]
    latin_square = []
    for i in range(len(mylist)):
        latin_square.append(tmplist[:])
        tmplist = tmplist[1:] + [tmplist[0]]
    return latin_square

первый случай кажется, что это должно быть достаточно эффективен для меня, так как он использует pop(), но вы не можете сделать это во втором случае, поэтому я хотел бы услышать идеи о том, как сделать это более эффективно. Может быть, в этом что-то есть!--6--> что поможет? Или, может быть, двойная очередь для второго дела?

7 ответов


для первой части наиболее сжатым способом, вероятно, является

a = [1, 2, 3, 4]
n = len(a)
[[a[i - j] for i in range(n)] for j in range(n)]
# [[1, 2, 3, 4], [4, 1, 2, 3], [3, 4, 1, 2], [2, 3, 4, 1]]

и для второй части

[[a[i - j] for i in range(n)] for j in range(n, 0, -1)]
# [[1, 2, 3, 4], [2, 3, 4, 1], [3, 4, 1, 2], [4, 1, 2, 3]]

Они также должны быть намного эффективнее вашего кода, хотя я не делал никаких таймингов.


вы можете использовать коллекции.deque:

from collections import deque

g = deque([1, 2, 3, 4])

for i in range(len(g)):
    print list(g) #or do anything with permutation
    g.rotate(1) #for right rotation
    #or g.rotate(-1) for left rotation

он печатает:

 [1, 2, 3, 4]
 [4, 1, 2, 3]
 [3, 4, 1, 2]
 [2, 3, 4, 1]

чтобы изменить его для левого вращения, просто замените g.rotate(1) С g.rotate(-1).


вариация на нарезку "закон сохранения"a = a[:i] + a[i:]

ns = list(range(5))
ns
Out[34]: [0, 1, 2, 3, 4]

[ns[i:] + ns[:i] for i in range(len(ns))]
Out[36]: 
[[0, 1, 2, 3, 4],
 [1, 2, 3, 4, 0],
 [2, 3, 4, 0, 1],
 [3, 4, 0, 1, 2],
 [4, 0, 1, 2, 3]]


[ns[-i:] + ns[:-i] for i in range(len(ns))]
Out[38]: 
[[0, 1, 2, 3, 4],
 [4, 0, 1, 2, 3],
 [3, 4, 0, 1, 2],
 [2, 3, 4, 0, 1],
 [1, 2, 3, 4, 0]]

использование itertools, чтобы избежать индексирования:

x = itertools.cycle(a)
[[x.next() for i in a] for j in a]

это будет мое решение.

#given list
a = [1,2,3,4]
#looping through list
for i in xrange(len(a)):
    #inserting last element at the starting
    a.insert(0,a[len(a)-1])
    #removing the last element
    a = a[:len(a)-1]
    #printing if you want to
    print a

это выведет следующее:

[4, 1, 2, 3]
[3, 4, 1, 2]
[2, 3, 4, 1]
[1, 2, 3, 4]

вы также можете использовать pop вместо использования нарезки списка, но проблема с pop это то, что он что-то вернет.

также приведенный выше код будет работать для любой длины списка. Я не проверял производительность кода. Я предполагаю, что он будет работать лучше.

надеюсь, что это помогает. Если у вас есть какие-либо сомнения,вы можете свяжитесь со мной. I буду рад помочь вам.

вы должны взглянуть на Python docs для получения хорошего понимания нарезки списка.


ответ @Bruno Lenzi, похоже, не работает:

In [10]: from itertools import cycle

In [11]: x = cycle('ABCD')

In [12]: print [[x.next() for _ in range(4)] for _ in range(4)]
[['A', 'B', 'C', 'D'], ['A', 'B', 'C', 'D'], ['A', 'B', 'C', 'D'], ['A', 'B', 'C', 'D']]

Я даю правильную версию ниже, однако решение @f5r5e5d быстрее.

In [45]: def use_cycle(a):
    x=cycle(a)
    for _ in a:
        x.next()
        print [x.next() for _ in a]
   ....:         

In [46]: use_cycle([1,2,3,4])
[2, 3, 4, 1]
[3, 4, 1, 2]
[4, 1, 2, 3]
[1, 2, 3, 4]

In [50]: def use_slice(a):
    print [ a[n:] + a[:n] for n in range(len(a)) ]
  ....:     

In [51]: use_slice([1,2,3,4])
[[1, 2, 3, 4], [2, 3, 4, 1], [3, 4, 1, 2], [4, 1, 2, 3]]

In [54]: timeit.timeit('use_cycle([1,2,3,4])','from __main__ import use_cycle',number=100000)
Out[54]: 0.4884989261627197

In [55]: timeit.timeit('use_slice([1,2,3,4])','from __main__ import use_slice',number=100000)
Out[55]: 0.3103291988372803

In [58]: timeit.timeit('use_cycle([1,2,3,4]*100)','from __main__ import use_cycle',number=100)
Out[58]: 2.4427831172943115

In [59]: timeit.timeit('use_slice([1,2,3,4]*100)','from __main__ import use_slice',number=100)
Out[59]: 0.12029695510864258

Я удалил инструкцию печати в use_cycle и use_slice для целей синхронизации.


more_itertools является сторонней библиотекой, которая предлагает инструмент для циклические перестановки:

import more_itertools as mit


mit.circular_shifts(range(1, 5))
# [(1, 2, 3, 4), (2, 3, 4, 1), (3, 4, 1, 2), (4, 1, 2, 3)]

см. также Википедия:

круговой сдвиг-это особый вид циклической перестановки, который, в свою очередь, является особым видом перестановки.