我想编写一个带有Ints
和length数组的程序,并返回包含位置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 ++ [[]])
但查询f [] 1000000
的工作时间很长,为什么?
答案 0 :(得分:7)
我看到我们正积累了一些数据结构。我想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
我们的底层元素有一些关联运算符<>
,因此我们可以将该运算符逐点应用于一对列表,就像zipWith
一样,除非当我们用完其中一个列表时,我们不会截断,而是我们只接受另一个。请注意,Bunch
是我为此答案而引入的名称,但这并不是一件不寻常的事情。我确定我之前已经使用过它,并且会再次使用它。
如果我们可以翻译
0 -> Bunch [[0]] -- single 0 in place 0
1 -> Bunch [[],[1]] -- single 1 in place 1
2 -> Bunch [[],[],[2]] -- single 2 in place 2
3 -> Bunch [[],[],[],[3]] -- single 3 in place 3
...
在输入中输入和foldMap
,然后我们将在每个地方获得正确的数量。只要您愿意将[]
解释为“其余是沉默”,输入中的数字就不需要上限来获得合理的输出。否则,就像Procrustes一样,你可以填充或切割到你需要的长度。
顺便说一句,请注意,当mappend
的第一个参数来自我们的翻译时,我们会执行一系列([]++)
操作,即id
s,然后是([i]++)
个(i:)
1}},又名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..]]]
然后就是
ala'
什么?好吧,如果没有ala' Bunch foldMap f = bunch . foldMap (Bunch . f)
一般来到城镇,其影响如下:
f
意味着,虽然f
是列表的函数,但我们会累积,好像Bunch
是ala'
的函数:pack
的作用是插入纠正unpack
和(basis !!) :: Int -> [[Int]]
操作,以实现这一目标。
我们需要basis :: [[[Int]]]
作为我们的翻译。因此basis
是我们翻译的图像列表,每次最多按需计算一次(即翻译,记忆)。
对于这个[ [] [ [[0]]
, [[]] , [[1]]
, [[],[]] , [[2]]
, [[],[],[]] , [[3]]
... ...
,请注意我们需要这两个无限列表
Bunch
明智地合并basis = zipWith (++) (iterate ([]:) []) [[[i]] | i <- [0..]]
。由于两个列表具有相同的长度(无穷大),我也可以编写
Bunch
但我认为值得观察的是,这也是accumArray
结构的一个例子。
当然,当像Monoid
之类的东西完全按照你需要的那种积累时,这是非常好的,整齐地包装了一堆蹩脚的幕后变异。但积累的一般方法是思考“什么是foldMap
?”和“我怎么处理每个元素?”。这就是{{1}}问你的问题。
答案 1 :(得分:6)
(++)
运算符复制左侧列表。出于这个原因,添加到列表的开头非常快,但添加到列表的 end 非常慢。
总之,请避免将内容添加到列表的 end 。尝试始终添加到开头。一种简单的方法是向后构建列表,然后在最后反转它。一个更狡猾的技巧是使用“差异列表”(Google it)。另一种可能性是使用Data.Sequence
而不是列表。
答案 2 :(得分:5)
首先应该注意的是,实现这一点最明显的方法是使用允许随机访问的数据结构,数组显然是一种选择。请注意,您需要多次将元素添加到数组中,然后以某种方式加入它们#34;。
accumArray
非常适合这种情况。
所以我们得到:
f l i = elems $ accumArray (\l e -> e:l) [] (0,i-1) (map (\e -> (e,e)) l)
我们很高兴(请参阅完整代码here)。
这种方法确实涉及将最终数组转换回列表,但该步骤很可能比对列表排序更快,这通常涉及扫描列表至少几次以获得合适大小的列表。
答案 3 :(得分:3)
每当您使用++
时,您必须重新创建整个列表,因为列表是不可变的。
一个简单的解决方案是使用:
,但这会构建一个反向列表。但是,可以使用reverse
修复此问题,这样只会生成两个列表(而不是您的情况下为100万个)。
答案 4 :(得分:3)
你将事物搞砸到累加器上的概念是非常有用的,MathematicalOrchid和Guvante都表明你如何合理有效地使用这个概念。但在这种情况下,有一种更简单的方法也可能更快。你从
开始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
这很好,但是让数字列表不是免费的,所以你可能想知道如何优化它。我邀请你尝试的一种方法是使用原始的技术来保持一个单独的计数器,但遵循同样的结构。