【问题标题】:Standard way to zip two lists using custom comparator functions使用自定义比较器函数压缩两个列表的标准方法
【发布时间】:2014-02-10 13:49:03
【问题描述】:

如果经常需要压缩两个列表,丢弃不匹配的元素(其中“匹配”是通过比较列表的部分元素来定义的)。例如:

let as = [(1,"a"), (2,"b"), (4,"c")]
let bs = [(2,"a"), (3,"b"), (4,"c"), (5, "d")]
zipWithAdjust (fst) (fst) as bs
-- ~> [((2,"b"),(2,"a")), ((4,"c"),(4,"c"))]

我实现zipWithAdjust如下:

zipWithAdjust :: (Ord c, Show a, Show b, Show c) => (a -> c) -> (b -> c) -> [a] -> [b] -> [(a,b)]
zipWithAdjust cmpValA cmpValB (a:as) (b:bs)
  | cmpValA a == cmpValB b = (a,b) : zipWithAdjust cmpValA cmpValB as bs
  | cmpValA a > cmpValB b = zipWithAdjust cmpValA cmpValB (a:as) bs
  | cmpValA a < cmpValB b = zipWithAdjust cmpValA cmpValB as (b:bs)
zipWithAdjust _ _ _ _ = []

它工作得很好,但我觉得有一种标准的方式来做这样的 zip。我找到了Data.Alignthis SO Question,但不知道如何在我的用例中使用它。

是否有标准的方法来做到这一点(使用库函数)?是 Data.Align 吗?如果是这样,如何使用 Data.Align 实现上述功能?

编辑:更改了 &lt; 大小写,以使实现与示例匹配。

【问题讨论】:

  • 您确定需要&gt; 案例吗?如果你这样做了,就不可能用zip 来表达这个函数,因为zip 不允许放回元素。如果您只需要删除一些不匹配的对,则有一个简单的解决方案,即来自Data.MaybezipWithcatMaybes
  • 是的,我需要丢弃元素。否则它将是一个简单的zip,正确。

标签: haskell


【解决方案1】:

据我所知,没有such function。但是,您可以通过使用 (a -&gt; b -&gt; Ordering) 而不是两个附加函数来使您的函数更通用:

zipWithAdjust :: (a -> b -> Ordering) -> [a] -> [b] -> [(a,b)]
zipWithAdjust cmp (a:as) (b:bs)
  | ord == LT = zipWithAdjust cmp as (b:bs)
  | ord == GT = zipWithAdjust cmp (a:as) (bs)
  | ord == EQ = (a,b) : zipWithAdjust cmp as bs
  where ord = cmp a b

zipWithAdjust _ _ _ = []

result =  zipWithAdjust (\x y -> compare (fst x) (fst y)) [(1,"a"), (2,"b"), (4,"c")] [(2,"a"), (3,"b"), (4,"c"), (5, "d")]

但是,我不会再称它为 zip,而是类似 compareMerge 或类似名称。

