Найти индексы повторяющихся элементов в массиве (Python, NumPy)

Предположим, у меня есть NumPy-массив целых чисел, как:

[34,2,3,22,22,22,22,22,22,18,90,5,-55,-19,22,6,6,6,6,6,6,6,6,23,53,1,5,-42,82]

Я хочу найти начальные и конечные индексы массива, где значение больше, чем x-раз (например, 5 раз) повторяется. Таким образом, в приведенном выше случае это значение 22 и 6. Начальный индекс повторного 22 равен 3, а конечный индекс - 8. То же самое для повторения 6. Есть ли в Python специальный инструмент? В противном случае я бы прошел через индекс массива индекса и сравнил фактическое значение с предыдущим.

С уважением.

Ответ 1

Используя np.diff и метод здесь от @WarrenWeckesser для поиска прогонов нулей в массиве:

import numpy as np

def zero_runs(a):  # from link
    iszero = np.concatenate(([0], np.equal(a, 0).view(np.int8), [0]))
    absdiff = np.abs(np.diff(iszero))
    ranges = np.where(absdiff == 1)[0].reshape(-1, 2)
    return ranges

a = [34,2,3,22,22,22,22,22,22,18,90,5,-55,-19,22,6,6,6,6,6,6,6,6,23,53,1,5,-42,82]

zero_runs(np.diff(a))
Out[87]: 
array([[ 3,  8],
       [15, 22]], dtype=int32)

Затем это можно отфильтровать по разности между началом и концом прогона:

runs = zero_runs(np.diff(a))

runs[runs[:, 1]-runs[:, 0]>5]  # runs of 7 or more, to illustrate filter
Out[96]: array([[15, 22]], dtype=int32)

Ответ 2

На самом деле для этого не очень хорошо. Вы можете сделать что-то вроде:

mult = 5
for elem in val_list:
    target = [elem] * mult
    found_at = val_list.index(target)

Я оставляю вам не обнаруженные исключения и более длительное обнаружение последовательности.

Ответ 3

Вот решение, использующее Python native itertools.

код

import itertools as it


def find_ranges(lst, n=2):
    """Return ranges for `n` or more repeated values."""
    groups = ((k, tuple(g)) for k, g in it.groupby(enumerate(lst), lambda x: x[-1]))
    repeated = (idx_g for k, idx_g in groups if len(idx_g) >=n)
    return ((sub[0][0], sub[-1][0]) for sub in repeated)

lst = [34,2,3,22,22,22,22,22,22,18,90,5,-55,-19,22,6,6,6,6,6,6,6,6,23,53,1,5,-42,82]    
list(find_ranges(lst, 5))
# [(3, 8), (15, 22)]

Испытания

import nose.tools as nt


def test_ranges(f):
    """Verify list results identifying ranges."""
    nt.eq_(list(f([])), [])
    nt.eq_(list(f([0, 1,1,1,1,1,1, 2], 5)), [(1, 6)])
    nt.eq_(list(f([1,1,1,1,1,1, 2,2, 1, 3, 1,1,1,1,1,1], 5)), [(0, 5), (10, 15)])
    nt.eq_(list(f([1,1, 2, 1,1,1,1, 2, 1,1,1], 3)), [(3, 6), (8, 10)])    
    nt.eq_(list(f([1,1,1,1, 2, 1,1,1, 2, 1,1,1,1], 3)), [(0, 3), (5, 7), (9, 12)])

test_ranges(find_ranges)

Этот пример захватывает пары (индекс, элемент) в lst, а затем группирует их по элементу. Сохраняются только повторяющиеся пары. Наконец, первая и последняя пары нарезаны, что дает (начало, конец) индексы из каждой повторяющейся группы.

См. также этот пост для определения диапазонов индексов с помощью itertools.groupby,

Ответ 4

Если вы ищете value повторение n раз в списке L, вы можете сделать что-то вроде этого:

def find_repeat(value, n, L):
    look_for = [value for _ in range(n)]
    for i in range(len(L)):
        if L[i] == value and L[i:i+n] == look_for:
            return i, i+n

Ответ 5

Вот относительно быстрое, безошибочное решение, которое также говорит вам, сколько копий было в перспективе. Некоторые из этого кода были заимствованы из решения KAL.

# Return the start and (1-past-the-end) indices of the first instance of
# at least min_count copies of element value in container l 
def find_repeat(value, min_count, l):
  look_for = [value for _ in range(min_count)]
  for i in range(len(l)):
    count = 0
    while l[i + count] == value:
      count += 1
    if count >= min_count:
      return i, i + count