规范外连接 zip 函数
Posted
技术标签:
【中文标题】规范外连接 zip 函数【英文标题】:Canonical outer join zip function 【发布时间】:2012-03-01 04:31:39 【问题描述】:如果您将列表中每个元素的(隐式)索引视为它们的键,那么zipWith
有点像关系内连接。它只处理两个输入都有值的键:
zipWith (+) [1..5] [10..20] == zipWith (+) [1..11] [10..14] == [11,13,15,17,19]
是否有对应外连接的规范对应函数?比如:
outerZipWith :: (a -> b -> c) -> a -> b -> [a] -> [b] -> [c]
outerZipWith _ _ _ [] [] = []
outerZipWith f a' b' [] (b:bs) = f a' b : outerZipWith f a' b' [] bs
outerZipWith f a' b' (a:as) [] = f a b' : outerZipWith f a' b' as []
outerZipWith f a' b' (a:as) (b:bs) = f a b : outerZipWith f a' b' as bs
或许
outerZipWith' :: (a -> b -> c) -> Maybe a -> Maybe b -> [a] -> [b] -> [c]
outerZipWith' _ _ _ [] [] = []
outerZipWith' _ Nothing _ [] _ = []
outerZipWith' _ _ Nothing _ [] = []
outerZipWith' f a' b' [] (b:bs) = f (fromJust a') b : outerZipWith f a' b' [] bs
outerZipWith' f a' b' (a:as) [] = f a (fromJust b') : outerZipWith f a' b' as []
outerZipWith' f a' b' (a:as) (b:bs) = f a b : outerZipWith f a' b' as bs
这样我就可以了
outerZipWith (+) 0 0 [1..5] [10..20] == [11,13,15,17,19,15,16,17,18,19,20]
outerZipWith (+) 0 0 [1..11] [10..14] == [11,13,15,17,19,6,7,8,9,10,11]
我发现自己时不时需要它,我宁愿使用一个常见的习惯来使我的代码更可写(并且更易于维护),而不是实现outerZipWith
,或者做if length as < length bs then zipWith f (as ++ repeat a) bs else zipWith f as (bs ++ repeat b)
。
【问题讨论】:
【参考方案1】:这看起来很尴尬,因为您试图跳到最后而不是处理原始操作。
首先,zipWith
在概念上是zip
,后跟map (uncurry ($))
。这是很重要的一点,因为 (un)currying 是 zipWith
完全可能的原因。给定类型为[a]
和[b]
的列表,要应用函数(a -> b -> c)
并获得[c]
类型的东西,你显然需要每个输入之一。执行此操作的两种方法正是列表的两个 Applicative
实例,zipWith
是其中之一的 liftA2
。 (另一个实例是标准实例,它提供笛卡尔积——如果您愿意,可以使用交叉连接)。
您想要的语义不对应于任何明显的Applicative
实例,这就是为什么它要困难得多。首先考虑outerZip :: [a] -> [b] -> [?? a b]
及其类型。结果列表的每个元素都可以是a
、b
,或两者兼而有之。这不仅不对应于任何标准数据类型,而且用它们来表达也很尴尬,因为您无法从表达式 (A + B + A*B)
中考虑任何有用的内容。
这样的数据类型有它自己的用途,这就是为什么我有my own package defining one。我记得也有一个关于 hackage 的(我认为辅助功能比我的少),但我不记得它叫什么了。
坚持标准的东西,你最终需要一个合理的“默认值”,它大致转化为拥有一个Monoid
实例并使用标识值来填充列表。但是,使用 newtype
包装器等在适当的 Monoid
之间进行转换可能不会比您当前的实现更简单。
顺便说一句,您关于列表索引作为键的评论实际上可以进一步发展;任何具有类似键概念的Functor
都与Reader
monad 同构,也就是从键到值的显式函数。 Edward Kmett 和往常一样,有一堆对这些抽象概念进行编码的包,在本例中是从the representable-functors
package 构建的。可能会有所帮助,如果您不介意编写十几个实例只是为了至少开始......
【讨论】:
不会outerZip :: a -> b -> [a] -> [b] -> [(a,b)]
?
更像outerZip :: (a -> c) -> (b -> d) -> c -> d -> [a] -> [b] -> [(c, d)]
包容或类型(如您的These
)可能是必要的第一步。至少,这是一个很好的起点。
outerZip f a b as bs = map (f . fromThese a b) $ zipThese as bs
非常干净。我喜欢它。
@rampion: 哦,还有——因为收集像这样的输入作为中间步骤显然是用于像These
这样的类型,所以我尝试非常全面地使用预测、案例分析功能,和These
的谓词,特别是为了支持干净地定义像你的outerZip
这样的东西。 :]【参考方案2】:
与其用一个常数填充较短的列表,为什么不提供一个值列表,直到到达较长列表的末尾?这也消除了对 Maybe
的需要,因为列表可以为空(或有限长度)。
我找不到任何标准,但没有按照您展示的方式完全重新实现 zipWith
,我开发了您的 length
测试版本,如下所示:
outerZipWith :: (a -> b -> c) -> [a] -> [b] -> [a] -> [b] -> [c]
outerZipWith f as bs as' bs' = take n $ zipWith f (as ++ as') (bs ++ bs')
where n = max (length as) (length bs)
【讨论】:
这不会编译(至少对我来说)。【参考方案3】:这种事情经常出现。这是PACT algebra 的cogroup
操作。在我工作的地方,我们使用类型类来区分这三种操作:
zip
:结构交叉。
align
: 结构联合。
liftA2
:结构笛卡尔积。
Paul Chiusano on his blog 对此进行了讨论。
【讨论】:
Paul Chiusano 的博客文章直截了当且富有洞察力。感谢您链接。以上是关于规范外连接 zip 函数的主要内容,如果未能解决你的问题,请参考以下文章
数据库编程1 Oracle 过滤 函数 分组 外连接 自连接