У меня есть список, состоящий из 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

Это работает для примера, но может быть более эффективный способ, который охватывает более крайние случаи.

1
Imran 27 Авг 2017 в 13:33

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]
6
MSeifert 27 Авг 2017 в 11:26

Вы заявили, что действительно заинтересованы в очень быстром решении. В случае критической производительности вы можете использовать тип расширения 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 раза.

4
MSeifert 27 Авг 2017 в 14:25

Это работает:

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
2
Karan Elangovan 27 Авг 2017 в 11:06

Другое решение, использующее 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)
1
FabienP 27 Авг 2017 в 16:33

Вот способ сделать это, используя 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]
2
mhawke 27 Авг 2017 в 11:54

Вот векторизованное решение -

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
3
Divakar 27 Авг 2017 в 13:42
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])))

Как насчет этого списка понимания одной строки.

-2
whackamadoodle3000 27 Авг 2017 в 14:47