【问题标题】:Can a guard have more than one result?后卫可以有多个结果吗?
【发布时间】:2021-02-21 14:26:17
【问题描述】:

作为 Haskell 的新手,以惯用的方式思考是一项挑战。我有一份双人名单。我想根据两张地图对二元组成员进行加权,一张指示权重方向,另一张提供权重本身。在下面的代码中,1 表示 dyad 的底部成员接受重量; -1 二元组的最高成员接受重量; 0 两个成员都获得权重。在所有情况下,决定权重和方向的是对偶成员之间的差异

我的问题是,在权重相等的情况下,如何重新使用顶部和底部权重分配的定义?到目前为止,我咨询过的每个消息来源似乎都表明守卫只能有一个结果——我怀疑这是正确的 Haskell 方式......

allocateWeight :: [[Integer]] -> (Integer, Integer, Maybe Double)
allocateWeight [x, y]
    |direction <= Just 1 = assignBottom
    |direction <= Just (-1)  = assignTop
    |direction <= Just 0 = (?? assignBottom and assignTop ??)
  where diff = (abs(x!!1 - y!!1))
      direction = Map.lookup diff getWeightDirection 
      weight = Map.lookup diff schemaCAT
      assignBottom = (head x, last x, weight)
      assignTop = (head y,  last y, weight)

好的,我已被要求进一步澄清。我会削减非必需品,因为它们只会使问题变得模糊。

第 1 阶段:从值列表开始,例如:[6, 3, 8, 11, 2]:值限制在 1 到 12 之间。

第 2 阶段:将它们排列成对子:[(6,3),(6,8), (6,11), (6,2), (3, 8), (3, 11),(3 , 2),(8, 11)(8, 2),(11,2)]

Stage 3:获取每对的绝对差:[(3),(2),(5),(4),(5),(8),(1),(3),(6) ,(9)]

第4阶段:根据他们之间的差异,每个对子中的一个成员(6个除外)将获得权重;这是在以下地图中预先确定的:

getWeightDiretion :: Map.Map Integer Integer -- determine weight direction 
getWeightDirection = Map.fromList $
 [(1, -1),
  (2, -1), 
  (3, 1),
  (4, 1),
  (5, -1),
  (6, 0),
  (7, 1),
  (8, -1),
  (9, -1),
  (10, 1),
  (11, 1),
  (12, 1))]

如前所述,如果 map lookup 的值为 1,则权重到底部; -1 到顶部。问题是,当查询键为 6 时, 个二元组成员的权重均不大于另一个:也就是说,它们的权重均等。权重也是通过在此地图中查找键来预先确定的:

schemaCAT :: Map.Map Integer Double  --Cross-At-Tail weighting scheme
schemaCAT = Map.fromList $
[(12, 0.985),
 (11, -0.7),
 (10, 0.2),
 (9, 0.4), 
 (8, 0.6),
 (7, 0.9),
 (6, 0.08),
 (5, 0.8),
 (4, 0.7),
 (3, 0.5),
 (2, 0.1),
 (1, -0.8),
 (999, 0.25)]

