我试图提高代码中特定功能的效率,这会占用大量的运行时间。在分析之后,我相信这是因为代码中的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
感谢您的帮助!
答案 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似乎是完美的。