Выполнение нескольких значений при разрезании массива в Python

Я пытаюсь получить значения m при переходе через все n элементов массива. Например, при m = 2 и n = 5 и заданных

a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Я хочу получить

b = [1, 2, 6, 7]

Есть ли способ сделать это, используя нарезку? Я могу сделать это, используя понимание вложенного списка, но мне было интересно, есть ли способ сделать это, используя только индексы. Для справки, способ понимания списка:

 b = [k for j in [a[i:i+2] for i in range(0,len(a),5)] for k in j]

Ответ 1

Я согласен с wim, что вы не можете сделать это, просто нарезая. Но вы можете сделать это только с одним пониманием списка:

>>> [x for i,x in enumerate(a) if i%n < m]
[1, 2, 6, 7]

Ответ 2

Нет, это невозможно с нарезкой. Нарезка поддерживает только начало, остановку и шаг - нет способа представить степпинг с "группами" размером более 1.

Ответ 3

Короче говоря, нет, вы не можете. Но вы можете использовать itertools для устранения необходимости в промежуточных списках:

from itertools import chain, islice

res = list(chain.from_iterable(islice(a, i, i+2) for i in range(0, len(a), 5)))

print(res)

[1, 2, 6, 7]

Заимствование логики @Kevin, если вы хотите, чтобы векторное решение избегало цикла for, вы можете использовать numbound library numpy:

import numpy as np

m, n = 2, 5
a = np.array(a)  # convert to numpy array
res = a[np.where(np.arange(a.shape[0]) % n < m)]

Ответ 4

Есть и другие способы сделать это, все из которых имеют преимущества для некоторых случаев, но ни один из них не "просто нарезка".


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

# from itertools recipes in the docs
def grouper(iterable, n, fillvalue=None):
    "Collect data into fixed-length chunks or blocks"
    # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    return itertools.zip_longest(*args, fillvalue=fillvalue)
groups = grouper(a, 5)
truncated = (group[:2] for group in groups)
b = [elem for group in truncated for elem in group]

И вы можете преобразовать это в довольно простой однострочный, хотя вам все еще нужна функция grouper:

b = [elem for group in grouper(a, 5) for elem in group[:2]]

Другой вариант - создать список индексов и использовать itemgetter для захвата всех значений. Это может быть более понятным для более сложной функции, чем просто "первые 2 из каждых 5", но, вероятно, менее читаемо для чего-то такого же простого, как ваше использование:

indices = [i for i in range(len(a)) if i%5 < 2]
b = operator.itemgetter(*indices)(a)

... который можно превратить в однострочный:

b = operator.itemgetter(*[i for i in range(len(a)) if i%5 < 2])(a)

И вы можете объединить преимущества двух подходов, написав собственную версию itemgetter которая занимает ленивый индексный итератор, который я не буду показывать, потому что вы можете пойти еще лучше, написав тот, который использует функцию фильтра индекса:

def indexfilter(pred, a):
    return [elem for i, elem in enumerate(a) if pred(i)]
b = indexfilter((lambda i: i%5<2), a)

(Чтобы сделать indexfilter ленивым, просто замените скобки на parens.)

... или, как однострочный:

b = [elem for i, elem in enumerate(a) if i%5<2]

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

Ответ 5

В вопросе указывается массив, и если говорить о массивах NumPy, мы можем использовать несколько очевидных трюков NumPy и несколько не столь очевидных. Мы можем, конечно, использовать slicing чтобы получить 2D-представление на вход при определенных условиях.

Теперь, основываясь на длине массива, позвольте называть его l и m, у нас будет три сценария:

Сценарий №1: l делится на n

Мы можем использовать slicing и reshaping, чтобы получить представление во входном массиве и, следовательно, получить постоянное время выполнения.

Проверьте концепцию представления:

In [108]: a = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])

In [109]: m = 2; n = 5

In [110]: a.reshape(-1,n)[:,:m]
Out[110]: 
array([[1, 2],
       [6, 7]])

In [111]: np.shares_memory(a, a.reshape(-1,n)[:,:m])
Out[111]: True

Проверьте тайминги на очень большом массиве и, следовательно, постоянное требование времени исполнения:

In [118]: a = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])

In [119]: %timeit a.reshape(-1,n)[:,:m]
1000000 loops, best of 3: 563 ns per loop

In [120]: a = np.arange(10000000)

In [121]: %timeit a.reshape(-1,n)[:,:m]
1000000 loops, best of 3: 564 ns per loop

Чтобы получить сплющенную версию:

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

In [127]: a = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])

In [128]: m = 2; n = 5

In [129]: a.reshape(-1,n)[:,:m].ravel()
Out[129]: array([1, 2, 6, 7])

