У меня есть список, состоящий из 1 и 0, например.
[0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0]
Я хочу вывести еще один список такой же длины, где каждая запись представляет количество последовательных нулей, которые только что прошли, т.е. выходные данные для приведенного выше примера будут:
[0, 1, 2, 3, 4, 0, 0, 0, 0, 0, 1, 0]
Обратите внимание, что первая запись в списке вывода всегда будет 0
, и не имеет значения, какой будет последняя запись в списке ввода.
Что я пробовал до сих пор:
def zero_consecutive(input_list):
output = [0]
cons = 0
for i in input_list[:-1]:
if i == 0:
cons += 1
output.append(cons)
else:
cons = 0
output.append(cons)
return output
Это работает для примера, но может быть более эффективный способ, который охватывает более крайние случаи.
7 ответов
Вместо функции, которая append
все помещает в список, вы можете написать функцию генератора, а затем просто преобразовать ее в list
. В общем, это короче, а в большинстве случаев даже быстрее (при том же самом)!
def zero_consecutive(input_list):
yield 0
cons = 0
for i in input_list[:-1]:
if i == 0:
cons += 1
else:
cons = 0
yield cons
>>> list(zero_consecutive([0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0]))
[0, 1, 2, 3, 4, 0, 0, 0, 0, 0, 1, 0]
Вы заявили, что действительно заинтересованы в очень быстром решении. В случае критической производительности вы можете использовать тип расширения C, например, с Cython.
Я использую IPython, поэтому я просто использую Cythonmagic:
%load_ext cython
И пусть Cython скомпилирует этот класс итератора:
%%cython
cdef class zero_consecutive_cython(object):
cdef long cons
cdef object input_list
cdef int started
def __init__(self, input_list):
self.input_list = iter(input_list[:-1])
self.cons = 0
self.started = 0
def __iter__(self):
return self
def __next__(self):
if self.started == 0:
self.started = 1
return 0
item = next(self.input_list)
if item == 0:
self.cons += 1
else:
self.cons = 0
return self.cons
По сути, это то же самое, что и функция генератора, упомянутая в другом ответе, но гораздо быстрее:
import numpy as np
def zero_consecutive_numpy(input_list): # from https://stackoverflow.com/a/45905344/5393381
a = np.array(input_list)
idx = np.flatnonzero(a[1:] != a[:-1])+2
out = np.ones(a.size,dtype=int)
out[0] = 0
if len(idx)==0:
out = np.arange(a.size)
elif len(idx)==1:
out[idx[0]] = -a.size
np.cumsum(out, out=out)
out[out<0] = 0
else:
out[idx[0]] = 2-idx[1]
if len(idx)%2==1:
out[idx[-1]] = -a.size
out[idx[2:-1:2]] = 1-idx[3:-1:2] - idx[1:-3:2]
else:
out[idx[2::2]] = 1-idx[3::2] - idx[1:-2:2]
np.cumsum(out, out=out)
out[out<0] = 0
return out
def zero_consecutive_python(input_list): # from https://stackoverflow.com/a/45904440/5393381
yield 0
cons = 0
for i in input_list[:-1]:
if i == 0:
cons += 1
else:
cons = 0
yield cons
np.random.seed(0)
for n in [200, 2000, 20000, 100000]:
print(n)
a = np.repeat(np.arange(n)%2, np.random.randint(3,8,(n))).tolist()
%timeit list(zero_consecutive_python(a))
%timeit list(zero_consecutive_cython(a))
%timeit zero_consecutive_numpy(a)
Дает мне такой результат:
200
380 µs ± 13.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) # python
122 µs ± 1.06 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each) # cython
488 µs ± 7.35 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) # numpy
2000
3.49 ms ± 26.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) # python
1.07 ms ± 19.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) # cython
3.85 ms ± 288 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) # numpy
20000
42.9 ms ± 3.03 ms per loop (mean ± std. dev. of 7 runs, 10 loops each) # python
15 ms ± 778 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) # cython
33.9 ms ± 670 µs per loop (mean ± std. dev. of 7 runs, 10 loops each) # numpy
100000
199 ms ± 2.69 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) # python
77.8 ms ± 507 µs per loop (mean ± std. dev. of 7 runs, 10 loops each) # cython
173 ms ± 4.37 ms per loop (mean ± std. dev. of 7 runs, 10 loops each) # numpy
По крайней мере, на моем компьютере это может превзойти другие подходы в 2-3 раза.
Это работает:
def zero_consecutive(a):
y = []
for i, _ in enumerate(a):
#prevents a StopIteration error
if not(1 in a[:i]): y.append(i)
else:
index = next(j for j in range(i-1, -1, -1) if a[j])
y.append(i - index - 1)
return y
Другое решение, использующее numpy
и scipy
, для развлечения
import numpy as np
from scipy.ndimage.measurements import label
from scipy.ndimage.interpolation import shift
a = np.array([0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0])
a_zeros = a == 0
labels = label(a_zeros)[0]
for l in np.unique(labels):
a[labels == l] = a_zeros[labels == l].cumsum()
shift(a, 1, output=a)
>>> a
Out[1]:
array([0, 1, 2, 3, 4, 0, 0, 0, 0, 0, 1, 0])
И функция, если вы этого хотите.
def zero_consecutive(array):
a = array.copy()
a_zeros = a == 0
labels = label(a_zeros)[0]
for l in np.unique(labels):
a[labels == l] = a_zeros[labels == l].cumsum()
shift(a, 1, output=a)
return a
РЕДАКТИРОВАТЬ: улучшенная версия
Лучшая производительность.
import numpy as np
from scipy.ndimage.measurements import label
from scipy.ndimage.interpolation import shift
from scipy.ndimage.measurements import labeled_comprehension
def zero_consecutive(array):
def func(a, idx):
r[idx] = a.astype(bool).cumsum()
return True
r = np.zeros_like(array)
labels, nlabels = label(array == 0)
labeled_comprehension(labels, labels, np.arange(1, nlabels + 1), func, int, 0, pass_positions=True)
return shift(r, 1)
Вот способ сделать это, используя itertools.groupby
для обнаружения прогонов нулей:
from itertools import groupby
def zero_consecutive(input_list):
result = [0]
for k, values in groupby(input_list[:-1], bool):
len_values = len(list(values))
if k:
result.extend([0] * len_values)
else:
result.extend(range(1, len_values + 1))
return result
>>> zero_consecutive([0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0])
[0, 1, 2, 3, 4, 0, 0, 0, 0, 0, 1, 0]
Это группируется с использованием лямбда-выражения x == 0
в качестве ключа, чтобы ненулевые значения обрабатывались эквивалентно. Это означает, что функция будет работать для списков, которые содержат значения, отличные от 0 и 1, например:
>>> zero_consecutive([0, 0, 0, 0, 1, 2, 'a', 2, 1000, 0, 1, 0])
[0, 1, 2, 3, 4, 0, 0, 0, 0, 0, 1, 0]
Вот векторизованное решение -
def zero_consecutive_vectorized(input_list):
a = np.array(input_list)
idx = np.flatnonzero(a[1:] != a[:-1])+2
out = np.ones(a.size,dtype=int)
out[0] = 0
if len(idx)==0:
out = np.arange(a.size)
elif len(idx)==1:
out[idx[0]] = -a.size
np.cumsum(out, out=out)
out[out<0] = 0
else:
out[idx[0]] = 2-idx[1]
if len(idx)%2==1:
out[idx[-1]] = -a.size
out[idx[2:-1:2]] = 1-idx[3:-1:2] - idx[1:-3:2]
else:
out[idx[2::2]] = 1-idx[3::2] - idx[1:-2:2]
np.cumsum(out, out=out)
out[out<0] = 0
return out
Пробный прогон -
In [493]: a = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
In [494]: zero_consecutive_vectorized(a)
Out[494]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
In [495]: a = [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
In [496]: zero_consecutive_vectorized(a)
Out[496]: [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
In [497]: a = [0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0]
In [498]: zero_consecutive_vectorized(a)
Out[498]: [0, 1, 2, 3, 4, 0, 0, 0, 0, 0, 1, 0]
Тест во время выполнения
Время против решения @ MSeifert, которое, кажется, достаточно конкурирует с множеством зацикленных решений -
In [579]: n = 10000
In [580]: a = np.repeat(np.arange(n)%2, np.random.randint(3,8,(n))).tolist()
In [581]: %timeit list(zero_consecutive(a))
...: %timeit zero_consecutive_vectorized(a)
...:
100 loops, best of 3: 2.85 ms per loop
100 loops, best of 3: 1.96 ms per loop
In [582]: n = 60000
In [583]: a = np.repeat(np.arange(n)%2, np.random.randint(3,8,(n))).tolist()
In [584]: %timeit list(zero_consecutive(a))
...: %timeit zero_consecutive_vectorized(a)
...:
100 loops, best of 3: 17.2 ms per loop
100 loops, best of 3: 12 ms per loop
list(map(int,list(''.join(['0' if elem=='' else ''.join(map(str,list(range(len(elem)+1)))) for elem in str([0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0]).strip('[').strip(']').replace(', ','').split('1')])[0:-1])))
Как насчет этого списка понимания одной строки.
Похожие вопросы
Новые вопросы
python
Python - это многопарадигмальный, динамически типизированный, многоцелевой язык программирования. Он разработан для быстрого изучения, понимания и использования, а также для обеспечения чистого и единообразного синтаксиса. Обратите внимание, что Python 2 официально не поддерживается с 01.01.2020. Тем не менее, для вопросов о Python, связанных с версией, добавьте тег [python-2.7] или [python-3.x]. При использовании варианта Python (например, Jython, PyPy) или библиотеки (например, Pandas и NumPy) включите его в теги.