扁平嵌套循环/降低复杂性 - 互补对计数算法

时间:2012-01-13 15:13:46

标签: python algorithm loops nested complexity-theory

我最近试图解决Python中的一些任务,我发现解决方案似乎具有 O(n log n)的复杂性,但我认为对于某些输入来说效率非常低(例如第一个参数为0pairs为非常长的零列表。)

它还有三级for循环。我相信它可以进行优化,但目前我无法对其进行优化,我可能只是遗漏了一些明显的东西;)

所以,基本上,问题如下:

  

给定整数列表(values),函数需要返回满足以下条件的索引对的数量:

     
      
  • 假设单个索引对是(index1, index2)
  • 之类的元组   
  • 然后values[index1] == complementary_diff - values[index2]为真,
  •   
     

示例:   如果将[1, 3, -4, 0, -3, 5]列为values1列为complementary_diff,则该函数应返回4(这是以下索引列表的长度'对:[(0, 3), (2, 5), (3, 0), (5, 2)])。

这就是我到目前为止,它应该在大多数情况下完美地工作,但是 - 正如我所说的 - 在某些情况下它可以非常缓慢地运行,尽管它的复杂性近似 O(n log n) (看起来悲观的复杂性是 O(n ^ 2))。

def complementary_pairs_number (complementary_diff, values):
    value_key = {} # dictionary storing indexes indexed by values
    for index, item in enumerate(values):
        try:
            value_key[item].append(index)
        except (KeyError,): # the item has not been found in value_key's keys
            value_key[item] = [index]
    key_pairs = set() # key pairs are unique by nature
    for pos_value in value_key: # iterate through keys of value_key dictionary
        sym_value = complementary_diff - pos_value
        if sym_value in value_key: # checks if the symmetric value has been found
            for i1 in value_key[pos_value]: # iterate through pos_values' indexes
                for i2 in value_key[sym_value]: # as above, through sym_values
                    # add indexes' pairs or ignore if already added to the set
                    key_pairs.add((i1, i2))
                    key_pairs.add((i2, i1))
    return len(key_pairs)

对于给定的示例,它的行为类似于:

>>> complementary_pairs_number(1, [1, 3, -4, 0, -3, 5])
4

如果您看到代码如何“扁平化”或“简化”,请告知我们。

我不确定只是检查complementary_diff == 0等是最好的方法 - 如果您认为是,请告诉我。

编辑:我已经更正了示例(谢谢,unutbu!)。

5 个答案:

答案 0 :(得分:4)

我认为这会提高O(n)的复杂性:

  • value_key.setdefault(item,[]).append(index)比使用更快 try..except块。它也比使用collections.defaultdict(list)更快。 (我用ipython%timeit测试了这个。)
  • 原始代码访问每个解决方案两次。对于每个pos_valuevalue_key中,有一个与之关联的唯一sym_value pos_value。当sym_value也在时,有解决方案 value_key。但是当我们遍历value_key中的键时, 最终将pos_value分配给sym_value的值,即pos_value 使代码重复已经完成的计算。所以你可以 如果你可以阻止sym_value等于 旧的seen = set()。我用sym_value来实现它 跟踪len(key_pairs) s。
  • 代码仅关注key_pairs,而不关心set。因此,而不是跟踪对(用 num_pairs),我们可以简单地跟踪计数(使用num_pairs += 2*len(value_key[pos_value])*len(value_key[sym_value]) )。所以我们可以用

    替换两个内部for循环
    pos_value == sym_value

    或“独特对角线”情况的一半,def complementary_pairs_number(complementary_diff, values): value_key = {} # dictionary storing indexes indexed by values for index, item in enumerate(values): value_key.setdefault(item,[]).append(index) # print(value_key) num_pairs = 0 seen = set() for pos_value in value_key: if pos_value in seen: continue sym_value = complementary_diff - pos_value seen.add(sym_value) if sym_value in value_key: # print(pos_value, sym_value, value_key[pos_value],value_key[sym_value]) n = len(value_key[pos_value])*len(value_key[sym_value]) if pos_value == sym_value: num_pairs += n else: num_pairs += 2*n return num_pairs


{{1}}

答案 1 :(得分:2)

您可能希望研究函数式编程习语,例如reduce等。

通常,通过使用reduce,map,reject等函数可以简化嵌套数组逻辑。

例如(在javascript中)查看下划线js。我对Python并不十分聪明,所以我不知道他们有哪些库。

答案 2 :(得分:0)

我认为(部分或全部)这些会有所帮助,但我不确定如何证明这一点。

1)取值并将其减少到一组不同的值,记录每个元素的数量(O(n))

2)对结果数组进行排序。 (n log n)

3)如果你可以分配大量内存,我想你可能能够用值填充稀疏数组 - 所以如果值的范围是-100:+100,则分配一个[201]和任何数组在简化集合中存在的值会在大型稀疏数组中的值索引处弹出一个值。

4)你想要检查它是否满足条件的任何值现在必须根据x-y关系查看稀疏数组中的索引,并查看是否存在值。

5)正如unutbu指出的那样,它是平凡对称的,所以如果{a,b}是一对,那么{b,a}。

