Haskell 计算整数的程序的长时间工作
例如,我想编写一个程序,它接受Haskell 计算整数的程序的长时间工作,haskell,Haskell,例如,我想编写一个程序,它接受int的数组和长度,并返回包含在I位置的所有元素的数组,这些元素等于I [0,0,0,1,3,5,3,2,2,4,4,4] 6 -> [[0,0,0],[1],[2,2],[3,3],[4,4,4],[5]] [0,0,4] 7 -> [[0,0],[],[],[],[4],[],[]] [] 3 -> [[],[],[]] [2,2] 3 -> [[],[],[2,2]] 这就是我的解决方案 import Data.List import
int的数组和长度,并返回包含在I
位置的所有元素的数组,这些元素等于I
[0,0,0,1,3,5,3,2,2,4,4,4] 6 -> [[0,0,0],[1],[2,2],[3,3],[4,4,4],[5]]
[0,0,4] 7 -> [[0,0],[],[],[],[4],[],[]]
[] 3 -> [[],[],[]]
[2,2] 3 -> [[],[],[2,2]]
这就是我的解决方案
import Data.List
import Data.Function
f :: [Int] -> Int -> [[Int]]
f ls len = g 0 ls' [] where
ls' = group . sort $ ls
g :: Int -> [[Int]] -> [[Int]] -> [[Int]]
g val [] accum
| len == val = accum
| otherwise = g (val+1) [] (accum ++ [[]])
g val (x:xs) accum
| len == val = accum
| val == head x = g (val+1) xs (accum ++ [x])
| otherwise = g (val+1) (x:xs) (accum ++ [[]])
但是queryf[]1000000
工作时间很长,为什么?无论何时使用++
都必须重新创建整个列表,因为列表是不可变的
一个简单的解决方案是使用:
,但这会构建一个反向列表。但是,可以使用reverse
修复此问题,这只会生成两个列表(而不是100万个列表)。(++)
操作符复制左侧列表。由于这个原因,添加到列表的开头相当快,但是添加到列表的结尾非常慢
总之,避免在列表末尾添加内容。试着总是在开头加上。一种简单的方法是向后构建列表,然后在最后将其反转。一个更狡猾的技巧是使用“差异列表”(Google it)。另一种可能是使用数据。序列而不是列表。你将东西拼凑到累加器上的概念非常有用,两者都可以,并展示你如何合理有效地使用这个概念。但在这种情况下,有一种更简单的方法也可能更快。你从
group . sort $ ls
这是一个非常好的开始!你会得到一张几乎是你想要的清单,只是你需要填一些空白。我们怎么才能弄清楚呢?最简单的方法,尽管可能不是最有效的方法,是使用一个包含所有要计数的数字的列表:[0..len-1]
所以我们从
f ls len = g [0 .. len-1] (group . sort $ ls)
where
?
我们如何定义g
?通过模式匹配
f ls len = g [0 .. len-1] (group . sort $ ls)
where
-- We may or may not have some lists left,
-- but we counted as high as we decided we
-- would
g [] _ = []
-- We have no lists left, so the rest of the
-- numbers are not represented
g ns [] = map (const []) ns
-- This shouldn't be possible, because group
-- doesn't make empty lists.
g _ ([]:_) = error "group isn't working!"
-- Finally, we have some work to do!
g (n:ns) xls@(xl@(x:_):xls')
| n == x = xl : g ns xls'
| otherwise = [] : g ns xls
这很好,但是制作数字列表不是免费的,所以你可能想知道如何优化它。我邀请您尝试的一种方法是使用您最初的保留一个单独计数器的技术,但遵循相同的结构。首先要注意的是实现这一点最明显的方法是使用允许随机访问的数据结构,数组显然是一种选择。请注意,您需要多次将元素添加到数组中,并以某种方式“连接它们”
accumArray
非常适合这一点
因此,我们得到:
f l i = elems $ accumArray (\l e -> e:l) [] (0,i-1) (map (\e -> (e,e)) l)
我们可以开始了(请参阅完整代码)
这种方法确实需要将最终数组转换回列表,但这一步很可能比排序列表要快,排序列表通常需要扫描列表至少几次,以获得一个大小合适的列表。我看到我们正在积累一些数据结构。我想foldMap
。我问“哪个Monoid
”?这是一种积累清单。像这样
newtype Bunch x = Bunch {bunch :: [x]}
instance Semigroup x => Monoid (Bunch x) where
mempty = Bunch []
mappend (Bunch xss) (Bunch yss) = Bunch (glom xss yss) where
glom [] yss = yss
glom xss [] = xss
glom (xs : xss) (ys : yss) = (xs <> ys) : glom xss yss
然后,我们将在每个位置获得正确的编号。只要您愿意将[]
解释为“其余的都是静默”,就不需要输入中的数字上界来获得合理的输出。否则,像Procrustes一样,你可以按需要的长度进行填充或切割
请注意,顺便说一下,当mappend
的第一个参数来自我们的翻译时,我们会执行一系列([]+)
操作,也称为id
s,然后是单个([i]+)
,也称为(i:)
,因此如果foldMap
是正确嵌套的(用于列表),然后,我们将始终在列表的左端执行廉价操作
现在,由于这个问题与列表有关,我们可能只想在有用的时候介绍Bunch
结构。这就是Control.Newtype
的用途。我们只需要告诉它关于Bunch
instance Newtype (Bunch x) [x] where
pack = Bunch
unpack = bunch
然后是
groupInts :: [Int] -> [[Int]]
groupInts = ala' Bunch foldMap (basis !!) where
basis = ala' Bunch foldMap id [iterate ([]:) [], [[[i]] | i <- [0..]]]
这意味着,尽管f
是列表的一个函数,但我们累积起来就好像f
是Bunch
es的一个函数:ala'
的作用是插入正确的pack
和unpack
操作来实现这一点
我们需要(basis!!)::Int->[[Int]]
作为我们的翻译。因此,basis::[[[Int]]]
是我们翻译的图像列表,每个图像最多按需计算一次(即,翻译,记忆)
对于这个基础
,请注意我们需要这两个无限列表
[ [] [ [[0]]
, [[]] , [[1]]
, [[],[]] , [[2]]
, [[],[],[]] , [[3]]
... ...
组合Bunch
wise。由于两个列表的长度相同(无穷大),我也可以写
basis = zipWith (++) (iterate ([]:) []) [[[i]] | i <- [0..]]
basis=zipWith(++)(迭代([]:)[[])[[[i]]]谢谢!我明白了!谢谢!你的答案对我很有用!你可以把它写成\ls len->map(\i->filter(=i)ls)[0..len 1]
。很好的解决方案,谢谢!@user2407038,如果列表很长,可能会非常非常慢。谢谢你的解释!你能从解释你折叠的是什么结构开始吗?这对我来说并不清楚。而且,当我看到!!
,二次曲线的景象开始在我的脑海中起舞。这不是有问题吗son?一种类型的foldMap
是(a->Bunch x)->[a]->Bunch x
。我正在折叠输入列表。我并不是说这是最快的解决方案,只是它避免了+
的左嵌套。
[ [] [ [[0]]
, [[]] , [[1]]
, [[],[]] , [[2]]
, [[],[],[]] , [[3]]
... ...
basis = zipWith (++) (iterate ([]:) []) [[[i]] | i <- [0..]]