Прерывистый фрагмент в списке python

#python #performance #list #slice

#python #Производительность #Список #фрагмент

Вопрос:

Я ищу эффективный способ достижения этого, который, я думаю, представляет собой операцию, подобную нарезке:

 >>> mylist = range(100)
>>>magicslicer(mylist, 10, 20)
[0,1,2,3,4,5,6,7,8,9,30,31,32,33,34,35,36,37,38,39,60,61,62,63......,97,98,99]
  

идея такова: при нарезке получается 10 элементов, затем пропускается 20 элементов, затем получаются следующие 10, затем пропускаются следующие 20 и так далее.

Я думаю, что мне не следует использовать циклы, если это возможно, по самой причине использования slice (я полагаю), чтобы эффективно выполнять «извлечение» за одну операцию.

Спасибо за чтение.

Ответ №1:

itertools.compress (новое в 2.7 / 3.1) прекрасно поддерживает подобные варианты использования, особенно в сочетании с itertools.cycle :

 from itertools import cycle, compress
seq = range(100)
criteria = cycle([True]*10   [False]*20) # Use whatever pattern you like
>>> list(compress(seq, criteria))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
  

Синхронизация Python 2.7 (относительно явного понимания списка Sven):

 $ ./python -m timeit -s "a = range(100)" "[x for start in range(0, len(a), 30) for x in a[start:start 10]]"
100000 loops, best of 3: 4.96 usec per loop

$ ./python -m timeit -s "from itertools import cycle, compress" -s "a = range(100)" -s "criteria = cycle([True]*10   [False]*20)" "list(compress(a, criteria))"
100000 loops, best of 3: 4.76 usec per loop
  

Синхронизация Python 3.2 (также относительно явного понимания списка Sven):

 $ ./python -m timeit -s "a = range(100)" "[x for start in range(0, len(a), 30) for x in a[start:start 10]]"
100000 loops, best of 3: 7.41 usec per loop

$ ./python -m timeit -s "from itertools import cycle, compress" -s "a = range(100)" -s "criteria = cycle([True]*10   [False]*20)" "list(compress(a, criteria))"
100000 loops, best of 3: 4.78 usec per loop
  

Как можно видеть, это не имеет большого значения по сравнению с пониманием встроенного списка в 2.7, но значительно помогает в 3.2, избегая накладных расходов, связанных с неявной вложенной областью.

Аналогичное различие также можно увидеть в 2.7, если целью является повторение результирующей последовательности, а не превращение ее в полностью реализованный список:

 $ ./python -m timeit -s "a = range(100)" "for x in (x for start in range(0, len(a), 30) for x in a[start:start 10]): pass"
100000 loops, best of 3: 6.82 usec per loop
$ ./python -m timeit -s "from itertools import cycle, compress" -s "a = range(100)" -s "criteria = cycle([True]*10   [False]*20)" "for x in compress(a, criteria): pass"
100000 loops, best of 3: 3.61 usec per loop
  

Для особенно длинных шаблонов можно заменить список в выражении шаблона выражением типа chain(repeat(True, 10), repeat(False, 20)) , чтобы его никогда не приходилось полностью создавать в памяти.

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

1. Приятно! Обратите внимание, что явное понимание списка все равно будет быстрее, если вы пропускаете много значений, поскольку compress() приходится перебирать их все, в то время как понимание списка действительно пропускает их.

2. Верно, хотя обратное преимущество compress() подхода заключается в том, что он работает с произвольными итерациями, даже с теми, которые не поддерживают нарезку.

3. О, боже, я создал «вопрос монстра» : o) Этот ответ — именно то, что я искал, поскольку я услышал волшебные слова «C level». Мне скоро нужно будет это протестировать. Кроме того, этот приятный способ использования python прямо из командной строки является новым для меня. Спасибо.

Ответ №2:

Возможно, лучший способ — это прямой подход:

 def magicslicer(seq, take, skip):
    return [x for start in range(0, len(seq), take   skip)
              for x in seq[start:start   take]]
  

