通过删除concat来提高效率

时间:2015-12-13 02:15:44

标签: haskell

我试图提高代码中特定功能的效率,这会占用大量的运行时间。在分析之后,我相信这是因为代码中的concat。我怎样才能更快地改进这段代码呢?

chunk :: C -> [A] -> [[A]]
chunk c = go []
  where s = Set.fromList (map snd (Map.toList c))
        go :: [A] -> [A] -> [[A]]
        go l []     = [l | member l s]
        go l (x:xs) = if member l s then l : go [x] xs
                                    else go (l ++ [x]) xs

感谢您的帮助!

1 个答案:

答案 0 :(得分:4)

一个简单的解决方案是使用Seq,其中 snoc 操作是 O(1)。这涉及首先转换输入然后转换结果,这是值得的,除非该集合与列表的平均长度相比较大。

然而,还有另一个问题,那就是测试列表(或类似结构)的成员资格。列表上的比较或测试相等性是 O(n),在您的情况下,您测试列表的成员资格,该列表可能是集合中包含的列表的子列表,测试将确实是Ω(n)。即便如此,chunk的复杂性可能是 O(n ^ 2)的顺序,其中 n 是列表参数的长度。

似乎使用trie会是更好的解决方案。在内存和时间复杂度方面,trie比一组列表更有效。对于这种情况特别有用的是它的操作,它允许你通过在 O(1)中过滤具有给定前缀的所有元素来构建子构件。

示例代码(未经测试):

chunk :: C -> [A] -> [[A]]
chunk c = go trie
  where trie = Trie.fromList (map snd (Map.toList c))
        go :: Trie -> [A] -> [A] -> [[A]]
        go s l []     = [reverse l | Trie.member [] s]
        go s l (x:xs)
          | Trie.member [] s = reverse l : go (Trie.lookupPrefix [x] trie) xs
          | otherwise        = go (x : l) (Trie.lookupPrefix [x] s)

现在go的每一步都应该只采用 O(n)摊销成本(唯一的非 O(1)操作是{{1} },但这是 O(1)摊销,因为在 k 步骤之后只反转 k - 元素列表一次。)

现在我们还可以进一步改进:当子trie为空时,我们知道我们永远不会返回一个额外的元素,因为我们永远不会达到匹配的情况。所以我们可以在顶部添加一个模式

reverse

套餐list-trie似乎是完美的。