Corte discontinuo en la lista de python

Estoy buscando una forma eficiente de lograr esto, lo que creo que es una operación similar a un corte en rodajas:

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

la idea es: la división obtiene 10 elementos, luego salta 20 elementos, luego obtiene los 10 siguientes, luego salta los 20 siguientes, y así sucesivamente.

Creo que no debería usar bucles si es posible, por la misma razón que para usar slice es (supongo) hacer la “extracción” de manera eficiente en una sola operación.

Gracias por leer.

    itertools.compress (nuevo en 2.7 / 3.1) es compatible con casos de uso como este, especialmente cuando se combina con 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] 

    Tiempo de Python 2.7 (relativo a la comprensión explícita de la lista de 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 

    Tiempo de Python 3.2 (también en relación con la comprensión explícita de la lista de 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 

    Como puede verse, no hace una gran diferencia en relación con la comprensión de la lista en línea en 2.7, pero ayuda significativamente en 3.2 al evitar la sobrecarga del scope nested implícito.

    Una diferencia similar también se puede ver en 2.7 si el objective es recorrer la secuencia resultante en lugar de convertirla en una lista completa:

     $ ./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 

    Para patrones especialmente largos, es posible reemplazar la lista en la expresión de patrón con una expresión como chain(repeat(True, 10), repeat(False, 20)) para que nunca tenga que crearse completamente en la memoria.

    Tal vez la mejor manera es el enfoque directo:

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

    No creo que puedas evitar los bucles.

    Edición : ya que está etiquetado como “rendimiento”, aquí hay una comparación con la solución de módulo para 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 

    Creo que las rebanadas no pueden hacerlo, por desgracia. Solucionaría el problema usando listas de comprensión.

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

    No sé si está trabajando solo con números, pero en caso de que lo esté, hay una manera más rápida si se adhiere a los números. Pero lo siguiente solo funcionará si tiene una lista que consta de sublistas de igual longitud que se aplanaron.

    Para comparacion:

     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) 

    Yo usaría un bucle:

     #!/usr/bin/env python def magicslicer(l, stepsize, stepgap): output = [] i = 0 while i 
     >>>[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]] 

    Pero obtengo una lista de la lista 🙁

     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) 

    resultado

     [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'] 

    [x for x in range(100) if x%30 < 10] es otra forma de hacerlo. Pero, esto puede ser lento a medida que crece el tamaño de la lista.

    Una función en las mismas líneas.

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