简体   繁体   中英

QuickCheck Haskell - generating random lottery tickets

This works :

genAnimal :: Gen String
genAnimal = do
  animals <- shuffle ["tiger","rabbit","dragon","snake","rat","ox","pig","sheep","horse","monkey","dog"]
  return (head animals)

genWinner :: Gen String
genWinner = do 
  animal <- genAnimal
  prize <- choose (10::Int,1000::Int)
  return (unwords (replicate 3 animal) ++ " " ++ show prize)

genTicket :: Gen String
genTicket = do 
  animals <- replicateM 3 genAnimal
  prize <- choose (10::Int,1000::Int)
  return (unwords animals ++ " " ++ show prize)

genTickets :: Gen [String]
genTickets = do
  tickets <- replicateM 6 (oneof [genWinner, genTicket])
  return tickets

But it looks just ungainly, is there a more sensible way to combine these generators? It basically picks three random animals and then a random prize and then makes six tickets.

As @AJFarmar noted, you can use the elements combinator to pick a random element from a list, instead of taking the head of a shuffled version:

genAnimal :: Gen String
genAnimal = elements ["tiger","rabbit","dragon","snake","rat","ox",
                      "pig","sheep","horse","monkey","dog"]

Also, an applicative style can generally be more succinct, particularly if you introduce helper functions for the pure computations that combine the results:

genPrize :: Gen Int
genPrize = choose (10,1000)

genTicket :: Gen String
genTicket = ticket <$> replicateM 3 genAnimal <*> genPrize
  where ticket animals prize = unwords animals ++ " " ++ show prize

A few other miscellaneous improvements might come from:

  • shifting your perspective on your generators to generate words that you combine at the end, instead of strings that have to be unword ed and glued together with blanks;
  • or better yet, introducing some datatypes to better model your problem and providing a utility function to print it in the appropriate String form only when needed;
  • getting rid of the gen prefixes that clutter everything up
  • using the vectorOf alias for replicateM which is a little more readable in this context.

This might lead to something like:

{-# OPTIONS_GHC -Wall #-}

module Lottery where

import Test.QuickCheck

type Animal = String
data Ticket = Ticket [Animal] Int

pticket :: Ticket -> String
pticket (Ticket ts prz) = unwords ts ++ ' ':show prz

tickets :: Gen [Ticket]
tickets = vectorOf 6 (oneof [winner, loser])
  where winner = Ticket <$> (replicate 3 <$> animal) <*> prize
        loser  = Ticket <$> (vectorOf 3 animal)      <*> prize
        animal = elements ["tiger","rabbit","dragon","snake","rat","ox",
                           "pig","sheep","horse","monkey","dog"]
        prize  = choose (10,1000)

main :: IO ()
main = print . map pticket =<< generate tickets

which I think looks pretty good.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM