Reputation: 541
I would like to create a system that has to keep track of a changing state in order to discover an outcome. The format of the input data is a list of strings that I break down into chars.
For example ['ynynyy','ynynyn',...]
In an imperative style the logic would be as follows:
yCount=0,xCount=0,yTotal=0,nTotal=0
for x in input:
for y in x:
if y is 'y':
yCount++
else:
nCount++
if yCount is 10:
yTotal++
if nCount is 10:
nTotal++
if yCount is 10 or nCount is 10:
yCount=0
nCount=0
The use of mutable variables makes it difficult for me to translate the pattern to Haskell.
I am currently working to try and create a solution using nested functions:
main = do
parseInputData input
parseInputData :: [String] -> IO () --would like to write solution to file
parseInputData x = do
let result = map getSingleValue (lines input)
getSingleValue :: [Char] -> String --would like to return string representation of outcome
getSingleValue (x:xs)
| x == 'y' .. --update state and continue
| x == 'n' .. --update state and continue
| otherwise .. --return final state
I am very rusty and out of practice with Haskell syntax and also functional programming in general. I am aware of the State monad but unsure how to correctly implement it.
Any ideas shared that would get me going in the right direction or help me adjust my train of thought which is a bit stuck in imperative would allow me to produce a solution!
Any help is much appreciated!
Upvotes: 3
Views: 1181
Reputation: 477533
One can do this without a State
monad. In fact a State
monad roughly makes it easier to make passing the state implicit, such that you do not manually need to carry it from one item to another. Furthermore a State
is often better, since one can define helper functions on a generic State
that are thus useful.
It might however be more useful to first try to implement this without a State
, and thus simply pass the state of the outcome from one function to the next one. We can define a data type Outcome
that stores the four numbers:
data Outcome = Outcome
{ yCount :: Int
, nCount :: Int
, yTotal :: Int
, nTotal :: Int
} deriving Show
Then our initial state is:
initial :: Outcome
initial = Outcome 0 0 0 0
Next we thus can define a function that will "normalize" the values in case on of the values hits 10:
normalize :: Outcome -> Outcome
normalize (Outcome y0 n0 y1 n1) | y10 || n10 = Outcome 0 0 y1' n1'
where y10 = y0 == 10
n10 = n0 == 10
y1' = y1 + fromEnum y10
n1' = n1 + fromEnum n10
normalize o = o
Now we can thus make a function that takes an input element, and updates the Outcome
accordingly:
updateInput :: Outcome -> Char -> Outcome
updateInput o@Outcome {yCount=y0} 'y' = normalize o{yCount=y0+1}
updateInput o@Outcome {nCount=n0} 'n' = normalize o{nCount=n0+1}
So now we can make use of foldl :: Foldable f => (b -> a -> b) -> b -> f a -> b
to start with an initial Outcome
, and then each time take an item of a list, and update the Outcome
accordingly until we have the final outcome. So we can define an updateSequence
with:
updateSequence :: Foldable f => Outcome -> f Char -> Outcome
updateSequence = foldl updateInput
For example if we use initial
as initial Outcome
and we pass "ynynyy"
as string we get:
Prelude> updateSequence initial "ynynyy"
Outcome {yCount = 4, nCount = 2, yTotal = 0, nTotal = 0}
So we can use another foldl
to work over a list of lists (or more generic Foldable
s):
updateSequence2 :: (Foldable f, Foldable g) => Outcome -> f (g Char) -> Outcome
updateSequence2 = foldl updateSequence
Then we can obtain the final value with:
Prelude> updateSequence2 initial ["ynynyy","ynynyn"]
Outcome {yCount = 7, nCount = 5, yTotal = 0, nTotal = 0}
Prelude> updateSequence2 initial ["ynynyy","ynynyn", "nnnnyn"]
Outcome {yCount = 0, nCount = 0, yTotal = 0, nTotal = 1}
Upvotes: 3