module WordPuzzle ( WordPuzzle(..)
, validateSize
, validateLetters
, nineLetters
, spellingBee
, solve
, solver
, ValidationError(..)
, validate
, toEither
) where
import Data.Bits (shiftL, (.&.), (.|.))
import Data.Bool (bool)
import qualified Data.ByteString.Char8 as BS
import Data.Char (isLower, ord)
import Data.Functor.Contravariant (Predicate (..), getPredicate)
import Data.Ix (inRange)
import Data.List (nub)
import Data.Validation (Validation (..), toEither)
import qualified System.IO.Streams as Streams
data WordPuzzle = WordPuzzle
{
WordPuzzle -> Int
size :: Int
, WordPuzzle -> Char
mandatory :: Char
, WordPuzzle -> String
letters :: String
, WordPuzzle -> String
dictionary :: FilePath
, WordPuzzle -> Bool
repeats :: Bool
} deriving (Int -> WordPuzzle -> ShowS
[WordPuzzle] -> ShowS
WordPuzzle -> String
(Int -> WordPuzzle -> ShowS)
-> (WordPuzzle -> String)
-> ([WordPuzzle] -> ShowS)
-> Show WordPuzzle
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> WordPuzzle -> ShowS
showsPrec :: Int -> WordPuzzle -> ShowS
$cshow :: WordPuzzle -> String
show :: WordPuzzle -> String
$cshowList :: [WordPuzzle] -> ShowS
showList :: [WordPuzzle] -> ShowS
Show)
data ValidationError =
InvalidSize (Int, Int) Int
| InvalidLetters String
| UnexpectedValue String
deriving (ValidationError -> ValidationError -> Bool
(ValidationError -> ValidationError -> Bool)
-> (ValidationError -> ValidationError -> Bool)
-> Eq ValidationError
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: ValidationError -> ValidationError -> Bool
== :: ValidationError -> ValidationError -> Bool
$c/= :: ValidationError -> ValidationError -> Bool
/= :: ValidationError -> ValidationError -> Bool
Eq)
instance Show ValidationError where
show :: ValidationError -> String
show (InvalidSize (Int
en1,Int
en2) Int
an) = String
"expected value in range ("
String -> ShowS
forall a. [a] -> [a] -> [a]
++ Int -> String
forall a. Show a => a -> String
show Int
en1 String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
", " String -> ShowS
forall a. [a] -> [a] -> [a]
++ Int -> String
forall a. Show a => a -> String
show Int
en2 String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
") got " String -> ShowS
forall a. [a] -> [a] -> [a]
++ Int -> String
forall a. Show a => a -> String
show Int
an
show (InvalidLetters String
ls) = String
"expected 4-9 unique lowercase letters, got " String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
ls
show (UnexpectedValue String
xs) = String
"unexpected value " String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
xs String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
" for parameter"
validate :: Bool -> Int -> String -> FilePath -> Validation [ValidationError] WordPuzzle
validate :: Bool
-> Int
-> String
-> String
-> Validation [ValidationError] WordPuzzle
validate Bool
_ Int
_ [] String
_ = [ValidationError] -> Validation [ValidationError] WordPuzzle
forall err a. err -> Validation err a
Failure [String -> ValidationError
InvalidLetters String
"empty letters"]
validate Bool
r Int
s (Char
m:String
ls) String
d =
Int -> Char -> String -> String -> Bool -> WordPuzzle
WordPuzzle (Int -> Char -> String -> String -> Bool -> WordPuzzle)
-> Validation [ValidationError] Int
-> Validation
[ValidationError] (Char -> String -> String -> Bool -> WordPuzzle)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Int -> Validation [ValidationError] Int
validateSize Int
s
Validation
[ValidationError] (Char -> String -> String -> Bool -> WordPuzzle)
-> Validation [ValidationError] Char
-> Validation
[ValidationError] (String -> String -> Bool -> WordPuzzle)
forall a b.
Validation [ValidationError] (a -> b)
-> Validation [ValidationError] a -> Validation [ValidationError] b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Char -> Validation [ValidationError] Char
forall a. a -> Validation [ValidationError] a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Char
m
Validation
[ValidationError] (String -> String -> Bool -> WordPuzzle)
-> Validation [ValidationError] String
-> Validation [ValidationError] (String -> Bool -> WordPuzzle)
forall a b.
Validation [ValidationError] (a -> b)
-> Validation [ValidationError] a -> Validation [ValidationError] b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> String -> Validation [ValidationError] String
validateLetters (Char
mChar -> ShowS
forall a. a -> [a] -> [a]
:String
ls)
Validation [ValidationError] (String -> Bool -> WordPuzzle)
-> Validation [ValidationError] String
-> Validation [ValidationError] (Bool -> WordPuzzle)
forall a b.
Validation [ValidationError] (a -> b)
-> Validation [ValidationError] a -> Validation [ValidationError] b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> String -> Validation [ValidationError] String
forall a. a -> Validation [ValidationError] a
forall (f :: * -> *) a. Applicative f => a -> f a
pure String
d
Validation [ValidationError] (Bool -> WordPuzzle)
-> Validation [ValidationError] Bool
-> Validation [ValidationError] WordPuzzle
forall a b.
Validation [ValidationError] (a -> b)
-> Validation [ValidationError] a -> Validation [ValidationError] b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Bool -> Validation [ValidationError] Bool
forall a. a -> Validation [ValidationError] a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Bool
r
validateSize :: Int -> Validation [ValidationError] Int
validateSize :: Int -> Validation [ValidationError] Int
validateSize Int
s = Validation [ValidationError] Int
-> Validation [ValidationError] Int
-> Bool
-> Validation [ValidationError] Int
forall a. a -> a -> Bool -> a
bool ([ValidationError] -> Validation [ValidationError] Int
forall err a. err -> Validation err a
Failure [(Int, Int) -> Int -> ValidationError
InvalidSize (Int
4,Int
9) Int
s]) (Int -> Validation [ValidationError] Int
forall err a. a -> Validation err a
Success Int
s) (Int -> Bool
isSize Int
s)
validateLetters :: String -> Validation [ValidationError] String
validateLetters :: String -> Validation [ValidationError] String
validateLetters String
ls = Validation [ValidationError] String
-> Validation [ValidationError] String
-> Bool
-> Validation [ValidationError] String
forall a. a -> a -> Bool -> a
bool ([ValidationError] -> Validation [ValidationError] String
forall err a. err -> Validation err a
Failure [String -> ValidationError
InvalidLetters String
ls]) (String -> Validation [ValidationError] String
forall err a. a -> Validation err a
Success String
ls) (String -> Bool
isLetters String
ls)
isSize :: Int -> Bool
isSize :: Int -> Bool
isSize = (Int, Int) -> Int -> Bool
forall a. Ix a => (a, a) -> a -> Bool
inRange (Int
4,Int
9)
isLetters :: String -> Bool
isLetters :: String -> Bool
isLetters String
ls =
(Int, Int) -> Int -> Bool
forall a. Ix a => (a, a) -> a -> Bool
inRange (Int
4,Int
9) Int
n Bool -> Bool -> Bool
&& (Char -> Bool) -> String -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
isLower String
ls Bool -> Bool -> Bool
&& String -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length (ShowS
forall a. Eq a => [a] -> [a]
nub String
ls) Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
n
where
n :: Int
n = String -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length String
ls
hasMandatory :: Char -> BS.ByteString -> Bool
hasMandatory :: Char -> ByteString -> Bool
hasMandatory = Char -> ByteString -> Bool
BS.elem
solve :: WordPuzzle -> IO ()
solve :: WordPuzzle -> IO ()
solve WordPuzzle
wordpuzzle = String -> (InputStream ByteString -> IO ()) -> IO ()
forall a. String -> (InputStream ByteString -> IO a) -> IO a
Streams.withFileAsInput (WordPuzzle -> String
dictionary WordPuzzle
wordpuzzle) ((InputStream ByteString -> IO ()) -> IO ())
-> (InputStream ByteString -> IO ()) -> IO ()
forall a b. (a -> b) -> a -> b
$ \InputStream ByteString
is -> do
InputStream ByteString
lines_is <- InputStream ByteString -> IO (InputStream ByteString)
Streams.lines InputStream ByteString
is
InputStream ByteString
filtered_is <- WordPuzzle -> InputStream ByteString -> IO (InputStream ByteString)
solver WordPuzzle
wordpuzzle InputStream ByteString
lines_is
InputStream ByteString
printed_is <- (ByteString -> IO ())
-> InputStream ByteString -> IO (InputStream ByteString)
forall a b. (a -> IO b) -> InputStream a -> IO (InputStream a)
Streams.mapM_ ByteString -> IO ()
BS.putStrLn InputStream ByteString
filtered_is
InputStream ByteString -> IO ()
forall a. InputStream a -> IO ()
Streams.skipToEof InputStream ByteString
printed_is
solver :: WordPuzzle -> Streams.InputStream BS.ByteString -> IO (Streams.InputStream BS.ByteString)
solver :: WordPuzzle -> InputStream ByteString -> IO (InputStream ByteString)
solver WordPuzzle
puzzle = (ByteString -> Bool)
-> InputStream ByteString -> IO (InputStream ByteString)
forall a. (a -> Bool) -> InputStream a -> IO (InputStream a)
Streams.filter (Predicate ByteString -> ByteString -> Bool
forall a. Predicate a -> a -> Bool
getPredicate (Predicate ByteString
pS Predicate ByteString
-> Predicate ByteString -> Predicate ByteString
forall a. Semigroup a => a -> a -> a
<> Predicate ByteString
pM Predicate ByteString
-> Predicate ByteString -> Predicate ByteString
forall a. Semigroup a => a -> a -> a
<> Predicate ByteString
pL))
where
pS :: Predicate ByteString
pS = (ByteString -> Bool) -> Predicate ByteString
forall a. (a -> Bool) -> Predicate a
Predicate ((ByteString -> Bool) -> Predicate ByteString)
-> (ByteString -> Bool) -> Predicate ByteString
forall a b. (a -> b) -> a -> b
$ Bool -> Int -> ByteString -> Bool
checkLength (WordPuzzle -> Bool
repeats WordPuzzle
puzzle) (WordPuzzle -> Int
size WordPuzzle
puzzle)
pM :: Predicate ByteString
pM = (ByteString -> Bool) -> Predicate ByteString
forall a. (a -> Bool) -> Predicate a
Predicate ((ByteString -> Bool) -> Predicate ByteString)
-> (ByteString -> Bool) -> Predicate ByteString
forall a b. (a -> b) -> a -> b
$ Char -> ByteString -> Bool
hasMandatory (WordPuzzle -> Char
mandatory WordPuzzle
puzzle)
pL :: Predicate ByteString
pL = (ByteString -> Bool) -> Predicate ByteString
forall a. (a -> Bool) -> Predicate a
Predicate ((ByteString -> Bool) -> Predicate ByteString)
-> (ByteString -> Bool) -> Predicate ByteString
forall a b. (a -> b) -> a -> b
$ Bool -> String -> ByteString -> Bool
checkLettersPool (WordPuzzle -> Bool
repeats WordPuzzle
puzzle) (WordPuzzle -> String
letters WordPuzzle
puzzle)
checkLength :: Bool
-> Int
-> BS.ByteString
-> Bool
checkLength :: Bool -> Int -> ByteString -> Bool
checkLength Bool
True Int
s = (Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
s) (Int -> Bool) -> (ByteString -> Int) -> ByteString -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> Int
BS.length
checkLength Bool
False Int
s = (Int, Int) -> Int -> Bool
forall a. Ix a => (a, a) -> a -> Bool
inRange (Int
s, Int
9) (Int -> Bool) -> (ByteString -> Int) -> ByteString -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> Int
BS.length
checkLettersPool :: Bool
-> String
-> BS.ByteString
-> Bool
checkLettersPool :: Bool -> String -> ByteString -> Bool
checkLettersPool Bool
True String
ls = String -> ByteString -> Bool
spellingBee String
ls
checkLettersPool Bool
False String
ls = String -> ByteString -> Bool
nineLetters String
ls
nineLetters ::
String
-> BS.ByteString
-> Bool
nineLetters :: String -> ByteString -> Bool
nineLetters String
ls = (Int, Bool) -> Bool
forall a b. (a, b) -> b
snd ((Int, Bool) -> Bool)
-> (ByteString -> (Int, Bool)) -> ByteString -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((Int, Bool) -> Char -> (Int, Bool))
-> (Int, Bool) -> ByteString -> (Int, Bool)
forall a. (a -> Char -> a) -> a -> ByteString -> a
BS.foldl' (Int, Bool) -> Char -> (Int, Bool)
forall {a}. (Bits a, Num a) => (a, Bool) -> Char -> (a, Bool)
step (Int
0 :: Int, Bool
True)
where
step :: (a, Bool) -> Char -> (a, Bool)
step (a
mask, Bool
ok) Char
c
| Bool -> Bool
not Bool
ok = (a
mask, Bool
False)
| Char
c Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` String
ls = (a
mask, Bool
False)
| a
mask a -> a -> a
forall a. Bits a => a -> a -> a
.&. a
bit a -> a -> Bool
forall a. Eq a => a -> a -> Bool
/= a
0 = (a
mask, Bool
False)
| Bool
otherwise = (a
mask a -> a -> a
forall a. Bits a => a -> a -> a
.|. a
bit, Bool
True)
where
bit :: a
bit = a
1 a -> Int -> a
forall a. Bits a => a -> Int -> a
`shiftL` (Char -> Int
ord Char
c Int -> Int -> Int
forall a. Num a => a -> a -> a
- Char -> Int
ord Char
'a')
spellingBee ::
String
-> BS.ByteString
-> Bool
spellingBee :: String -> ByteString -> Bool
spellingBee String
ls = (Char -> Bool) -> ByteString -> Bool
BS.all (Char -> String -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
ls)