Performance versus elegant code, a never ending trade off [Desiderius part #19]

Generating with constraints

So, the next step, actually generating things. As I wrote last week, my strategy first was to generate entirely random hands. But! I realized that if we do that, we make some assumptions about our partner, because they will have an average hand. I then wanted to generate a random hand for my partner given a certain constraint, in this case that our partner has 0 points. This way, we get the best bid for just our hand. Also, in a later stage I will want to generate hands with other constraints, for the next step of our bidding system.

My first solution was to use the fits function I already wrote. Awesome, code reuse to the max, right? The fits function, if you don’t remember, checks if a certain hand complies to a condition:

let rec fits (cards : Hand) (c : Condition) : bool = 
    match c with
    | NCards(min, max, s) -> cardsofSuitinHand cards s >= min && cardsofSuitinHand cards s <= max 
    | NPoints(min, max) -> pointsinHand cards >= min && pointsinHand cards <= max 
    | And(p, q) -> fits cards p && fits cards q
    | Or(p, q) -> fits cards p || fits cards q

So, you give fits a hand and a condition, and it returns a boolean whether the hand satisfies the condition. We can use that, by just generating a random hand until it matches:

//this is an example of a hand constraint
 let constreent = fun x -> Desi.fits (Desi.Hand(x)) (Desi.points 0 12) *

* if you are wondering why my variable is called ‘constreent’ that is because constraint is reserved for future use.

We can now just generate random hands until we meet the condition:

let randomHandplusRemainderwithConstraint cardSet (constreent: Desi.Card List -> bool)  = 
   let mutable continueLooping = true
   let oneHand = oneRandomHand cardSet
   while continueLooping do
      let oneHand = oneRandomHand cardSet
      // Generate a random hand
      if constreent oneHand then 
         continueLooping
   (oneHand , except cardSet oneHand)

But again, that is very very slow… 🙁 Since in the first situation, I just needed hands with no points, I could solve it with a constraint on individual cards, like so:

let randomHandplusRemainderwithCardConstraint cardSet (constreent: Desi.Card -> bool) = 
   //get all the cards that fit the card constraint:
   let fitting = List.filter (constreent) cardSet

   //take 13 random from those:
   let oneHand = oneRandomHand fitting
  (oneHand , except cardSet oneHand)

Calling it with a constreent on cards:

let constreent = fun x -> Desi.cardtoPoint x <= 0
let (hand3, remainder) = randomHandplusRemainderwithCardConstraint remainder1 constreent

Finally, I made a relatively elegant and performant solution. The idea is to use conditions instead of functions on hands, and recursively break them down:

let rec randomHandwithCondition cardSet stillToTake (c: Desi.Condition)  =   
   match stillToTake with
   | 0 -> []
   | n -> let oneCard = oneRandomCard cardSet
             oneCard :: (randomHandwithCondition cardSet (n-1) (updateCondition c oneCard ))

The updateCondition function takes a card and a condition and makes it smaller

    let updateCondition condition card = 
       match condition with 
       | Desi.NPoints(min,max)    -> let x = Desi.cardtoPoint card
                                     Desi.NPoints(min-x,max-x)
       | Desi.NCards(min, max, s) -> if helper.getSuit card = s then Desi.NCards(min-1, max-1, s) else Desi.NCards(min, max, s)