【讨论】:

    【解决方案2】:

    您可能会喜欢Data.Map's intersection capabilities。它在某些方面的能力略逊一筹,而在另一些方面则更有能力。例如:

    > let as = fromList [(1,"a"), (2,"b"), (4,"c")]; bs = fromList [(2,"a"), (3,"b"), (4,"c"), (5, "d")]
    > intersectionWith (,) as bs
    fromList [(2,("b","a")),(4,("c","c"))]
    

    【讨论】:

      【解决方案3】:

      我会说这句话更惯用了

      zipWithAdjust cmpA cmpB (a:as) (b:bs) =
          case cmpA a `compare` cmpB b of
              EQ -> (a, b) : zipWithAdjust cmpA cmpB    as     bs
              GT ->          zipWithAdjust cmpA cmpB (a:as)    bs
              LT ->          zipWithAdjust cmpA cmpB    as  (b:bs)
      zipWithAdjust _ _ _ _ = []
      

      它肯定会更快,因为它减少了计算cmpA acmpB b 的次数。这不是真正的 zip,因为您同时进行过滤,并且在您的 GTLT 案例中也有所抵消。我会说这个解决方案非常好,不需要使用标准函数来实现它。

      【讨论】:

        【解决方案4】:

        编辑: 使用来自Data.TheseThese a b 类型(由Data.Align 使用),其中:

        ordzipBy :: (Ord t) => (a -> t) -> (b -> t) -> [a] -> [b] -> [These a b]
        ordzipBy f g a@(x:t) b@(y:r) = case compare (f x) (g y) of
            LT -> This  x   : ordzipBy f g t b
            GT -> That    y : ordzipBy f g a r
            EQ -> These x y : ordzipBy f g t r
        ordzipBy _ _ a       []      = map This a
        ordzipBy _ _ []      b       = map That b
        

        我们可以将三个集合操作表示为:

        diffBy :: (Ord t) => (a -> t) -> (b -> t)  -> [a] -> [b] -> [a]
        meetBy :: (Ord t) => (a -> t) -> (b -> t)  -> [a] -> [b] -> [(a, b)]
        joinBy :: (Ord t) => (a -> t) -> (a->a->a) -> [a] -> [a] -> [a]
        
        diffBy f g xs ys = [x     | This  x   <- ordzipBy f g xs ys]
        meetBy f g xs ys = [(x,y) | These x y <- ordzipBy f g xs ys]
        joinBy f h xs ys = mergeThese h   `map`  ordzipBy f f xs ys
        

        你描述的是meetBy,即集合交集运算,将两个有序列表视为集合。

        编译器高效编译这些定义的能力是另一个问题。沿着ordzipBy 的行手动编码的三个集合函数可能会运行得更快。

        ordzipBy f galign 兼容,[]nil 兼容,但实现它所涉及的类型机器高于我的工资等级。 :) 另外,我不清楚align (f &lt;$&gt; xs) (g &lt;$&gt; ys) = bimap f g &lt;$&gt; align xs ys 是否有意义,因为映射函数fg 可以很好地改变xsys 元素的相互顺序。

        这两个问题(类型和规律)是相关的:选择器函数恢复的用于排序目的的数据部分作为位置,作为形状,然而是原始数据的一部分。 (参见instance Alternative ZipList in Haskell?)。


        更新:看看下面的工作是否符合你的预期。

        {-# LANGUAGE InstanceSigs, DatatypeContexts #-}
        import Data.These
        import Data.Align
        
        newtype Ord a => ZL a b = ZL {unzl :: [(a,b)]}
              deriving (Eq, Show)
        
        instance Ord a => Functor (ZL a) where
          fmap f (ZL xs) = ZL [(k, f v) | (k,v)<-xs]
        
        instance Ord a => Align (ZL a) where 
          nil = ZL []
          align :: (ZL a b) -> (ZL a c) -> (ZL a (These b c))
          align (ZL a) (ZL b) = ZL (g a b) where
            g a@((k,x):t) b@((n,y):r) = case compare k n of
                LT -> (k, This  x  ) : g t b 
                GT -> (n, That    y) : g a r
                EQ -> (k, These x y) : g t r  
            g a               []              = [(k, This x) | (k,x) <- a]
            g []              b               = [(n, That y) | (n,y) <- b]
        
        diffBy :: (Ord t) => (a -> t) -> (b -> t)  -> [a] -> [b] -> [a]
        meetBy :: (Ord t) => (a -> t) -> (b -> t)  -> [a] -> [b] -> [(a, b)]
        joinBy :: (Ord t) => (a -> t) -> (a->a->a) -> [a] -> [a] -> [a]
        
        diffBy f g xs ys = catThis        . map snd . unzl 
                            $ align (ZL [(f x,x) | x<-xs]) (ZL [(g y,y) | y<-ys])
        meetBy f g xs ys = catThese       . map snd . unzl 
                            $ align (ZL [(f x,x) | x<-xs]) (ZL [(g y,y) | y<-ys])
        joinBy f h xs ys = map (mergeThese h . snd) . unzl 
                            $ align (ZL [(f x,x) | x<-xs]) (ZL [(f y,y) | y<-ys])
        

        虽然没有很好地处理无限列表,而手动编码的函数显然可以很容易地转换为handle such cases correctly

        *Main> diffBy id id [1..5] [4..9]
        [1,2,3]
        *Main> diffBy id id [1..5] [4..]
        [1,2,3Interrupted.
        
        *Main> meetBy id id [1,3..10] [2,5..20]
        [(5,5)]
        *Main> joinBy id const [1,3..10] [2,5..20]
        [1,2,3,5,7,8,9,11,14,17,20]
        

        【讨论】:

        • 这有助于理解Data.这些,谢谢!如果我做对了,我仍然必须实现ordzipBy,对吗?与我的zipWithAdjust(或此处建议的更通用的实现)相比,这并没有太大的收获。
        • 对,正如我所说,要么通过ordzipBy 定义所有内容,要么对它们进行手动编码,这通常会带来改进的可能性——例如diffBy 只响应 This,那么为什么要生成 ThatThese。除非 GHC 足够“聪明”。 :) -- 并感谢These 提上来 :),我之前在那里使用过(Maybe a, Maybe b),而These a b 更好! --所以问题是形状和有效载荷的分离;也许如果您将类型更改为显式对 (explicitly_unchangeable_position_index, datum),则可以使用 Data.Align。
        • 呃,(explicitly_unchangeable_position_indicator-producing_value, datum)。选择器必须仅限于仅作用于此类对的 fst 部分(在类型级别上)。
        • 打字问题源于[]是一个列表,但您需要一个排序列表。
        • @SassaNF 即使未排序,也只是结果不一致。我已经将列表明确排序(-able),检查一下。 :)
        猜你喜欢
        • 2011-02-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-01-05
        • 2015-02-07
        • 2019-02-20
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多