Я не думаю, что вы можете избежать циклов.

Редактировать: поскольку это помечено как «производительность», здесь приведено сравнение с решением по модулю для a = range(100) :

 In [2]: %timeit [x for start in range(0, len(a), 30)
                   for x in a[start:start   10]]
100000 loops, best of 3: 4.89 us per loop

In [3]: %timeit [e for i, e in enumerate(a) if i % 30 < 10]
100000 loops, best of 3: 14.8 us per loop
  

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

1. отлично! Теперь у нас также есть простой и полезный пример timeit использования! Спасибо!

2. itertools.compress позволяет снизить цикл до уровня C и выполняется так же быстро, как понимание списка, и быстрее, чем выражение генератора в 2.7, и быстрее, чем оба в 3.2 (из-за неявной вложенной области видимости в версиях 3.x).

Ответ №3:

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

 >>> a = range(100)
>>> a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 
 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
    ...
 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
>>> [e for i, e in enumerate(a) if i % 30 < 10]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 
 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
 60, 61, 62, 63, 64, 65, 66, 67, 68, 69,
 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
  

Ответ №4:

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

Для сравнения:

 import numpy as np
from itertools import cycle, compress

startList = list(range(0, 3000))
startNpArray = np.linspace(0,2999,3000,dtype=np.int)

def WithNumpy(seq, keep, skip):
    return seq.reshape((-1, keep skip))[:,:keep 1].flatten()

def WithItertools(seq, keep, skip):
    criteria = cycle([True]*keep   [False]* skip)
    return list(compress(seq, criteria))

%timeit WithNumpy(startListNp, 10, 20)
>>> 2.59 µs ± 48.7 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit WithItertools(startList, 10, 20)
>>> 33.5 µs ± 911 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
  

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

1. Мастерское использование изменения формы и нарезки! Очень поучительно!

Ответ №5:

Я бы использовал цикл:

 #!/usr/bin/env python


def magicslicer(l, stepsize, stepgap):
    output = []
    i = 0
    while i<len(l):
        output  = l[i:i stepsize]
        i  = stepsize   stepgap
    return output


mylist = range(100)
print magicslicer(mylist,10,20)
  

Ответ №6:

 >>>[mylist[start:start 10] for start in mylist[::30]]
>>>[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [30, 31, 32, 33, 34, 35, 36, 37, 38, 39], [60, 61, 62, 63, 64, 65, 66, 67, 68, 69], [90, 91, 92, 93, 94, 95, 96, 97, 98, 99]]
  

но я получаю список list 🙁

Ответ №7:

 mylist = range(100)

otherlist = ['21','31','689','777','479','51','71','yut','poi','ger',
             '11','61','789','zozozozo','8888','1']



def magic_slicer(iterable,keep,throw):
        it = iter(iterable).next
        for n in xrange((len(iterable)//keep throw) 1):
                for i in xrange(keep):  yield it()
                for i in xrange(throw):  it()

print list(magic_slicer(mylist,10,20))
print
print list(magic_slicer(otherlist,2,3))


print '__________________'


def magic_slicer2(iterable,keep,throw):
        return ( x for i,x in enumerate(iterable) if -1< i%(keep throw)<keep) 

print list(magic_slicer2(mylist,10,20))
print
print list(magic_slicer2(otherlist,2,3))
  

Результат

 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]

['21', '31', '51', '71', '11', '61', '1']
__________________
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]

['21', '31', '51', '71', '11', '61', '1']
  

Ответ №8:

[x for x in range(100) if x0 < 10] есть еще один способ сделать это. Но это может происходить медленно по мере увеличения размера списка.

Функция в тех же строках

 def magic_slice(n, no_elems, step):
    s = no_elems   step
    return [x for x in range(n) if x%s < no_elems]
  

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

1. Люди, предлагающие понимание списков, находятся на правильном пути. Вы правы в том, что цикл необходим, но понимание списка помещает цикл внутрь движка Python, где он выполняется быстрее, чем явный цикл.