【问题标题】:How to keep track of state in Haskell?如何在 Haskell 中跟踪状态?
【发布时间】:2020-08-28 18:23:10
【问题描述】:

我想创建一个系统,该系统必须跟踪不断变化的状态才能发现结果。输入数据的格式是我分解成字符的字符串列表。

例如 ['ynnynyy','ynynyn',...]

在命令式风格中,逻辑如下:

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

可变变量的使用让我很难将模式转换为 Haskell。

我目前正在尝试使用嵌套函数创建解决方案:

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

我对 Haskell 语法和一般的函数式编程非常生疏并且缺乏实践。我知道 State monad,但不确定如何正确实现它。

任何分享的想法可以让我朝着正确的方向前进,或者帮助我调整我的思路,这有点卡在命令式中,我可以提出解决方案!

非常感谢任何帮助!

【问题讨论】:

  • 不需要状态单子。只需使用列表递归并将状态变量作为参数传递。或者创建一个包含所有四个的记录,并将该记录作为单个值传递。或者然后将其与折叠一起使用。
  • 看来您本质上是想计算 y 和 n 的数量。为此,您只需将concat 的字符列表合并为一个,将filter 的列表合并为相应的字符,然后取length。大多数此类问题通常都有纯粹的功能解决方案,您不应该将状态作为第一冲动。
  • 您好,感谢您的意见。数组的每个元素都需要分别计算 ys 和 ns。每个元素的 ys 和 ns 的顺序也很重要。

标签: haskell recursion functional-programming state monads


【解决方案1】:

没有State monad 也可以做到这一点。事实上,State monad 大致使隐式传递状态更容易,这样您就不需要手动将它从一个项目带到另一个项目。此外,State 通常更好,因为可以在通用的 State 上定义辅助函数,因此很有用。

然而,首先尝试在没有State 的情况下实现它可能更有用,因此只需将结果的状态从一个函数传递到下一个函数。我们可以定义一个数据类型Outcome来存储四个数字:

data Outcome = Outcome
  { yCount :: Int
  , nCount :: Int
  , yTotal :: Int
  , nTotal :: Int
  } deriving Show

那么我们的初始状态是:

initial :: Outcome
initial = Outcome 0 0 0 0

接下来我们可以定义一个函数,在值达到 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

现在我们可以创建一个接受输入元素的函数,并相应地更新Outcome

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}

所以现在我们可以使用foldl :: Foldable f => (b -> a -> b) -> b -> f a -> b 以初始Outcome 开始,然后每次取一个列表项,并相应地更新Outcome,直到我们得到最终结果。所以我们可以定义一个updateSequence

updateSequence :: Foldable f => Outcome -> f Char -> Outcome
updateSequence = foldl updateInput

例如,如果我们使用 initial 作为初始 Outcome 并将 "ynynyy" 作为字符串传递,我们会得到:

Prelude> updateSequence initial "ynynyy"
Outcome {yCount = 4, nCount = 2, yTotal = 0, nTotal = 0}

所以我们可以使用另一个foldl 来处理列表列表(或更通用的Foldables):

updateSequence2 :: (Foldable f, Foldable g) => Outcome -> f (g Char) -> Outcome
updateSequence2 = foldl updateSequence

那么我们就可以得到最终的值了:

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}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-11-16
    • 1970-01-01
    • 1970-01-01
    • 2019-03-02
    • 2011-11-14
    • 1970-01-01
    • 2021-09-30
    • 1970-01-01
    相关资源
    最近更新 更多