Here's one for performance using array-slicing, similar to @piRSquared's second solution but without any appending/concatenation -
a = np.array(series)
out = np.flatnonzero((a[2:] == a[1:-1]) & (a[1:-1] != a[:-2]))+1
Sample run -
In [28]: a = np.array(series)
In [29]: np.flatnonzero((a[2:] == a[1:-1]) & (a[1:-1] != a[:-2]))+1
Out[29]: array([ 5, 11, 17, 21])
Runtime test (for working solutions)
Approaches -
def piRSquared1(series):
d = np.flatnonzero(np.diff(series) == 0)
w = np.append(True, np.diff(d) > 1)
return d[w].tolist()
def piRSquared2(series):
s = np.array(series)
return np.flatnonzero(
np.append(s[:-1] == s[1:], True) &
np.append(True, s[1:] != s[:-1])
).tolist()
def Zach(series):
s = pd.Series(series)
i = [g.index[0] for _, g in s.groupby((s != s.shift()).cumsum()) if len(g) > 1]
return i
def jezrael(series):
s = pd.Series(series)
s1 = s.shift(1).ne(s).cumsum()
m = ~s1.duplicated() & s1.duplicated(keep=False)
s2 = m.index[m].tolist()
return s2
def divakar(series):
a = np.array(series)
x = a[1:-1]
return (np.flatnonzero((a[2:] == x) & (x != a[:-2]))+1).tolist()
For the setup, we are simply tiling the sample input a number of times.
Timings -
Case #1 : Large set
In [34]: series0 = [2,3,7,10,11,16,16,9,11,12,14,16,16,16,5,7,9,17,17,4,8,18,18]
In [35]: series = np.tile(series0,10000).tolist()
In [36]: %timeit piRSquared1(series)
...: %timeit piRSquared2(series)
...: %timeit Zach(series)
...: %timeit jezrael(series)
...: %timeit divakar(series)
...:
100 loops, best of 3: 8.06 ms per loop
100 loops, best of 3: 7.79 ms per loop
1 loop, best of 3: 3.88 s per loop
10 loops, best of 3: 24.3 ms per loop
100 loops, best of 3: 7.97 ms per loop
Case #2 : Much larger set (on top 2 solutions)
In [40]: series = np.tile(series0,1000000).tolist()
In [41]: %timeit piRSquared2(series)
1 loop, best of 3: 823 ms per loop
In [42]: %timeit divakar(series)
1 loop, best of 3: 823 ms per loop
Now, those two solutions differ only in the way appending is avoided in the latter one. Let's take a closer look at them and run on a smaller dataset -
In [43]: series = np.tile(series0,100).tolist()
In [44]: %timeit piRSquared2(series)
10000 loops, best of 3: 89.4 µs per loop
In [45]: %timeit divakar(series)
10000 loops, best of 3: 82.8 µs per loop
Thus, it reveals that the concatenation/append avoiding in the latter solution helps a lot when dealing with smaller datasets, but at much larger datasets, they become comparable.
Marginal improvement on larger dataset is possible with one concatenation there. Thus, the last step could be re-written as :
np.flatnonzero(np.concatenate(([False],(a[2:] == a[1:-1]) & (a[1:-1] != a[:-2]))))