【问题标题】:Simplifying use of lambda expressions and memoizing a Map in the State Monad简化 lambda 表达式的使用并在 State Monad 中记忆 Map
【发布时间】:2012-04-01 08:39:02
【问题描述】:

这个函数比它的递归版本快得多:

crossSubstrings :: String -> String -> [(String,String)]
crossSubstrings string1 string2 = [(substr1,substr2) | substr1 <- inits string1,
                                                       substr2 <- inits string2]

type Distances = Map.Map (String,String) Int

editDistanceMemoized :: String -> String -> Int
editDistanceMemoized s1 s2 = 
   let 
     substrings = s1 `crossSubstrings` s2
     distances = foldl (editDistance) emptyMap substrings
   in
     distances Map.! (s1,s2)
   where 
     emptyMap = Map.fromList []
     editDistance :: Distances -> (String,String) -> Distances
     editDistance map ([],s1) = map `Map.union` getMap [] s1 (length s1)
     editDistance map (s1,[]) = map `Map.union` getMap s1 [] (length s1)
     editDistance map (s1,s2) = map `Map.union` getMap s1 s2 (cost map s1 s2)
     getMap s1 s2 d = Map.fromList [((s1,s2),d)]
     insertionPCost = \m -> \s1 -> \s2 -> m Map.! (s1, init s2) + 1
     deletionPCost = \m -> \s1 -> \s2 -> m Map.! (init s1, s2)  + 1
     substitutionPCost = \m -> \s1 -> \s2 -> m Map.! (init s1, init s2)
                                             + substitutionCostIfNEQ s1 s2
     substitutionCostIfNEQ = \s1 -> \s2 -> if (last s1 == last s2) then 0 else 2
     cost = \m -> \s1 -> \s2 -> minimum [insertionPCost m s1 s2,
                                         deletionPCost m s1 s2,
                                         substitutionPCost m s1 s2] 

但是(第一个问题),我觉得可以避免一些 lambda(它看起来不是重复的吗?特别看cost)。有没有办法写minimum

此外,State Monad 可用于传播地图(而不是使用foldl?)。尽管阅读了 State.&gt;&gt;=State.id 的行为方式,但我不能 100% 确定签名应该是什么样子(第二个问题)。

我想到了这个,状态是“下一对要测量的字符串”,而 Distances 包含记忆的距离。

 editDistance :: State Distances (String,String) -> State Distances ()?

【问题讨论】:

  • 顺便提一下,您的emptyMapMap.empty 相同。

标签: haskell memoization


【解决方案1】:

insertionPCostdeletionPCostsubstitutionPCostsubstitutionCostIfNEQ 只能相互调用,cost,并且始终使用相同的参数(除了 substitutionCostIfNEQ 不使用 m);所以我们可以像这样重新排列它们:

cost = \m -> \s1 -> \s2 -> minimum [insertionPCost, deletionPCost, substitutionPCost] 
  where insertionPCost = m Map.! (s1, init s2) + 1
        deletionPCost = m Map.! (init s1, s2)  + 1
        substitutionPCost = m Map.! (init s1, init s2) + substitutionCostIfNEQ
        substitutionCostIfNEQ = if (last s1 == last s2) then 0 else 2

而显式的 lambdas 并没有得到任何东西,所以重写以更清楚:

cost m s1 s2 = minimum [insertionPCost, deletionPCost, substitutionPCost] 
  where insertionPCost = m Map.! (s1, init s2) + 1
        deletionPCost = m Map.! (init s1, s2)  + 1
        substitutionPCost = m Map.! (init s1, init s2) + substitutionCostIfNEQ
        substitutionCostIfNEQ = if (last s1 == last s2) then 0 else 2

回答你的第二个问题,目前你有

editDistance :: Distances -> (String,String) -> Distances

如果你改用State,那就是

editDistance :: (String,String) -> State Distances ()

也就是说,editDistance 将是一个接受 (String,String) 的函数,并产生与 Distances 状态交互的东西,并且没有其他有意义的结果。

但是。

首先,我认为您使用foldl 没有任何问题。

其次,你从来没有真正使用过累计值,那会是什么状态。你用它来创造一个新的价值,但你没有在其中查找任何东西。所以你不需要State,你只需要Writer

editDistance :: (String,String) -> Writer Distances ()

也就是说,editDistance 将是一个接受 (String,String) 的函数,并产生一些添加到 Distances 累加器的东西,并且没有其他有意义的结果。

(这里有一个微妙之处:Writer 的第一个参数必须是 Monoid,并且它必须使用对您有用的组合操作 (mappend);嗯,Maps 是 @ 987654347@s,他们的mappend 与您在原始editDistance 中使用的union 相同,所以一切正常。)

【讨论】:

  • 哦,谢谢,我记得尝试了两个嵌套的 where's 并失败了,现在它可以工作并且看起来更干净了。
  • 谢谢! foldl 没什么问题,我就是想学一元解法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-04-03
  • 2015-10-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多