Python 查找列表中至少相隔x的最小n值

Python 查找列表中至少相隔x的最小n值,python,pandas,list,numpy,Python,Pandas,List,Numpy,我试图在一个列表中找到最小的n个值,其中它们的位置至少相隔x,考虑到重复。e、 g.最小5个值,彼此至少相距2 简单的例子: values = [-9995, -82, -659, -1006, -2009, 2062, -10107, -12, 13] result: [-10107, -9995, -2009, -659, 13] 更复杂的例子: values = [-9995, -83, -82, -82, -1006, -2009, 18, 2062, -659 ,-9995, -99

我试图在一个列表中找到最小的n个值,其中它们的位置至少相隔x,考虑到重复。e、 g.最小5个值,彼此至少相距2

简单的例子:

values = [-9995, -82, -659, -1006, -2009, 2062, -10107, -12, 13]
result: [-10107, -9995, -2009, -659, 13]
更复杂的例子:

values = [-9995, -83, -82, -82, -1006, -2009, 18, 2062, -659 ,-9995, -9995]
例如,在上述列表中:

  • -9995是最小值
  • -9995再次出现,并且与第一个相距至少2。其余的-9995将被忽略,因为它与上一个仅相差1
  • -2009年是第三小值
  • -不考虑1006,因为它与之前的值仅相差1。因此,我们取下一个最小的值-659,因为它与之前的值至少相差2(假设取第一个和最后一个-9995,忽略倒数第二个)
  • -83不被考虑,因为它距离-9995只有一个距离。所以我们选择-82
  • 我们已经到达了5个数字,所以我们停下来
  • 我正在处理的列表大约有1000000个元素,我有1000个列表。我从pandas数据帧生成了这些列表(通过对groupby进行迭代),因此如果有一种numpy/pandas方法来优化此计算,这将非常有用

    到目前为止,如果不发生重复,则尝试能够生成结果:

    
    def smallest_values(list_of_numbers: list, n_many: int, x_apart: int):
        
        sorted_values = sorted(values)
        small_val, small_val_loc = [], []
    
        for val in sorted_values:
            if len(small_val) <= n_many:
                ind = list_of_numbers.index(val)
                within_x = [i for i in range(ind-(x_apart-1), ind+x_apart)]
                if not any(i in small_val_loc for i in within_x):
                    small_val_loc.append(ind)     
                    small_val.append(val)
    
        return small_val
    
    values_simple = [-9995, -82, -659, -1006, -2009, 2062, -10107, -12, 13]
    values_complex = [-9995, -83, -82, -82, -1006, -2009, 18, 2062, -659 ,-9995, -9995]
    d = 2
    n = 5
    smallest_values(values_simple, n, d) # [-10107, -9995, -2009, -659, 13] CORRECT
    smallest_values(values_complex, n, d) # [-9995, -2009, -659, -82] INCORRECT
    
    
    def最小值(数字列表:列表,n个数:int,x个数:int):
    已排序的值=已排序的(值)
    small_val,small_val_loc=[],[]
    对于排序值中的val:
    
    如果len(small_val)这是一项复杂的工作,我们希望构建一个索引列表,其中第一个条目是数字列表中最小值的索引,并且该索引列表中的每个下一个条目都指向数字列表中的下一个最高值。操纵这种类型的列表将更加容易和高效。 我们可以做到以下几点:

    index_map=dict()
    for i in range(len(list_of_numbers)):
        value=list_of_numbers[i]
        if value in index_map:
            index_map[value]+=[i]
        else:
            index_map[value]=[i]
    sorted_values = sorted(index_map)
    
    现在我们有了一个字典,它包含了所有指向它的索引的数字列表中的每个唯一值。我们还有一个从最小的唯一值到最大的列表。现在,我们可以构建索引列表:

    index_list=[]
    for value in sorted_values:
        index_list+=index_map[value]
    
    del index_map, sorted_values
    
    剩下要做的就是在我们的index_列表中从左到右迭代,并找到具有适当间隔的第一个索引组合。这在算法中计算起来更容易、更快

    不幸的是,时间复杂度不可能小于O(n),因为您需要检查数字列表中的每个条目以找到最小的条目

    我使用递归函数实现了这一点,但您肯定可以对此进行优化,并使算法更智能:

    def gap_selecter(numlist, n_many, gap):
    
    if numlist==None:          # Fast exit if recursion fails
        return None
    
    x=numlist[0]
    speudolist=numlist[1:]
                        
    if n_many==1:              # base case
        return [x] 
                                
    
    else:
        for i in range(len(speudolist)):
            
            if abs(x-speudolist[i])>=gap:   #recursive step occurs here
                
                recursion_list = gap_selecter(speudolist[i:], n_many-1, gap)   
                
                if recursion_list !=None:
                    return [x]+recursion_list
    
    return None                # if we find no possible list we return None
    
    这是所有的东西

    def smallest_values(list_of_numbers: list, n_many: int, x_apart: int):
    
    index_map=dict()
    for i in range(len(list_of_numbers)):
        value=list_of_numbers[i]
        if value in index_map:
            index_map[value]+=[i]
        else:
            index_map[value]=[i]
    sorted_values = sorted(index_map)
    
    index_list=[]
    for value in sorted_values:
        index_list+=index_map[value]
    
    del index_map, sorted_values
    
    final_indices=gap_selecter(index_list, n_many, x_apart)
    if final_indices==None:
        return None
    
    final_numbers=[]
    for i in final_indices:
        final_numbers+=[list_of_numbers[i]]
    
    return final_numbers
    
    values_simple = [-9995, -82, -659, -1006, -2009, 2062, -10107, -12, 13]
    values_complex = [-9995, -83, -82, -82, -1006, -2009, 18, 2062, -659 ,-9995, -9995]
    d = 2
    n = 5
    
    test_simple = smallest_values(values_simple, n, d)       # [-10107, -9995, -2009, -659, -12]
    test_complex = smallest_values(values_complex, n, d)     # [-9995, -9995, -2009, -659, -83]
    

    //编辑:啊,现在我明白了。关键短语是
    (假设我们取第一个和最后一个-9995,忽略倒数第二个)

    最大的问题是您不能选择任何重复的值(在您的示例中是列表中的倒数第二个-9995)。相反,您希望选取重复的值,以便最后一个元素(或结果列表的总和?)最小,对吗


    对我来说,这听起来像是一个受限优化问题。我甚至不确定它是否具有相同的结果,这取决于您定义为“最优”的内容(总和或最后一个元素或其他内容……)

    这里的关键问题是打破重复值的联系,例如示例中的-9995。我们基本上需要尝试按不同的顺序挑选它们,并检查哪一个生成具有下一个较低值的序列(或者如果下一个值相同,则检查后面的一个值,依此类推)

    一种方法是使用递归搜索:

    from collections import defaultdict
    
    # find the next smallest and return all locations of that number
    # that can be used (i.e. not within d from the previously used values)
    def get_next(vs, vd, d, skip):
        for v in vs:
            os = []
            for l in vd[v]:
                if not any([l>x-d and l<x+d for x in skip]):
                    os.append((l, v))
            if len(os) > 0:
                return os
        return None
    
    # recursive search
    def r(vs, vd, n, d, skip=[], out=[]):
        if len(out) >= n:
            return out
        
        os = []
        for (l, v) in get_next(vs, vd, d, skip):
            o = r(vs, vd, n, d, skip+[l], out+[v])
            os.append(o)
        mo = min(os)
        return mo
    
    # main func
    def smallest_values(values, n, d):
        vd = defaultdict(list)
        for l, v in enumerate(values):
            vd[v].append(l)
        vs = sorted(vd.keys())
        return r(vs, vd, n, d, [], [])
    
    输出:

    simple:   [-10107, -9995, -2009, -659, 13]
    complex:  [-9995, -9995, -2009, -659, -82]
    
    CPU times: user 780 ms, sys: 20.8 ms, total: 800 ms
    Wall time: 800 ms
    [3, 5, 6, 7, 8]
    

    在1000000值列表上进行计时测试(800ms,因此对于1000个单线程列表,大约15分钟):

    输出:

    simple:   [-10107, -9995, -2009, -659, 13]
    complex:  [-9995, -9995, -2009, -659, -82]
    
    CPU times: user 780 ms, sys: 20.8 ms, total: 800 ms
    Wall time: 800 ms
    [3, 5, 6, 7, 8]
    


    另一方面,这将查找序列中较早时具有最低值的序列。例如,它更喜欢
    [1,2100]
    而不是
    [1,3,4]
    (两个序列的位置1都有1,但第一个序列的位置2有2<3)。IIUC这是预期的,根据您的评论
    我猜逻辑的措辞是,是否有一个先前选择的值的选择,允许选择下一个最小值。

    我不确定您试图优化的内容是否明确。考虑列表<代码> [ 1, 0, 1,300, 500, 400 ] < /代码> <代码> n=3 < /代码>。如果你从零开始,你会得到
    [0300400]
    ,但是如果你从一开始,你会得到
    [1400]
    。第一个数字最小,但第二个总数最小。哪个是正确答案?
    [0300400]
    将是我问题的正确答案。我不是在寻找最小的和,我想要最小的n个数,它们之间至少相隔x。谢谢该算法将从最小的数字开始,然后迭代地添加下一个最小的数字,该数字在列表中与之前添加的数字至少相隔x。因此,您是说您将始终获取下一个最小的数字,即使该选择迫使您稍后获取更大的数字?因此,在上面加上
    -1
    ,使
    n=4
    -
    [-1100,1,0,1300,500,400]
    ,正确的答案是
    [-1,0300400]
    而不是
    [-1,1,1400]
    ?是的,完全正确在更复杂的例子中,取最后一个-9995值而不是倒数第二个值的逻辑是什么(哪个值相同,但在列表中出现得更早)?
    test_complex
    失败,不是吗?它不应该有
    -83
    (如果我正确理解了这个问题)是的,很抱歉,在这个简单的示例中,它应该是
    [-10107,-9995,-2009,-659,13]
    -12紧挨着-10107,正如perl提到的那样,不应该选择-83。您的方法非常聪明,非常感谢。您对问题陈述的理解是正确的,
    [1,2100]
    应该更喜欢酷,很高兴它有帮助!这是一个非常有趣的问题:)
    CPU times: user 780 ms, sys: 20.8 ms, total: 800 ms
    Wall time: 800 ms
    [3, 5, 6, 7, 8]