【问题标题】:Python check if list is present in list of lists for detecting cyclesPython检查列表中是否存在用于检测循环的列表
【发布时间】:2021-02-25 22:01:46
【问题描述】:

我有大约 60000 个数据点的样本,并且在迭代算法中,在每个步骤中,根据某些标准,我要么“删除”(设置为 NaN)其中一个数据点,要么“添加”之前的一个删除的数据点回到样本中(设置回其原始值)。为了避免算法陷入死循环,每次迭代的样本应该总是不同的。因此,我会跟踪当前在每次迭代中删除的数据点,并将元素索引存储在列表中,如下所示:

  • 迭代 1:data_state_list = [[2]](删除了数组索引为 2 的元素)
  • 迭代 2:data_state_list = [[2],[2,3]](删除数组索引为 3 的元素)
  • 迭代 3:data_state_list = [[2],[2,3],[2,3,1]]
  • 迭代 4:data_state_list = [[2],[2,3],[2,3,1],[2,1]](重新添加数组索引为 3 的元素)
  • 迭代 5:data_state_list = [[2],[2,3],[2,3,1],[2,1],[2,1,4]]李>
  • 迭代 6:data_state_list = [[2],[2,3],[2,3,1],[2,1],[2,1,4],[2 ,1,4,3]]

现在在当前迭代 7 中,算法建议删除数组索引为 4 的元素,因此新状态 data_state_temp 将是 [2,1,3]。目前它通过

检查它是否已经看到了迄今为​​止的状态
flag_cycle = (data_state_temp in data_state_list)

算法检查新状态以添加/删除不同的数组元素,直到flag_cycleFalse,然后继续。

除此之外它还没有完全工作,因为迭代 7 中的状态 [2,1,3] 和迭代 3 中的 [2,3,1] 相同,但列表不同(需要排序它们或更好地将新删除的数组元素插入到它们应该属于排序列表的位置),问题是算法变得非常慢。在实践中,例如data_state_temp 的长度为 15000,data_state_list 有 40000 个列表,通常长度会增加到 15000。

问题:

  • 我们怎样才能加快循环/无限循环检查的速度?检查我们之前是否已经拥有相同状态的其他/概念上不同的方法非常好。
  • 在当前代码中,当 Python 检查data_state_temp 是否在data_state_list 中时,它是否只比较长度与data_state_temp 匹配的列表元素(我希望如此)还是我们需要手动选择事先列出这些清单?

【问题讨论】:

    标签: python list


    【解决方案1】:

    保留历史记录(固定查找)

    如果您不关心过去状态的顺序——只关心它以前曾被访问过——那么让我们收集所有过去状态的set。这给了我们一个固定的查找而不是线性查找in(例如for needle in haystack

    set 要发挥它的魔力,它必须使用可散列类型。简而言之,tuple 是不可变的,因此是可散列的,因此可以与set 一起使用。

    from itertools import chain
    
    # let's say we already have state that is 15000 long
    data_states = set()
    data_state = tuple(range(15000))
    data_states.add(data_state)
    
    # we loop until we decide on a new state
    while data_state in data_states:
        # either you decide to remove an element, say 42
        # (can skip sort because the previous tuple was already sorted)
        new_data_state = tuple(x for x in data_state if x != 42)
    
        # or you decide to add an element, 9000
        new_data_state = tuple(sorted(x for x in chain(data_state, [9000])))
    
        data_state = new_data_state
    
    # now commit this state to your history
    data_states.add(new_data_state)
    

    请注意,元组是不可变的,因此我们必须在创建元组之前先进行排序。还要注意sorted(_) 表单创建一个副本,而_.sort() 执行就地排序。由于我们将先前的状态解析为生成器,因此后一种排序是不可能的,因为我们没有内存。所以选择前者。然后将其输入tuple() 构造函数。

    运行时复杂度

    给定len(state) = n

    new_data_state = tuple(sorted(x for x in chain(data_state, [9000])))
    
    1. x for x in ...:具有n 操作的生成器
    2. sorted(...):创建一个列表,填充 n 条目
    3. tuple(...):创建一个元组,填充 n 条目

    步骤 1+2 发生在 n 的一次传递中。然后对第 3 步重复 n。每次迭代 state 时,总运行时复杂度为 O{2n}

    问:如何加速?

    我在上面列出的内容。如果您记忆力不足,您可以考虑使用更紧凑的表示 state 的形式。

    例如,您可以将state 分块为由5000 连续数字组成的3 块。然后使用嵌套字典。例如。 data_states[(x for x in range(5000)][(x for x in range(5000)] == <all the remaining digits>

    通过这种方式,空间成本被摊销,使得具有15777 数字的元素的增量空间成本仅为15777 - 5000 - 5000 = 5777 数字。这基本上就是字典的作用。

    问:它会按长度比较列表元素吗?

    是的,它执行长度匹配作为短路False 的第一个检查。但是不,它仍然处理每个元素。

    【讨论】:

    • bisect.insort 的好处是您正在处理已排序的数据,因此可以使用二进制搜索在 O{log(n)} 中查找元素。但请记住,您刚刚执行了一个完整的副本,即O{n}。使用我的方法,复制和过滤发生在O{n} 的同一通道中。归根结底,您将通过基准测试来了解哪个更快,因为它们具有相同的运行时复杂性顺序。
    • 我会说你的方法充其量和我的一样快,但可能更慢。原因是bisect.insort 基本上正在执行我的代码正在执行的操作(使用过滤器制作完整副本)。但是,您的方法还会在 bisect.insort 之前执行完整副本,而我的方法没有。
    • @bproxauf 会导致 O{3n + log(n)}: n 从元组到列表,n 从 bisect 需要在剪切后复制您的列表,log(n) 它必须搜索,@987654361 @ 用于返回元组。
    • @bproxauf 添加了运行时复杂性分析来回答 TL;DR 是 O{2n}O{3n + log(n)} 。另外,我从删除元素的情况中删除了sorted(),因为之前的状态元组已经被排序了。
    • 嗯,在我的算法中,元组列表元组转换实际上比sorted(generator_expression) 更快,但我想渐近并不总是适用(复杂性前面的系数)......无论如何,最重要的变化是外部使用 set 而不是 list,每次迭代的时间现在几乎保持不变,而不是在某个时间点之后增长。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-17
    • 2013-08-07
    • 1970-01-01
    • 2013-12-12
    • 1970-01-01
    • 2018-11-03
    相关资源
    最近更新 更多