这里有全新的Haskell程序员。刚刚完成“了解你一个Haskell”......我对一个有多大特定属性的集合感兴趣。我有一些小参数值的代码,但我想知道如何处理更大的结构。我知道Haskell可以做“无限的数据结构”,但我只是没有看到如何从我所在的位置到达那里并且了解一个Haskell / Google并没有让我了解这一点。
以下是eSet
给出的“小”参数r
和t
import Control.Monad
import System.Environment
import System.Exit
myPred :: [Int] -> Bool
myPred a = myPred' [] a
where
myPred' [] [] = False
myPred' [] [0] = True
myPred' _ [] = True
myPred' acc (0:aTail) = myPred' acc aTail
myPred' acc (a:aTail)
| a `elem` acc = False
| otherwise = myPred' (a:acc) aTail
superSet :: Int -> Int -> [[Int]]
superSet r t = replicateM r [0..t]
eSet :: Int -> Int -> [[Int]]
eSet r t = filter myPred $ superSet r t
main :: IO ()
main = do
args <- getArgs
case args of
[rArg, tArg] ->
print $ length $ eSet (read rArg) (read tArg)
[rArg, tArg, "set"] ->
print $ eSet (read rArg) (read tArg)
_ ->
die "Usage: eSet r r set <set optional for printing set itself otherwise just print the size
编译/运行时我得到了
$ ghc eSet.hs -rtsopts
[1 of 1] Compiling Main ( eSet.hs, eSet.o )
Linking eSet ...
$ # Here's is a tiny eSet to illustrate. It is the set of lists of r integers from zero to t with no repeated nonzero list entries
$ ./eSet 4 2 set
[[0,0,0,0],[0,0,0,1],[0,0,0,2],[0,0,1,0],[0,0,1,2],[0,0,2,0],[0,0,2,1],[0,1,0,0],[0,1,0,2],[0,1,2,0],[0,2,0,0],[0,2,0,1],[0,2,1,0],[1,0,0,0],[1,0,0,2],[1,0,2,0],[1,2,0,0],[2,0,0,0],[2,0,0,1],[2,0,1,0],[2,1,0,0]]
$ ./eSet 8 4 +RTS -sstderr
3393
174,406,136 bytes allocated in the heap
29,061,152 bytes copied during GC
4,382,568 bytes maximum residency (7 sample(s))
148,664 bytes maximum slop
14 MB total memory in use (0 MB lost due to fragmentation)
Tot time (elapsed) Avg pause Max pause
Gen 0 328 colls, 0 par 0.047s 0.047s 0.0001s 0.0009s
Gen 1 7 colls, 0 par 0.055s 0.055s 0.0079s 0.0147s
INIT time 0.000s ( 0.000s elapsed)
MUT time 0.298s ( 0.301s elapsed)
GC time 0.102s ( 0.102s elapsed)
EXIT time 0.001s ( 0.001s elapsed)
Total time 0.406s ( 0.405s elapsed)
%GC time 25.1% (25.2% elapsed)
Alloc rate 585,308,888 bytes per MUT second
Productivity 74.8% of total user, 75.0% of total elapsed
$ ./eSet 10 5 +RTS -sstderr
63591
27,478,010,744 bytes allocated in the heap
4,638,903,384 bytes copied during GC
532,163,096 bytes maximum residency (15 sample(s))
16,500,072 bytes maximum slop
1556 MB total memory in use (0 MB lost due to fragmentation)
Tot time (elapsed) Avg pause Max pause
Gen 0 52656 colls, 0 par 6.865s 6.864s 0.0001s 0.0055s
Gen 1 15 colls, 0 par 8.853s 8.997s 0.5998s 1.8617s
INIT time 0.000s ( 0.000s elapsed)
MUT time 52.652s ( 52.796s elapsed)
GC time 15.717s ( 15.861s elapsed)
EXIT time 0.193s ( 0.211s elapsed)
Total time 68.564s ( 68.868s elapsed)
%GC time 22.9% (23.0% elapsed)
Alloc rate 521,883,277 bytes per MUT second
Productivity 77.1% of total user, 76.7% of total elapsed
我看到我的内存使用率非常高,并且有很多垃圾收集。运行eSet 12 6
时出现分段错误。
我觉得filter myPred $ superSet r t
让我不能懒惰地让子集一次成为一部分因此我可以处理更大(但有限)的集合,但我不知道如何改变另一种方法会这样做的。我认为这是我问题的根源。
另外,由于这是我的第一个Haskell程序,因此非常感谢对样式以及如何实现Haskell类似“pythonic”的观点!
答案 0 :(得分:5)
我怀疑这里的罪魁祸首是LineEnd()
,其中有this implementation:
replicateM
问题行是replicateM cnt0 f =
loop cnt0
where
loop cnt
| cnt <= 0 = pure []
| otherwise = liftA2 (:) f (loop (cnt - 1))
;可能liftA2 (:) f (loop (cnt - 1))
在loop (cnt - 1)
部分应用于(:)
元素的所有调用之间共享,因此f
必须保留在内存中。不幸的是loop (cnt - 1)
是一个很长的列表......
说服GHC 不分享某些东西可能有点繁琐。以下对loop (cnt - 1)
的重新定义给了我一个很好的平坦内存使用情况;当然,在适合内存的示例中,它可能会慢一些。关键的想法是让它看起来像未经训练的眼睛(即GHC),就像递归的monadic动作取决于之前做出的选择,即使它没有。
superSet
如果你不介意避免优化,那么更自然的定义也可以起作用:
superSet :: Int -> Int -> [[Int]]
superSet r t = go r 0 where
go 0 ignored = if ignored == 0 then [[]] else [[]]
go r ignored = do
x <- [0..t]
xs <- go (r-1) (ignored+x)
return (x:xs)
...但superSet 0 t = [[]]
superSet r t = do
x <- [0..t]
xs <- superSet (r-1) t
return (x:xs)
GHC过于聪明,并注意到它可以共享递归调用。
答案 1 :(得分:4)
完全替代方法是进行一些组合分析。这是eSet r t
所做的过程,尽我所能:
r
个元素,而无需从一组尺寸t
替换。r
的长度。因此,让我们只计算执行这些步骤的方法,而不是实际执行它们。我们将引入一个新参数s
,它是步骤(1)生成的序列的长度(因此我们知道它具有s <= r
和s <= t
)。在没有替换大小s
的情况下绘制元素时,有多少个大小为t
的序列?好吧,第一个元素有t
个选项,第二个元素有t-1
个选项,第三个元素有t-2
个选项,......
-- sequencesWithoutReplacement is a very long name!
seqWORepSize :: Integer -> Integer -> Integer
seqWORepSize s t = product [t-s+1 .. t]
然后我们想要将序列填充到r
的长度。我们将从我们的序列中选择s
- 长序列中的r
个位置,其余的将是哨兵。有多少种方法可以做到这一点?这是一个着名的组合运算符choose
。
choose :: Integer -> Integer -> Integer
choose r s = product [r-s+1 .. r] `div` product [2 .. s]
生成给定长度的填充序列的方法数量只是这两个数字的乘积,因为“插入什么值”和“插入值的位置”的选择可以完全独立。
paddedSeqSize :: Integer -> Integer -> Integer -> Integer
paddedSeqSize r s t = seqWORepSize s t * (r `choose` s)
现在我们已经完成了很多工作。只需迭代所有可能的序列长度并加起来paddedSeqSize
。
eSetSize :: Integer -> Integer -> Integer
eSetSize r t = sum $ map (\s -> paddedSeqSize r s t) [0..r]
我们可以在ghci中尝试:
> :set +s
> map length $ [eSet 1 1, eSet 4 4, eSet 6 4, eSet 4 6]
[2,209,1045,1045]
(0.13 secs, 26,924,264 bytes)
> [eSetSize 1 1, eSetSize 4 4, eSetSize 6 4, eSetSize 4 6]
[2,209,1045,1045]
(0.01 secs, 120,272 bytes)
这种方式明显更快,并且对内存更友好。实际上,我们可以进行查询并获得有关eSet
的答案,这些答案我们永远无法逐个计算,例如。
> length . show $ eSetSize 1000 1000
2594
(0.26 secs, 909,746,448 bytes)
祝你好运一次只计算10 ^ 2594。 = P
修改强>
这个想法也可以适用于生成填充序列本身,而不仅仅计算有多少。首先,我发现自己定义了一个方便的实用程序,用于挑选列表中的各个元素并报告剩余的内容:
zippers :: [a] -> [([a], a, [a])]
zippers = go [] where
go ls [] = []
go ls (h:rs) = (ls, h, rs) : go (h:ls) rs
现在,无需替换的序列可以通过从剩余部分重复选择一个元素来完成。
seqWORep :: Int -> [a] -> [[a]]
seqWORep 0 _ = [[]]
seqWORep n xs = do
(ls, y, rs) <- zippers xs
ys <- seqWORep (n-1) (ls++rs)
return (y:ys)
一旦我们得到一个序列,我们可以通过产生sentinel值的所有交错来填充它到所需的大小,如下所示:
interleavings :: Int -> a -> [a] -> [[a]]
interleavings 0 _ xs = [xs]
interleavings n z [] = [replicate n z]
interleavings n z xs@(x:xt) = map (z:) (interleavings (n-1) z xs)
++ map (x:) (interleavings n z xt)
最后,顶级函数只委托给seqWORep
和interleavings
。
eSet :: Int -> Int -> [[Int]]
eSet r t = do
s <- [0..r]
xs <- seqWORep s [1..t]
interleavings (r-s) 0 xs
在我的测试中,这个修改过的eSet
在使用和不使用优化的情况下具有良好的平坦内存使用率;不会产生任何需要稍后filter
输出的虚假元素,因此比原始提案更快;与依赖于欺骗GHC的答案相比,对我来说看起来很自然。一系列不错的房产!
答案 2 :(得分:1)
重新阅读LYaH的部分并考虑@ daniel-wagners回答monadically组成听起来像是值得再试一次。新代码总内存是平坦的,可以使用和不使用-O2
优化。
来源:
import Control.Monad
import System.Environment
import System.Exit
allowed :: [Int] -> Bool
allowed a = allowed' [] a
where
allowed' [ ] [ ] = False
allowed' [ ] [0] = True
allowed' _ [ ] = True
allowed' acc (0:aTail) = allowed' acc aTail
allowed' acc (a:aTail)
| a `elem` acc = False
| otherwise = allowed' (a:acc) aTail
branch :: Int -> [Int] -> [[Int]]
branch t x = filter allowed [n:x | n <- [0..t]]
eSet :: Int -> Int -> [[Int]]
eSet r t = return [] >>= foldr (<=<) return (replicate r (branch t))
main :: IO ()
main = do
args <- getArgs
case args of
[rArg, tArg] ->
print $ length $ eSet (read rArg) (read tArg)
[rArg, tArg, "set"] ->
print $ eSet (read rArg) (read tArg)
_ -> die "Usage: eSet r r set <set optional>"
具有monadic函数组合的版本测试速度更快,没有内存问题......
$ ./eSetMonad 10 5 +RTS -sstderr
63591
289,726,000 bytes allocated in the heap
997,968 bytes copied during GC
63,360 bytes maximum residency (2 sample(s))
24,704 bytes maximum slop
2 MB total memory in use (0 MB lost due to fragmentation)
Tot time (elapsed) Avg pause Max pause
Gen 0 553 colls, 0 par 0.008s 0.008s 0.0000s 0.0002s
Gen 1 2 colls, 0 par 0.000s 0.000s 0.0002s 0.0003s
INIT time 0.000s ( 0.000s elapsed)
MUT time 0.426s ( 0.429s elapsed)
GC time 0.009s ( 0.009s elapsed)
EXIT time 0.000s ( 0.000s elapsed)
Total time 0.439s ( 0.438s elapsed)
%GC time 2.0% (2.0% elapsed)
Alloc rate 680,079,893 bytes per MUT second
Productivity 98.0% of total user, 98.3% of total elapsed