Сроки показывают, что это не так уж плохо по сравнению с другими циклами и векторизованными версиями numpy.where из других сообщений -

In [143]: a = np.arange(10000000)

# @Kevin soln
In [145]: %timeit [x for i,x in enumerate(a) if i%n < m]
1 loop, best of 3: 1.23 s per loop

# @jpp soln
In [147]: %timeit a[np.where(np.arange(a.shape[0]) % n < m)]
10 loops, best of 3: 145 ms per loop

In [144]: %timeit a.reshape(-1,n)[:,:m].ravel()
100 loops, best of 3: 16.4 ms per loop

Сценарий № 2: l не делится на n, но группы заканчиваются полным в конце

Мы переходим к неочевидным методам NumPy с np.lib.stride_tricks.as_strided что позволяет перейти к границам блока памяти (поэтому нам нужно быть осторожным здесь, чтобы не писать в них), чтобы облегчить решение, используя slicing. Реализация будет выглядеть примерно так:

def select_groups(a, m, n):
    a = np.asarray(a)
    strided = np.lib.stride_tricks.as_strided

    # Get params defining the lengths for slicing and output array shape    
    nrows = len(a)//n
    add0 = len(a)%n
    s = a.strides[0]
    out_shape = nrows+int(add0!=0),m

    # Finally stride, flatten with reshape and slice
    return strided(a, shape=out_shape, strides=(s*n,s))

Образец запуска, чтобы проверить, что вывод является view -

In [151]: a = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13])

In [152]: m = 2; n = 5

In [153]: select_groups(a, m, n)
Out[153]: 
array([[ 1,  2],
       [ 6,  7],
       [11, 12]])

In [154]: np.shares_memory(a, select_groups(a, m, n))
Out[154]: True

Чтобы получить сглаженную версию, добавьте с .ravel().

Позвольте получить некоторое сравнение времени -

In [158]: a = np.arange(10000003)

In [159]: m = 2; n = 5

# @Kevin soln
In [161]: %timeit [x for i,x in enumerate(a) if i%n < m]
1 loop, best of 3: 1.24 s per loop

# @jpp soln
In [162]: %timeit a[np.where(np.arange(a.shape[0]) % n < m)]
10 loops, best of 3: 148 ms per loop

In [160]: %timeit select_groups(a, m=m, n=n)
100000 loops, best of 3: 5.8 µs per loop

Если нам нужна сплющенная версия, все равно не так уж плохо -

In [163]: %timeit select_groups(a, m=m, n=n).ravel()
100 loops, best of 3: 16.5 ms per loop

Сценарий № 3: l не делится на n, а группы заканчиваются неполным в конце

Для этого случая нам понадобится дополнительная нарезка в конце поверх того, что мы имели в предыдущем методе, например:

def select_groups_generic(a, m, n):
    a = np.asarray(a)
    strided = np.lib.stride_tricks.as_strided

    # Get params defining the lengths for slicing and output array shape    
    nrows = len(a)//n
    add0 = len(a)%n
    lim = m*(nrows) + add0
    s = a.strides[0]
    out_shape = nrows+int(add0!=0),m

    # Finally stride, flatten with reshape and slice
    return strided(a, shape=out_shape, strides=(s*n,s)).reshape(-1)[:lim]

Пример прогона -

In [166]: a = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])

In [167]: m = 2; n = 5

In [168]: select_groups_generic(a, m, n)
Out[168]: array([ 1,  2,  6,  7, 11])

Сроки -

In [170]: a = np.arange(10000001)

In [171]: m = 2; n = 5

# @Kevin soln
In [172]: %timeit [x for i,x in enumerate(a) if i%n < m]
1 loop, best of 3: 1.23 s per loop

# @jpp soln
In [173]: %timeit a[np.where(np.arange(a.shape[0]) % n < m)]
10 loops, best of 3: 145 ms per loop

In [174]: %timeit select_groups_generic(a, m, n)
100 loops, best of 3: 12.2 ms per loop

Ответ 6

С помощью itertools вы можете получить итератор с:

from itertools import compress, cycle

a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
n = 5
m = 2

it = compress(a, cycle([1, 1, 0, 0, 0]))
res = list(it)

Ответ 7

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

def get_elements(A, m, n):
    if(len(A) < m):
        return A
    else:
        return A[:m] + get_elements(A[n:], m, n)

A - массив, m и n определены как в вопросе. Первый, если охватывает базовый регистр, где у вас есть массив с длиной меньше числа элементов, которые вы пытаетесь получить, а второй, если это рекурсивный случай. Я немного новичок в python, пожалуйста, простите мое плохое понимание языка, если это не работает должным образом, хотя я его протестировал и, похоже, работает нормально.