答案 3 :(得分:0)

我认为你可以通过将代数部分与搜索分离并使用更智能的数据结构来改进这一点。

  1. 浏览列表并从列表中每个项目的互补差异中减去。

    resultlist[index] = complementary_diff - originallist[index]
    

    您可以使用地图或简单循环。 - >需要 O(n)时间。

  2. 查看结果列表中的数字是否存在于原始列表中。

    • 在这里,通过一个天真的列表,你实际上会得到 O(n ^ 2),因为你最终可以在结果列表中的每个项目中搜索整个原始列表。 / p>

    • 但是,有更聪明的方法来组织您的数据。如果您有原始列表已排序,则您的搜索时间将减少为 O(nlogn + nlogn)= O(nlogn) nlogn nlogn ,用于每个元素的二进制搜索。

    • 如果您想要更聪明,可以将列表放入字典(或哈希表),然后此步骤变为 O(n + n)= O (n) n 用于构建字典, 1 * n 用于搜索字典中的每个元素。 (*编辑:*因为您不能假设原始列表中每个值的唯一性。您可能想要计算每个值在原始列表中出现的次数。)

  3. 所以现在你得到 O(n)总运行时间。

    使用您的示例:

    1, [1, 3, -4, 0, -3, 5],
    
    1. 生成结果列表:

      >>> resultlist
      [0, -2, 5, 1, 4, -4].
      
    2. 现在我们搜索:

      • 将原始列表展平为字典。我选择使用原始列表的索引作为值,因为这似乎是您感兴趣的副数据。

        >>> original_table
        {(1,0), (3,1), (-4,2), (0,3), (-3,4), (5,5)}
        
      • 对于结果列表中的每个元素,在哈希表中搜索并生成元组:

        (resultlist_index, original_table[resultlist[resultlist_index]])
        

        这看起来应该是您的示例解决方案。

    3. 现在您只需找到生成的元组列表的长度。

    4. 现在这里是代码:

      example_diff = 1
      example_values = [1, 3, -4, 0, -3, 5]
      example2_diff = 1
      example2_values = [1, 0, 1]
      
      def complementary_pairs_number(complementary_diff, values):
          """
              Given an integer complement and a list of values count how many pairs
              of complementary pairs there are in the list.
          """
          print "Input:", complementary_diff, values
          # Step 1. Result list
          resultlist = [complementary_diff - value for value in values]
          print "Result List:", resultlist
      
          # Step 2. Flatten into dictionary
          original_table = {}
          for original_index in xrange(len(values)):
              if values[original_index] in original_table:
                  original_table[values[original_index]].append(original_index)
              else:
                  original_table[values[original_index]] = [original_index]
          print "Flattened dictionary:", original_table
      
          # Step 2.5 Search through dictionary and count up the resulting pairs.
          pair_count = 0
          for resultlist_index in xrange(len(resultlist)):
              if resultlist[resultlist_index] in original_table:
                  pair_count += len(original_table[resultlist[resultlist_index]])
          print "Complementary Pair Count:", pair_count
      
          # (Optional) Step 2.5 Search through dictionary and create complementary pairs. Adds O(n^2) complexity.
          pairs = []
          for resultlist_index in xrange(len(resultlist)):
              if resultlist[resultlist_index] in original_table:
                  pairs += [(resultlist_index, original_index) for original_index in
                      original_table[resultlist[resultlist_index]]]
          print "Complementary Pair Indices:", pairs
      
          # Step 3
          return pair_count
      
      if __name__ == "__main__":
          complementary_pairs_number(example_diff, example_values)
          complementary_pairs_number(example2_diff, example2_values)
      

      输出:

      $ python complementary.py
      Input: 1 [1, 3, -4, 0, -3, 5]
      Result List: [0, -2, 5, 1, 4, -4]
      Flattened dictionary: {0: 3, 1: 0, 3: 1, 5: 5, -4: 2, -3: 4}
      Complementary Pair Indices: [(0, 3), (2, 5), (3, 0), (5, 2)]
      Input: 1 [1, 0, 1]
      Result List: [0, 1, 0]
      Flattened dictionary: {0: [1], 1: [0, 2]}
      Complementary Pair Count: 4
      Complementary Pair Indices: [(0, 1), (1, 0), (1, 2), (2, 1)]
      

      谢谢!

答案 4 :(得分:0)

修改了@unutbu提供的解决方案:

比较这两个词典可以减少问题:

  1. 预先计算的字典(complementary_diff - values [i])

    def complementary_pairs_number(complementary_diff, values):
        value_key = {} # dictionary storing indexes indexed by values
        for index, item in enumerate(values):
            value_key.setdefault(item,[]).append(index)
    
        answer_key = {} # dictionary storing indexes indexed by (complementary_diff - values)
        for index, item in enumerate(values):
            answer_key.setdefault((complementary_diff-item),[]).append(index)
    
        num_pairs = 0
        print(value_key)
        print(answer_key)
        for pos_value in value_key: 
            if pos_value in answer_key: 
                num_pairs+=len(value_key[pos_value])*len(answer_key[pos_value])
        return num_pairs