一个守卫可以有多个结果吗?

时间:2021-02-21 14:26:17

标签: haskell

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

我的问题是,在权重相等的情况下,如何重新使用顶部和底部权重分配的定义?到目前为止,我咨询过的每一个来源似乎都表明守卫只能有一个结果——我怀疑这是正确的 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 之间。

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

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

第四阶段:根据他们之间的差异,每个二元组中的一名成员(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))]

如前所述,如果地图查找的值为 1,则权重进入底部; -1 到顶部。问题是当查找 key 为 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 进行操作。

应用上述内容后,我应该得到一个列表 a tuples [(-1, 3, 0.5)....] 第二个元组成员是接收权重的元组成员,第三个是权重本身(我将忽略元组的第一个成员,因为这并不重要。键 999 也是如此,这是一个特殊情况)。

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

THX....

1 个答案:

答案 0 :(得分:0)

我不确定您在编写“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 <= Just … 建议您首先对 Maybe 值进行模式匹配,然后比较其范围。

  • 你的守卫值是重叠的。守卫按顺序检查,对于任何NothingJust x 比较小于x,如果Just x <= Just y 比较x <= 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 作为需要。

但这说明了我想展示的内容:使用此函数对单个对进行运算,您可以通过 map ping 输入列表和 {{1} 来计算整个对列表的结果}生成结果,分别使用这些函数或使用 concat:

concatMap

您可能会问:您在每一对中保留的“额外”值发生了什么变化?答案是这个函数不应该关心它们;通常的方法是提取你需要的部分(使用 allocateWeight :: (Integer, Integer) -> [Weighted Integer] map allocateWeight :: [(Integer, Integer)] -> [[Weighted Integer]] concatMap allocateWeight :: [(Integer, Integer)] -> [Weighted Integer] 或类似的)并重新组合结果(例如在 map 之前使用 zipWith),或者参数化这个函数所以它只能看到它需要的值的部分,例如:

concat

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

  • 在任何需要查找的地方使用 Map 来明确处理缺少值的情况。它很冗长,但很有效,以后可以做得更地道。

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

  • 如果您只想提供默认值,请使用 !! 中的 fromMaybe 之类的函数,例如Data.Maybe

  • 使用函数而不是 fromMaybe (defaultWeight :: Double) (maybeWeight :: Maybe Double) :: Double,对于未找到的键使用失败案例。