首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >迭代囚徒困境Haskell实现

迭代囚徒困境Haskell实现
EN

Code Review用户
提问于 2019-12-13 11:46:55
回答 1查看 99关注 0票数 4

我和一个学院在haskell的博弈论中实现了一个仿真器--迭代囚徒困境。我们希望得到关于代码质量的任何反馈,以及如何更有效或更优雅地解决这些问题。

该代码文件连同一个tex文件(以及已编译的PDF)以识字的haskell风格解释了问题,所有的代码都可以找到论GitHub

我在这里也包括了代码本身:

代码语言:javascript
运行
复制
{-# LANGUAGE FlexibleInstances #-}

module Main where

import System.Random
import Data.List (nubBy, sortBy, intercalate)  
import Data.Bifunctor (bimap)
import Data.Function (on)

main :: IO ()
main =  do
  result <- startSimulation 100 3 100
  print $ show $ stats $ snd result

data Choice = Cooperate | Defect
     deriving (Eq, Show)
type BattleResult = (Choice, Choice)

-------------------------- TYPES  -------------------------- 
type PlayerID = Int
type Payment = Int
type PlayerHist = [((Choice, Payment), (PlayerID, Choice))]
data Player = Player 
                { name :: String -- strategy name 
                , playerID :: PlayerID
                , decide :: PlayerHist -> Choice 
                , getPlayerHist :: PlayerHist
                }

instance Show Player where
  show (Player n p _ o) = 
    "Player { name: '" ++ n ++ "'" ++ 
           ", playerID: " ++ (show p) ++ 
           ", getPlayerHist: " ++ (show o) ++ "'}"

instance Eq Player where
  (Player n _ _ _) == (Player n' _ _ _) = n == n'

instance Eq (Int -> Player) where
  p1 == p2 = (p1 0) == (p2 0)

instance Show (Int -> Player) where
  show p = show $ p 0

type Population      = [Player]

type RandList        = [Int]
type IterationResult = [Player]


-------------------------- DEFINITIONS --------------------------
payment :: BattleResult -> (Int, Int)
payment (Cooperate, Cooperate) = (3,3)
payment (Cooperate, Defect)    = (1,4)
payment (Defect, Cooperate)    = (4,1)
payment (Defect, Defect)       = (2,2)

defector :: Int -> Player
defector n = Player 
                "Defector" 
                n
                (\_ -> Defect)
                []

cooperator :: Int -> Player
cooperator n = Player
                "Cooperator"
                n
                (\_ -> Cooperate)
                []


tftDecide :: PlayerHist -> Choice
tftDecide []            = Cooperate
tftDecide ((_,(_,c)):_) = c

tft :: Int -> Player
tft n = Player
                "TFT"
                n
                tftDecide
                []

rageDecide :: PlayerHist -> Choice
rageDecide [] = Cooperate
rageDecide l  = if (elem Defect . map getOpChoice $ l) then Defect else Cooperate 
                  where getOpChoice = snd . snd 

rage :: Int -> Player
rage n = Player
                "Yasha"
                n
                rageDecide
                []

playerTypes :: [Int -> Player]
playerTypes = [defector, cooperator, tft, rage]

generatePopulation :: [(Int->Player, Int)] -> Population
generatePopulation = map (\(i,p) -> p i) . 
                     zip [1..] . 
                     intercalate [] . 
                     map (\(p,n) -> replicate n p)   



-------------------------- GAME LOGIC --------------------------


--              shuffled population  iteration count
runIteration :: Population ->        Int ->           IterationResult 
runIteration p i = undoPairs $ play i (makePairs p)

--      counter   shuffled list of battles         
play :: Int ->    [(Player, Player)] ->    [(Player, Player)]
play 0 h        = h
play i p  
    | i < 0     = p
    | otherwise = play (i-1) $ newPlayers decisions
  where 
    dec p      = decide p $ getPlayerHist p
    decisions  = zip p $ map (bimap dec dec) p   :: [((Player, Player), BattleResult)]
    newPlayers = 
      map (\((p1,p2),cs@(c1,c2)) ->  
             let (a1, a2) = payment cs
             in 
             (p1{getPlayerHist = ((c1, a1),(playerID p2, c2)):(getPlayerHist p1)}
             ,p2{getPlayerHist = ((c2, a2),(playerID p1, c1)):(getPlayerHist p2)}))


--         tournaments  maxIterations  initial Population                      for shuffling   stats for tournaments    with updated histories
runGame :: Int ->       Int ->         ([[(Int->Player, Int)]], Population) -> RandList ->     ([[(Int->Player, Int)]], Population)
runGame _ maxIter res [] = res
runGame 0 maxIter res _  = res
runGame i maxIter res@(hist,ps) rs@(h:t) 
  | i < 0                = res
  | otherwise      = runGame (i-1) maxIter (iterStats:hist, newPopulation) $
                       drop (length iteration) t
  where    
    getPayments = map (snd . fst) . getPlayerHist                                          :: Player -> [Payment]
    iteration   = runIteration (shuffle rs ps) maxIter                                     :: Population
    iterStats   = map (\p -> (p, sum .    
                                 map (sum . getPayments) . 
                                 filter (==(p 0)) $ iteration)
                       ) playerTypes                                                       :: [(Int->Player, Payment)]
    payments    = sum . map snd $ iterStats                                                :: Int
    newPopulationStats = map (\(p, s) -> (p, calcCount s payments (length ps))) iterStats  :: [(Int->Player, Payment)]
    newPopulation      = generatePopulation newPopulationStats                             :: [Player]



startSimulation :: Int -> Int -> Int -> IO ([[(Int->Player, Int)]], Population) 
startSimulation genSize tournaments iterations = do
    g <- getStdGen
    let gen = generatePopulation $ map (\p-> (p, genSize `div` (length playerTypes))) playerTypes
        randList = randoms g
    putStrLn "Simulating Iterated prisoner"
    putStrLn $ "Population " ++ show (stats gen) 
    return $ runGame tournaments iterations ([], gen) randList 


-------------------------- AUXILIARY --------------------------


shuffle :: RandList -> [a] -> [a]                                  
shuffle rands xs = let 
  ys = take (length xs) rands
  in
  map fst $ sortBy (compare `on` snd) (zip xs ys)

makePairs :: [a] -> [(a,a)]
makePairs []       = []
makePairs [_]      = []
makePairs (h:h':t) = (h,h'):(makePairs t)

undoPairs :: [(a,a)] -> [a]
undoPairs [] = []
undoPairs ((a,b):t) = [a,b]++(undoPairs t)

stats :: Population -> [(String, Int)]
stats l = map (\p -> (name p, length $ filter (\e->name e == name p) l)) $
                 nubBy (\p1 p2 -> name p1 == name p2) l

-- tries to preserve the calculated amount for each player as close as possible
--           player payout       overall payout    population size
calcCount :: Int ->              Int ->            Int                -> Int
calcCount _ 0 _ = 0
calcCount _ _ 0 = 0
calcCount a g p = let a' = fromIntegral a
                      g' = fromIntegral g
                      p' = fromIntegral p
                      in round $ a'/g'*p' 
EN

回答 1

Code Review用户

回答已采纳

发布于 2019-12-15 01:44:52

设计

总的来说,我认为您在分离关注点和保持功能独立方面做得很好。我遇到的一个问题是,有太多的元组而不是记录类型,所以有时您要处理的事情并不明显,例如在getPayments = map (snd . fst) . getPlayerHist中,如果不是snd . fst,而是getPayment . getFirstTuple,那就更好了。要做到这一点,您可以替换:

代码语言:javascript
运行
复制
type PlayerHist = [((Choice, Payment), (PlayerID, Choice))]

使用

代码语言:javascript
运行
复制
data FirstTuple = FirstTuple
  {  getChoice :: Choice
  ,  getPayment :: Payment
  }

data SecondTuple = SecondTuple
  { getOpponentID :: PlayerID
  , getOpponentChoice :: Choice
  }

data GameResult = GameResult
  {  getFirstTuple :: FirstTuple
  ,  getSecondTuple :: SecondTuple
  }

type PlayerHist = [GameResult]

最好有更多的描述性的名字,如果可能的话。如果处理嵌套类型变得过于复杂,可以考虑使用lens来简化这一点。

在几个地方,您使用[(Int->Player, Int)]来表示球员列表,其中第二个元组项目是每个玩家的计数,第一个项目获得一个ID并返回一个播放器。我认为您可以像使用表示一样容易地使用[Player],并让调用者首先负责调用generatePopulation。这将简化某些类型的意义,并使其更易于阅读,特别是因为它不清楚[(Int->Player, Int)]是什么。

Simplification

generatePopulation函数中,在列表中使用intercalate [] . map,这相当于concatMap,这应该更容易理解。请注意,以下类型是等价的:

代码语言:javascript
运行
复制
Prelude Data.List> :t \f -> intercalate [] . map f
\f -> intercalate [] . map f :: (a1 -> [a2]) -> [a1] -> [a2]
Prelude Data.List> :t \f -> concatMap f
\f -> concatMap f :: Foldable t => (a -> [b]) -> t a -> [b]

在相同的函数中,您还使用mapzip,with与zipWith相同。

这就是我想出来的:

代码语言:javascript
运行
复制
generatePopulation :: [(Int->Player, Int)] -> Population
generatePopulation = zipWith (flip ($)) [1..] .
                     concatMap (\(f, count) -> replicate count f)

shuffle中,您可以用ys = zipWith const rands xs代替ys = take (length xs) rands。这应该在1次遍历中做同样的事情,而不是2次。const被定义为const a b = a,所以当您将这两个元素压缩在一起时,您将只从第一个列表rands中获取元素。当较短的列表耗尽时,zip就停止了,所以剩下的长度是length xs。您可以看到一些类似的这里。示例。

票数 3
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/233969

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档