allocateWeights 函数的输入格式为 [[(-1, 6), (0, 3)], [.... 其中每个子列表中每个元组的第一个成员是转置因子- 这里不相关;第二个是排列对之一。输入中的每个子列表代表一个排列。 allocateWeights 函数直接对每个子列表中每个元组的 x!!1 进行操作。

应用上述内容后,我应该得到一个元组列表 [(-1, 3, 0.5)....] 第二个元组成员是接收权重的元组成员,第三个是权重本身(我将省略元组的第一个成员是什么并不重要。与键 999 相同,这是一个特殊情况)。

据我了解,我有两个问题。首先,maps 返回 Maybes 并且在守卫中使用这些值是有问题的,其次,是在守卫中的表达式右侧使用两个定义的问题。仍在为“Justs”和“Maybes”而苦苦挣扎:(

THX....

【问题讨论】:

  • 您可以返回一个包含两个项目的列表,如果总是有两个结果,则可以返回一个 2 元组。
  • 你的第二个和第三个后卫永远不会匹配,因为如果方向是
  • 我不清楚你在这里想要什么样的行为。请举几个例子。
  • 如何以非惯用方式编写结果,即不尝试重用 assignBottomassignTop
  • 守卫可以返回任何东西,只要每个守卫都返回相同类型的值。 (head x, last x, weight)(head y, last y, weight) 的组合应该是什么样子?

标签: haskell


【解决方案1】:

我不确定您在编写“assignBottom assignTop”时打算如何组合结果,但我可以提供一些可能会有所帮助的建议。

当您发现自己处于这种情况下,使用一堆令人沮丧的原始类型时,这通常表明您试图在一个函数中同时做太多事情,并且您需要引入辅助函数或数据类型并组合起来解决问题。当我查看这段代码时:

allocateWeight :: [[Integer]] -> (Integer, Integer, Maybe Double)
allocateWeight' [x, y]
    |direction <= Just 1 = assignBottom
    |direction <= Just (-1)  = assignTop
    |direction <= Just 0 = (?? assignBottom and assignTop ??)
  where diff = (abs(x!!1 - y!!1))
      direction = Map.lookup diff getWeightDirection 
      weight = Map.lookup diff schemaCAT
      assignBottom = (head x, last x, weight)
      assignTop = (head y,  last y, weight)

让我眼前一亮的是:

  • allocateWeight 还是allocateWeight'

  • 输入是[[Integer]],但是你说内部列表是已知的“二元组”,所以这个列表可能想要成为[(Integer, Integer)]的对列表。

  • 此外,allocateWeight 假设 outer 列表中只有两个元素具有模式 [x, y]。此函数是要对 整个 列表进行操作,还是对列表中的 每个 对进行操作?

  • 结果类型(Integer, Integer, Maybe Double),不解释其含义;它将受益于成为 data 类型。

  • 一系列守卫direction &lt;= Just … 建议您首先对Maybe 值进行模式匹配,然后比较其范围。

  • 你的守卫值是重叠的。警卫按顺序检查,Nothing 比较小于Just x 为任何xJust x &lt;= Just y 如果x &lt;= y。因此,如果没有找到该值,Nothing 将比较小于Just 1 并采取第一个守卫;如果值比较小于或等于Just (-1)Just 0,那么它必须已经 比较小于或等于Just 1,所以其他两个守卫永远不会开火。

  • diff = (abs(x!!1 - y!!1)) 再次暗示这些不想成为列表;在普通代码中最好避免使用索引运算符!!。 (它在某些情况下确实有用,例如,您正在使用列表进行记忆,但那是另一次了。)

  • 权重方向始终为-101,要求为data 类型。

  • 返回的重量可能不应该包含在 Maybe 中。

所以就代码组织而言,这是我的处理方法:

-- A pair of a value with a weight.
data Weighted a = Weighted a Double
  deriving (Show)

-- The direction in which to weight a pair.
data WeightDirection
  = WeightBottom
  | WeightTop
  | WeightBoth

-- Use data type instead of integer+comment.
getWeightDirection :: Map.Map Integer WeightDirection
getWeightDirection = Map.fromList
  [ (1, WeightTop)
  , (2, WeightTop) 
  , (3, WeightBottom)
  , (4, WeightBottom)
  , (5, WeightTop)
  , (6, WeightBoth)
  -- …
  ]

-- Allocate weights for a single pair.
allocateWeight :: (Integer, Integer) -> [Weighted Integer]
allocateWeight (x, y) = case Map.lookup diff getWeightDirection of

  Just direction -> case Map.lookup diff schemaCAT of
    Just weight -> case direction of
      WeightBottom -> [Weighted x weight]
      WeightTop -> [Weighted y weight]
      WeightBoth -> [Weighted x weight, Weighted y weight]

    -- What do you want to do in this case?
    Nothing -> error ("direction for " ++ show diff ++ " not found")

  -- What do you want to do if the difference isn’t found?
  -- Raise an error like this, or return a default?
  Nothing -> error ("weight for " ++ show diff ++ " not found")
  where
    diff = abs (x - y)

我猜想,当您想为两个值“平等”分配权重时,您希望在结果中包含两个值,每个值都有自己的权重,所以我更改了 allocateWeight 以返回一个列表.如果您想以不同的方式组合权重,例如(x * weight / 2 + y * weight / 2) 或类似的方式,那么您可能不需要这样做。使用列表,对于您的特定问题,您还可以编写:bottom = [Weighted x weight]top = [Weighted y weight],然后根据需要返回 bottomtop 或它们的串联 bottom ++ top

但这说明了我想要展示的内容:使用此函数对单个对进行操作,您可以通过mapping 输入列表和concatenating 来计算整个对列表的结果, 单独使用这些函数或使用concatMap:

          allocateWeight ::  (Integer, Integer)  ->  [Weighted Integer]

      map allocateWeight :: [(Integer, Integer)] -> [[Weighted Integer]]

concatMap allocateWeight :: [(Integer, Integer)] ->  [Weighted Integer]

您可能会问:您在每一对旁边保留的“额外”值发生了什么变化?答案是这个函数不应该关心他们;通常,这些方法是提取您需要的部分(使用map 或类似的)并重新组合结果(例如,在concat 之前使用zipWith),或参数化此函数,使其只能看到一个值的部分它需要,例如:

allocateWeightBy :: (a -> Integer) -> (a, a) -> [Weighted a]
allocateWeightBy getValue (x, y) = …

  -- Since ‘x’ has a polymorphic type, it enforces that
  -- the only thing you can do with it is call ‘getValue’
  -- or return it in the list of weighted values.

另一件事是,如果您在查找Map 中可能不存在的键时遇到Maybe 结果,您可能只需要练习,但您也可以从尝试不同的方法中受益,例如:

  • 使用case 进行查找以显式处理值的缺失。它很冗长,但很有效,以后可以变得更惯用。

  • 如果Nothing 的情况应引发错误,请使用不安全的索引运算符(Data.Map.!)。 (比如!!,这可能是数据结构不佳的标志,以后可能会咬你。)

  • 如果您只想提供默认值,请使用 fromMaybe 之类的函数,该函数位于 Data.Maybe 中,例如fromMaybe (defaultWeight :: Double) (maybeWeight :: Maybe Double) :: Double.

  • 使用函数而不是 Map,并为未找到的键使用失败案例。

【讨论】:

  • 感谢您的回答。当编码是一个我并不陌生的问题时,试图一次完成太多事情!我会仔细考虑你的回答..
  • 放弃数据类型的数值是很有意义的。事实上,我之前用 Java 编写了一个项目版本,我使用:枚举根 (BOTTOM, INVERSION, INDEFINITE, NULL) 来确定权重方向。我对 Haskell 仍然相对不熟悉,尤其是在打字方面,所以不知道移植到这种相同方法的可能性。无论如何,再次感谢...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-06
  • 2012-03-17
  • 2020-09-12
  • 1970-01-01
  • 2013-02-14
相关资源
最近更新 更多