我正在学习在haskell中思考和编码。 “最小数量获胜”的游戏:n人在1和n之间的数字上下注,而只有一次下注的最小数字获胜。
我正在计算n = 10的所有可能的投注系数并计算获胜者数字。是的,这段代码并不能完全解决这个问题,但这不是我的观点,而是我的代码,内存耗尽相对较快。
(添加评论 - 抱歉!)
import Data.Array
import Data.List
f xs = flip map [1..10] $ flip (:) xs
p 1 = f []
p n = concat $ map f $ p (n-1)
--the above, (p n) generates the list of all possible [a1, a2, ..., an] lists, where ai=1..10
--p 2 = [[1,1],[2,1],[3,1],[4,1],[5,1],...,[10,10]
--my first shot at the countidens function, the functionality stays the same with the other
--countidens2 xs = map (\x->(head x, length x)) $ group $ sort xs
countidens' xs = accumArray (+) 0 (1,10) $ zip xs $ repeat 1
countidens xs = filter ((/=) 0 . snd) $ zip [1..10] $ map ((countidens' xs)!) [1..10]
--counts the number of occurrences of each number (1..10) in a list
--countidens [1,1,1,2,2,3] = (1,3),(2,2),(3,1)]
--(the above, countidens2 is much easier to understand)
numlist n = map (flip (++) ([(0,0)])) $ map countidens $ p n
--maps countidens on the (p n) list, and attaches a dummy (0,0) to the end (this is needed later)
g (x, (y, z)) | (x==y) && (z==1) = True
| (x < y) = True
| (y==0) = True
| otherwise = False
-- filter function for [(a, (a,a)] lists - (a1, (a1, a)) -> Bool
winners n = map fst $ map (head . filter g) $ map (zip [1..]) $ numlist n
-- extracts the number of the first element of (numlist n) that qualifies as g
-- for each element of g (note: these are results of the countidens function, since that was mapped)
-- the dummy (0,0) was needed so there's always one that does
winnernumsarr n = accumArray (+) 0 (1,10) $ flip zip (repeat 1) $ winners n
-- winners n produces a simple list of integers (1..10) that is 10^n long, this (winnernumsarr) accumulates the number of each integer, much like countidens did
-- (but does not produce a fancy output)
main = putStrLn $ show $ winnernumsarr 7 -- aiming for 10! even 8 runs out of memory on my machine
虽然我知道这段代码不能完全按照我的意愿去做,但更重要的是,这不是我第一次遇到haskell的“内存不足”问题,而且我遇到了问题know可以用C ++编写,只使用少量内存。
一定有办法 - 但是如何?
答案 0 :(得分:1)
这里有两件事很重要。输入签名和未装箱的数组。
module Main (main) where
import Data.Array.Unboxed
import Data.List
f xs = flip map [1..10] $ flip (:) xs
p 1 = f []
p n = concat $ map f $ p (n-1)
--my first shot at the countidens function, the functionality stays the same with the other
--countidens2 xs = map (\x->(head x, length x)) $ group $ sort xs
countidens' :: [Int] -> UArray Int Int
countidens' xs = accumArray (+) 0 (1,10) $ zip xs $ repeat 1
countidens xs = filter ((/=) 0 . snd) $ assocs (countidens' xs)
numlist n = map (flip (++) ([(0,0)])) $ map countidens $ p n
g (x, (y, z)) | (x==y) && (z==1) = True
| (x < y) = True
| (y==0) = True
| otherwise = False
winners n = map fst $ map (head . filter g) $ map (zip [1..]) $ numlist n
winnernumsarr :: Int -> UArray Int Int
winnernumsarr n = accumArray (+) 0 (1,10) $ flip zip (repeat 1) $ winners n
main = putStrLn $ show $ winnernumsarr 7
在很小的空间内运行,虽然速度很慢(8秒需要50秒,7秒需要4.9秒)。
当您使用盒装数组时,accumArray
不会将纯数字写入数组,而是使用thunk。在winnernumsarr
中,thunk变得巨大。这需要大量内存,并且最终需要大量的堆栈空间来进行评估。使用未装箱的阵列,添加会在它们到来时执行,而不是构建巨大的阵容。
类型签名是修复要打印的数组类型以及使所有出现的数字类型Int
以减少分配和提高速度所必需的。
在不改变算法的情况下,更加惯用的版本是
module Main (main) where
import Data.Array.Unboxed
import Data.List
p :: Int -> [[Int]]
p 0 = [[]]
p n = [k:xs | xs <- p (n-1), k <- [1 .. 10]]
countidens' :: [Int] -> UArray Int Int
countidens' xs = accumArray (+) 0 (1,10) $ map (\k -> (k,1)) xs
countidens :: [Int] -> [(Int,Int)]
countidens = filter ((/=) 0 . snd) . assocs . countidens'
numlist n = map ((++[(0,0)]) . countidens) $ p n
g :: (Int,(Int,Int)) -> Bool
g (x, (y, z)) | (x==y) && (z==1) = True
| (x < y) = True
| (y==0) = True
| otherwise = False
winners :: Int -> [Int]
winners n = map fst $ map (head . filter g) $ map (zip [1..]) $ numlist n
winnernumsarr :: Int -> UArray Int Int
winnernumsarr n = accumArray (+) 0 (1,10) $ map (\k -> (k,1)) $ winners n
main :: IO ()
main = print $ winnernumsarr 7
这也更快。一点点加速来自GHC可以更好地优化列表生成函数p
的这种形式,大部分来自用zip xs (repeat 1)
替换map (\k -> (k,1)) xs
。我必须承认,我不明白为什么会产生如此大的影响,但zip
必须将两个列表与_ : _
匹配,而map
只需匹配xs
,这节省了一些工作。
答案 1 :(得分:0)
我很难理解你的代码究竟在做什么,所以我只是编写了一个函数bets
,它接受了所有可能的赌注的懒惰列表中的玩家数量和吐出。
-- `bets n` calculates all possible sequences of bets with `n` players.
-- It returns a list of lists, each sub-list being `n` in length
bets :: Int -> [[Int]]
bets n = bets' n
where bets' :: Int -> [[Int]] -- use separate function so we always have the total `n` available
bets' n'
| n' == 0 = [[]]
| n' > 0 = concatMap step $ bets' (pred n')
| otherwise = error "bets: negative number of players"
step :: [Int] -> [[Int]]
step bs = zipWith (:) [1..n] (repeat bs)
我用n == 5
进行了测试,效果非常好。我不知道你期望用n == 10
表现出什么样的表现,所以这有可能最终对你来说太慢了。
答案 2 :(得分:0)
我建议您从数组(甚至是未装箱的数组)切换到Vector库。界面更加丰富,基于融合的矢量实现通常可以带来性能优势。
这是使用Vector的等效版本,其中包含了一些Daniel Fisher的更改:
{-# LANGUAGE TupleSections #-}
import qualified Data.Vector.Unboxed as V
import Data.List
p :: Int -> [[Int]]
p 0 = [[]]
p n = [k:xs | xs <- p (n-1), k <- [1 .. 10]]
countidens' :: [Int] -> V.Vector Int
countidens' xs = V.accum (+) (V.replicate 11 0) $ map (,1) xs
countidens = V.filter ((/= 0) . snd) . V.indexed . countidens'
numlist = map ((`V.snoc` (0,0)) . countidens) . p
g (x, (y, z)) | (x==y) && (z==1) = True
| (x < y) = True
| (y==0) = True
| otherwise = False
winners n = map (fst . V.head . V.filter g . V.imap (\ix a -> (ix+1,a)) ) $ numlist n
winnernumsarr :: Int -> V.Vector (Int,Int)
winnernumsarr n = V.tail . V.indexed $ V.accum (+) (V.replicate 11 0)
$ flip zip (repeat 1) $ winners n
main = putStrLn $ show $ winnernumsarr 8
在我的系统上,这会将运行时间从49秒减少到31秒,两个程序都使用“-O2 -msse2”进行编译。
两个警告:首先,Vector实现向量,因此如果您需要多维索引,您可能希望继续使用数组。其次,向量是0索引的,因此您可能需要对代码的其余部分进行适当的调整。