fix: use average instead of max as evaluation function as it gives a slightly more efficient algorithm

This commit is contained in:
wi11-holdsworth 2025-10-07 19:32:42 +11:00
parent 8f07d1a83c
commit 136ba9d1d5

View file

@ -40,7 +40,7 @@
-- --
-- To pick the best next guess, we filter the game state to contain only -- To pick the best next guess, we filter the game state to contain only
-- targets that are consistent with the received feedback from the previous -- targets that are consistent with the received feedback from the previous
-- guess. We then calculate, for each candidate, the maximum number of guesses -- guess. We then calculate, for each candidate, the average number of guesses
-- that would remain if we chose it as the next guess. We can then choose the -- that would remain if we chose it as the next guess. We can then choose the
-- candidate that has the smallest of this number as the next guess. -- candidate that has the smallest of this number as the next guess.
@ -54,8 +54,9 @@ module Proj2
) )
where where
import Data.Function (on)
import Data.List import Data.List
import Data.Map qualified as M import Data.Map qualified as Map
import Data.Ord (comparing) import Data.Ord (comparing)
import Data.Set qualified as S import Data.Set qualified as S
@ -178,8 +179,8 @@ initialGuess = (bestFirstGuess, allChords)
-- strategy: -- strategy:
-- 1. reduce the size of the search space by removing all guesses inconsistent -- 1. reduce the size of the search space by removing all guesses inconsistent
-- with the answer received for the previous guess. -- with the answer received for the previous guess.
-- 2. for each candidate, calculate the worst case number of remaining target -- 2. for each candidate, calculate the average number of remaining targets
-- 3. choose the candidate with the smallest of this number -- 3. choose the candidate with the smallest average
-- --
nextGuess :: ([Pitch], GameState) -> (Int, Int, Int) -> ([Pitch], GameState) nextGuess :: ([Pitch], GameState) -> (Int, Int, Int) -> ([Pitch], GameState)
nextGuess (prevGuess, state) prevFeedback = (chosen, newState) nextGuess (prevGuess, state) prevFeedback = (chosen, newState)
@ -190,11 +191,12 @@ nextGuess (prevGuess, state) prevFeedback = (chosen, newState)
scored = map (\x -> (x, score x candidates)) candidates scored = map (\x -> (x, score x candidates)) candidates
candidates = filter (\x -> prevFeedback == feedback prevGuess x) state candidates = filter (\x -> prevFeedback == feedback prevGuess x) state
-- maximum number of possible targets per candidate -- average number of possible targets per candidate
score candidate candidates = score candidate candidates = ((/) `on` fromIntegral) (sum l) (length l)
maximum $ where
M.elems $ l =
M.fromListWith Map.elems $
Map.fromListWith
(+) (+)
[(feedback candidate target, 1) | target <- candidates] [(feedback candidate target, 1) | target <- candidates]