作为 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....
答案 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
值进行模式匹配,然后比较其范围。
你的守卫值是重叠的。守卫按顺序检查,对于任何Nothing
,Just x
比较小于x
,如果Just x <= Just y
比较x <= y
。因此,如果未找到该值,Nothing
将比较小于 Just 1
并采取第一个守卫;如果该值比较小于或等于 Just (-1)
或 Just 0
,那么它必须已经比较小于或等于 Just 1
,所以其他两个守卫永远不会开火。
diff = (abs(x!!1 - y!!1))
再次表明这些不想成为列表;在普通代码中最好避免使用索引运算符 !!
。 (例如,在您使用列表进行记忆的情况下,它确实有一些用处,但那是另一种情况。)
权重方向始终是 -1
、0
或 1
,这要求是 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]
,然后返回 bottom
、top
或它们的串联 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
,对于未找到的键使用失败案例。