使用pandas dataframe执行此操作的最佳方法是什么?我想遍历一个数据帧,找到最接近+/- 2值差异的最近的下一个索引。例如:[100,99,102,98,103,103]将使用此[2,2,3,0,N / A]创建一个新列,0表示找不到。
我的解决方案性能是n * log(n)。任何才华横溢的人都可以向我展示更好的性能解决方案吗?
答案 0 :(得分:1)
当所有元素都是整数时,可以在线性时间内完成。以下解决方案很复杂,并且只有算法兴趣(如果在那里)。由于它使用循环和数据结构,任何真正的实现都需要在C / C ++ / Cython中(否则,常量会如此之高,以至于需要非常长的序列来开始看到改进,即使它和#39 ; s线性)。
由于解决方案很复杂,我首先会做一些简化的假设,然后展示如何摆脱它们。最初的假设是:
需要的是找到下一个位置为2或更大的索引。
所有整数都是不同的。
考虑到这些假设,可以使用一个众所周知的面试问题的变体(它是如此常见,我认为它是民间传说)。我们的想法是保持数组的位置堆栈,其中尚未找到下一个位置。循环遍历元素和位置时,将保持循环不变量:
堆栈中的索引正在增加。
堆栈不包含 i , j 的位置,以便 a [i] + 2< = a [j] 和 i< Ĵ。
不变量最初是非常满意的,我将展示它们是如何维护的。
在迭代 j 中说,堆栈的顶部是 i :我将此标记为< ...,i> ; (堆栈向右移动)。当 a [j]> = a [i] + 2 时,我们可以弹出堆栈并将 i 的下一个位置设置为 j 。当发生这种情况时,我们可以弹出堆栈直到条件失败。然而,有些人认为,堆栈可以是< ...,k,i> ,其中 a [i] + 2>一个[j]的。关于不变量的一些想法足以看出,在这种情况下,如果堆栈中有一个需要弹出的元素,它必须是 k (如果它存在)。这是唯一需要检查的项目 - 在最后一项之前的任何其他项目都不能是需要弹出的项目。所以,我们只需要检查 k ,并在必要时弹出它。在迭代结束时,我们只需要推送 j 本身。
以下代码执行此操作:
def plus2_detector(a, verbose=False):
if verbose:
print 'starting with', a
remaining, out = [], [None] * len(a)
for i, e in enumerate(a):
if verbose:
print 'it begin', i, e, remaining
while remaining and e >= a[remaining[-1]] + 2:
if verbose:
print 'setting', i, remaining[-1], a[remaining[-1]]
out[remaining[-1]] = i
del remaining[-1]
if len(remaining) > 1 and e >= a[remaining[-2]] + 2:
if verbose:
print 'back setting', i, remaining[-2], a[remaining[-2]]
out[remaining[-2]] = i
del remaining[-2]
remaining.append(i)
if verbose:
print 'it end', i, e, remaining
return out
您可以运行它,例如
>>> plus2_detector([1, 2, 3, 5, 4, -1, -2, 10, 9, 8, 7, 11], False)
[2, 3, 3, 7, 7, 7, 7, None, 11, 11, 11, None]
为了直观地了解它的功能,您可以使用verbose=True
在不同的(不同的整数!)上运行它,看看它的作用。
现在摆脱简化。
第一个简化设置很容易:运行此算法的两个副本:一个检查> = 2 ,另一个检查< = -2 ,以及结合结果。
第二次简化谜语更加棘手。问题是如果堆栈的顶部不需要弹出,我们可能需要搜索许多项目以查看是否需要弹出任何人 - 这个潜在的项目不一定是真的在顶部。如果堆栈顶部的元素相同,则会发生这种情况。
处理这个问题很乏味,但概念上并不困难。堆栈现在需要包含等效元素的连续欠盐指数的整数列表。这意味着当您推送新索引时,您需要检查它是否继续运行。如果是,请将其附加到顶部的列表中;如果没有,创建一个新的元组。现在所有连续等效的未定位项都被组合在一起(类似于itertools.groupby
所做的)。
存在技术上的复杂情况(当弹出倒数第二个列表时,我们可能需要结合顶部和新的倒数第二个元组),但这个想法是一样的。
使用摊销分析的标准参数,复杂性是线性的(每个元素都插入并弹出一次,非弹出操作是常量)。
以下是查找+2或以上索引的一般情况的代码,没有元素唯一的限制:
def general_plus2_detector(a, verbose=False):
if verbose:
print 'starting with', a
remaining, out = [], [None] * len(a)
for i, e in enumerate(a):
if verbose:
print 'it begin', i, '(', e, ')', remaining
while remaining and e >= a[remaining[-1][0]] + 2:
for j in remaining[-1]:
if verbose:
print 'setting', j, '(', a[j], ') to', i, '(', a[i], ')'
out[j] = i
del remaining[-1]
if len(remaining) > 1 and e >= a[remaining[-2][0]] + 2:
for j in remaining[-2]:
if verbose:
print 'back setting', j, '(', a[j], ') to', i, '(', a[i], ')'
out[j] = i
del remaining[-2]
if len(remaining) > 1 and a[remaining[-2][0]] == a[remaining[-1][0]]:
if verbose:
print 'joining', remaining[-2], remaining[-1]
remaining[-1].extend(remaining[-2])
del remaining[-2]
if not remaining or a[remaining[-1][0]] != e:
remaining.append([i])
else:
remaining[-1].append(i)
if verbose:
print 'it end', i, '(', e, ')', remaining
return out
运行它显示:
a = [-1, -2, 3, 2, 2, 3, 2, 2, 4, 5, 4, 5, 2, 3, 4, 5, 5, 4, 4, 7]
>>> general_plus2_detector(a, False)
[2, 2, 9, 8, 8, 9, 8, 8, 19, 19, 19, 19, 14, 15, 19, 19, 19, 19, 19, None]