Эффективный способ найти индекс повторяющейся последовательности в списке?

#python #python-3.x

#python #python-3.x

Вопрос:

У меня есть большой список чисел в python, и я хочу написать функцию, которая находит разделы списка, где одно и то же число повторяется более n раз. Например, если n равно 3, то моя функция должна возвращать следующие результаты для следующих примеров:

При применении к example = [1,2,1,1,1,1,2,3] функция должна возвращать [(2,6)] , потому что example[2:6] — это последовательность, содержащая одно и то же значение.

При применении к example = [0,0,0,7,3,2,2,2,2,1] функция должна возвращать [(0,3), (5,9)], потому что оба примера [0:3] и пример [5:9] содержат повторяющиеся последовательности одного и того же значения.

При применении к example = [1,2,1,2,1,2,2,1,2,1,2] функция должна возвращать [], потому что нет последовательности из трех или более элементов с одинаковым числом.

Я знаю, что мог бы написать кучу циклов, чтобы получить то, что я хочу, но это кажется неэффективным, и мне было интересно, есть ли более простой вариант получить то, что я хотел.

Комментарии:

1. Пожалуйста, укажите код, с которым вы пробовали.

2. На самом деле вы хотите найти параметры среза , в которых повторяются разделы списка, а не индексы. Вы бы сбились на единицу.

3. Второй пример либо неверен, либо определение n неверно. В начале есть 3 нуля, поэтому ноль не повторяется более n раз .

Ответ №1:

Используйте itertools.groupby и enumerate :

 >>> from itertools import groupby
>>> n = 3
>>> x = [1,2,1,1,1,1,2,3] 
>>> grouped = (list(g) for _,g in groupby(enumerate(x), lambda t:t[1]))
>>> [(g[0][0], g[-1][0]   1) for g in grouped if len(g) >= n]
[(2, 6)]
>>> x = [0,0,0,7,3,2,2,2,2,1]
>>> grouped = (list(g) for _,g in groupby(enumerate(x), lambda t:t[1]))
>>> [(g[0][0], g[-1][0]   1) for g in grouped if len(g) >= n]
[(0, 3), (5, 9)]
  

Чтобы понять groupby: просто поймите, что каждая итерация возвращает значение ключа, который используется для группировки элементов итерации, вместе с новым lazy-iterable, который будет повторяться по группе.

 >>> list(groupby(enumerate(x), lambda t:t[1]))
[(0, <itertools._grouper object at 0x7fc90a707bd0>), (7, <itertools._grouper object at 0x7fc90a707ad0>), (3, <itertools._grouper object at 0x7fc90a707950>), (2, <itertools._grouper object at 0x7fc90a707c10>), (1, <itertools._grouper object at 0x7fc90a707c50>)]
  

Комментарии:

1. Спасибо, это определенно намного эффективнее, чем то, что я делал. Я хотел бы понять, как именно это работает. Я понимаю, что делают enumerate(x) и ключевая функция, но что именно возвращает groupby? Почему нам нужна вторая половина, а не первая? После вызова этой функции я начинаю теряться.

2. Аккуратный. Вероятно, наиболее эффективно, если вам нужны все группы, но мне интересно, не будет ли это медленнее, если вам не нужны все подпоследовательности сразу и вы будете удовлетворены их генератором.

3. @FilipMalczak Если я вас правильно понял, то этого легко достичь, заменив понимание списка в конце еще одним выражением генератора.

4. Да, в принципе, но вся groupby(…) выполняется до понимания. Я думаю, вы могли бы обернуть его в другой генератор, используемый в понимании, или даже встроить его. Мне просто интересно, не будет ли какое-то решение, которое даст каждую подпоследовательность, как только она будет найдена, немного быстрее.

5. @FilipMalczak Да, это должно быть выполнение двух проходов по списку, но если вы обернете его в генератор, вы можете выжать из него последний бит эффективности.

Ответ №2:

Вы можете сделать это за один цикл, следуя текущему алгоритму:

 def find_pairs (array, n):
    result_pairs = []
    prev = idx = 0
    count = 1
    for i in range (0, len(array)):
        if(i > 0):
            if(array[i] == prev):
                count  = 1
            else:
                if(count >= n):
                    result_pairs.append((idx, i))
                else:
                    prev = array[i]
                    idx = i
                count = 1
        else:
            prev = array[i]
            idx = i
    return result_pairs
  

И вы вызываете функцию следующим образом: find_pairs(list, n) . Это наиболее эффективный способ выполнить эту задачу, поскольку она имеет сложность O (len (array)). Я думаю, это довольно просто понять, но если у вас есть какие-либо сомнения, просто спросите.

Комментарии:

1. Это хороший ответ, в котором представлены «классические» методы императивного программирования. При этом вы должны исправить свой отступ.

2. Этот ответ не будет работать, если последовательность заканчивается в конце массива. Алгоритм в его нынешнем виде не обнаружит разницы с предыдущим элементом и не перейдет в блок else. Он также будет считаться неправильно, если есть две монотонные подпоследовательности подряд. В этом случае он начнет считать один индекс слишком поздно в последней подпоследовательности.

Ответ №3:

Вы могли бы использовать это. Обратите внимание, что ваш вопрос неоднозначен относительно роли n . Я предполагаю, что здесь должна быть сопоставлена серия из n равных значений. Если он должен иметь не менее n 1 значений, то замените >= на > :

 def monotoneRanges(a, n):
    idx = [i for i, v in enumerate(a) if not i or a[i-1] != v]   [len(a)]
    return [r for r in zip(idx, idx[1:]) if r[1] >= r[0] n]

# example call
res = monotoneRanges([0,0,0,7,3,2,2,2,2,1], 3)

print(res)
  

Выводит:

 [(0, 3), (5, 9)]
  

Комментарии:

1. Хорошо, но вам нужно иметь дело с крайним случаем: monotoneRanges([0,0,0,7,3,2,2,2,2,1,0], 3)

2. @juanpa.arrivillaga, спасибо, что заметили это. Я думаю, что я исправил это с добавленным not i условием.