【问题标题】:Haskell: concat a list of tuples with an element and a list: [(a,[b])] -> [(a,b)]Haskell:用一个元素和一个列表连接一个元组列表:[(a,[b])] -> [(a,b)]
【发布时间】:2019-07-29 19:20:42
【问题描述】:

我想在元素和字符串的元组列表中连接,每个字符都带有元素。

例如:[(True, "xy"), (False, "abc")] ????-> [(True,'x'),(True,'y'),(False,' a'), (False,'b'),(False,'c')]

我确实有一个解决方案,但我想知道是否有更好的解决方案:

concatsplit :: [(a,[b])] -> [(a,b)]
concatsplit a = concatMap (\(x,y)-> concatsplit' (x,y)) a

concatsplit' :: (a,[b]) -> [(a,b)]
concatsplit' y = map (\x -> ((fst y),x)) (snd y)

【问题讨论】:

    标签: list haskell split tuples concat


    【解决方案1】:

    sequenceA 让事情变得非常简单:

    concatsplit = concatMap sequenceA
    

    或者进一步概括:

    concatsplit = (>>= sequenceA)
    

    详情:

    • sequenceA 的类型为 (Applicative f, Traversable t) => t (f a) -> f (t a)。这意味着,如果您有一个类型在“外部”上带有 Traversable,而在“内部”上带有 Applicative,您可以在其上调用 sequenceA 将其从内向外翻转,以便 Applicative位于“外部”,Traversable 位于“内部”。
    • 在这种情况下,(True, "xy") 的类型为 (Bool, [Char]),它脱糖为 (,) Bool ([] Char)((,) a)an instanceTraversable[]an instanceApplicative。因此,您可以对其调用sequenceA,结果将是[] ((,) Bool Char) 类型,或带有糖的[(Bool, Char)]
    • 事实证明,sequenceA 不仅有一个有用的类型,而且它完全符合您在此处需要的功能(结果完全等同于 concatsplit')。
    • concatMap 的类型为 Foldable t => (a -> [b]) -> t a -> [b](>>=) 的类型为 Monad m => m a -> (a -> m b) -> m b。当专门用于列表时,它们变得相同,只是它们的参数顺序相反。

    【讨论】:

    • 如果您能详细说明所涉及的特定类型和实例,它会为答案增加很多价值。
    • @WillNess 我添加了一些解释。
    • 太棒了;不能紫外线两次。 :) 我不知道这是否是一个公认的术语,但我通常会说sequence“转置”类型,或者“翻转”它们。 (“从里到外”听起来也不错。)在这里,它将带有列表的对变成对列表......所以我们有concatsplit = (>>= (\(a,bs) -> (a ,) <$> bs)),这正是“对” Traversable 的定义。好的;谢谢你让我完成这个工作。 :) 另一个棘手的例子是traverse (splitAt 1),它完全符合类型的预期,比如魔术;更简单但很好的是traverse ZipList(参见上面的“转置”;))。
    【解决方案2】:

    其他答案显示了如何从头开始惯用地执行此操作,我非常喜欢。展示你如何打磨你已经拥有的东西也可能很有趣。这里再次提醒一下:

    concatsplit a = concatMap (\(x,y)-> concatsplit' (x,y)) a
    concatsplit' y = map (\x -> ((fst y),x)) (snd y)
    

    我要始终更改的第一件事称为“减少 eta”,即当您将形状 \x -> foo x 的东西变成 foo 时。我们可以在 concatMap 的参数中这样做来获取

    concatsplit a = concatMap concatsplit' a
    

    然后在concatsplit 的参数中再次得到:

    concatsplit = concatMap concatsplit'
    

    查看concatsplit',我最不喜欢的是使用fstsnd 而不是模式匹配。使用模式匹配,它看起来像这样:

    concatsplit' (a,bs) = map (\x -> (a,x)) bs
    

    如果你真的想练习减少 eta,你可能会注意到 (,) 可以应用前缀并将其更改为

    concatsplit' (a,bs) = map (\x -> (,) a x) bs
                        = map ((,) a) bs
    

    但我想我也一样快乐。在这一点上,这个定义足够小,我很想将它内联到 concatsplit 本身,得到:

    concatsplit = concatMap (\(a,bs) -> map ((,) a) bs)
    

    这对我来说似乎是一个很好的定义,我会停在那里。

    可能被这里的几乎 eta-reduction 所困扰:删除bs 几乎是正确的形状。高级用户可能会继续,注意到:

    uncurry (\a bs -> map ((,) a) bs) = \(a,bs) -> map ((,) a) bs
    

    因此,通过一些 eta 缩减和其他无点技术,我们可以通过这种方式进行转换:

    concatsplit = concatMap (uncurry (\a bs -> map ((,) a) bs))
                = concatMap (uncurry (\a -> map ((,) a)))
                = concatMap (uncurry (map . (,)))
    

    但是,就我个人而言,我发现这比我在上面声明要停止的地方可读性差,即:

    concatsplit = concatMap (\(a,bs) -> map ((,) a) bs)
    

    【讨论】:

    • 我非常喜欢TupleSectionsconcatMap (\(a,bs) -> map (a,) bs)。没有本质区别,但少了一对括号对我来说很干净。
    • @luqui 我更喜欢(a ,),类似于(f .) 而不是(f.)
    【解决方案3】:

    您也可以将 monad 实例用于列表:

    concatSplit l = l >>= \(a,x) -> x >>= \b -> return (a,b)
    

    可以简化为:

    concatSplit l = l >>= \(a,x) -> map ((,) a) x
    

    并重新格式化为do 符号

    concatSplit l = do
      (a,x) <- l
      map ((,) a) x
    

    【讨论】:

      【解决方案4】:

      为什么不是简单的列表理解?

      列表推导通常可以做与高阶函数一样多的工作,我认为如果您不需要更改数据而只需“解包”它,它们就很清楚了。这是一个工作示例:

      concS :: [(a,[b])] -> [(a,b)]
      concS ls = [(a,b) | (a,x) <- ls, b <- x]
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-02-18
        • 2021-07-12
        • 2021-11-02
        • 1970-01-01
        • 1970-01-01
        • 2010-12-25
        • 2015-05-14
        • 1970-01-01
        相关资源
        最近更新 更多