Ответ 1
Я согласен с wim, что вы не можете сделать это, просто нарезая. Но вы можете сделать это только с одним пониманием списка:
>>> [x for i,x in enumerate(a) if i%n < m]
[1, 2, 6, 7]
Я пытаюсь получить значения 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]
Я согласен с wim, что вы не можете сделать это, просто нарезая. Но вы можете сделать это только с одним пониманием списка:
>>> [x for i,x in enumerate(a) if i%n < m]
[1, 2, 6, 7]
Нет, это невозможно с нарезкой. Нарезка поддерживает только начало, остановку и шаг - нет способа представить степпинг с "группами" размером более 1.
Короче говоря, нет, вы не можете. Но вы можете использовать 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)]
Есть и другие способы сделать это, все из которых имеют преимущества для некоторых случаев, но ни один из них не "просто нарезка".
Самое общее решение, вероятно, состоит в том, чтобы группировать ваш вход, нарезать группы, а затем сглаживать срезы. Одно из преимуществ этого решения заключается в том, что вы можете сделать это лениво, не создавая больших промежуточных списков, и вы можете сделать это для любого итеративного, включая ленивый итератор, а не только список.
# 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). Но я все еще не считаю это более простым, чем ваше первоначальное понимание, и это не просто нарезка.
В вопросе указывается массив, и если говорить о массивах NumPy, мы можем использовать несколько очевидных трюков NumPy и несколько не столь очевидных. Мы можем, конечно, использовать slicing
чтобы получить 2D-представление на вход при определенных условиях.
Теперь, основываясь на длине массива, позвольте называть его l
и m
, у нас будет три сценария:
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
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
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
С помощью 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)
Я понимаю, что рекурсия не популярна, но что-то вроде этой работы? Кроме того, неясно, добавляет ли рекурсия в микс только использование срезов.
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, пожалуйста, простите мое плохое понимание языка, если это не работает должным образом, хотя я его протестировал и, похоже, работает нормально.