【问题标题】:Using Algebraic Data Type to combine lists使用代数数据类型组合列表
【发布时间】:2019-08-17 23:43:14
【问题描述】:

我正在尝试了解创建的代数数据类型的语法。我创建的类型是[Int]Empty,类似于MaybeJustNothing,除了Just 必须是Int 的列表。当它接受两个输入并给出相同类型的输出时,我无法理解如何操作创建的类型。

data Example = Arg [Int]
         | Empty
    deriving Show

我使用模式匹配并理解必须解决每种情况;但是,我的问题来自最终模式的语法,Empty 都不是。我正在尝试编写两个函数:一个结合来自Example 构造函数的[Int] 列表,并且我想创建一个只显示一组[Int] 共享的函数,而不是组合。

第一个问题是合并两个集合。我可以在普通函数中执行此操作,但在某个地方,使用 Example 数据类型,语法已关闭,我不熟悉它。第二个最大的问题是相同的:我了解递归,但我不了解创建的数据类型中递归的语法。我也在考虑在第二个函数中使用where 语句,但如果我不能正确获得基本递归的语法,那么我怀疑它会成功。

combine :: Example -> Example -> Example
combine Empty Empty = Empty
combine (Arg xs) Empty = (Arg xs)
combine Empty (Arg ys) = (Arg ys)
combine (Arg xs) (Arg ys) = Arg xs ++ ys

same :: Example -> Example -> Example
same _ Empty = Empty
same Empty _ = Empty
same (Arg x : xs) (Arg y : ys)
  | x == y    = x
  | otherwise = same (Arg xs) (Arg ys)

combine 的输出应该是一个 [Int] 列表,其中包含两个列表中的所有 Ints;如果一个列表为空,它应该返回整组非空列表。

same 的输出应该包含一个[Int] 列表,该列表仅包含两个组共享的数字,没有重复;如果一组为空,则输出为空。

【问题讨论】:

  • 我注意到的唯一问题(现在无法测试)是函数的输出应该使用 Arg 构造函数 - 例如。 combine 的前 2 个案例应导致 Arg []
  • 哦,same 函数还有一些问题。 Arg x : xs 应该是 Arg (x : xs)。您还没有涵盖任一参数为Arg [] 的情况。并且(在最终模式中的 x == y 守卫中)您不能将 x 作为输出。
  • 在试图理解same 函数可能打算做什么时——Arg []Empty 之间的“含义”是否应该有所不同?如果是这样,它是什么?
  • 您在combine 的最后一行缺少括号。要通过类型检查器,它应该是... = Arg (xs ++ ys)。这是不对的(如果您不想在结果中包含重复的元素),但至少它会进行类型检查。 --- same (Arg x : xs) (Arg y : ys) = ... 也错了:应该是 same (Arg (x : xs)) (Arg (y : ys)) = ...
  • 你有很多问题都在一篇文章中混乱。如果您使用/关于 one 代码 sn-p 提出一个具体的、重点突出的问题,您将获得更好的投资回报率。即最小代码,如minimal reproducible example。但事实证明,我之前的评论回答了您的新编辑。您只需要在(Arg (x:xs)) 等内部的(x:xs) 周围添加括号,因为您现在拥有的内容被解释为(Arg x) : xs

标签: haskell recursion pattern-matching algebraic-data-types


【解决方案1】:

使用lifting的概念,我得出了这个解决方案:

import Data.List (nub)

data Example = Arg [Int]
         | Empty
    deriving Show

combine :: Example -> Example -> Example
combine Empty Empty = Empty
combine x Empty = x
combine Empty x = x
combine (Arg xs) (Arg ys) = Arg $ nub $ xs ++ ys

same :: Example -> Example -> Example
same arg1 arg2 = lift nub $ same' arg1 arg2
    where
        same' _ Empty = Empty
        same' Empty _ = Empty
        same' (Arg []) y = Arg []
        same' (Arg (x : xs)) (Arg (ys))
            | x `elem` ys = lift (x : ) $ same' (Arg xs) (Arg ys)
            | otherwise  = same' (Arg xs) (Arg ys)

lift :: ([Int] -> [Int]) -> Example -> Example
lift _ Empty = Empty
lift f (Arg x) = Arg (f x)

lift 所做的是将函数应用于Arg 的“内容”。如果你有表达式same (Arg [1, 2, 4, 3]) (Arg [3,1,4]),递归将把它变成:

lift nub $ lift (1 : ) $ lift (4 : ) $ lift (3 : ) $ Arg []

评价如下:

lift nub $ lift (1 : ) $ lift (4 : ) $ Arg (3:[])
lift nub $ lift (1 : ) $ Arg (4:3:[])
lift nub $ Arg (1:4:3:[])
Arg (nub $ 1:4:3:[])

最后:

Arg [1,4,3]

【讨论】:

  • 请注意,lift 类似于 mono-traversable 包中的 omap
  • @WillNess 你是对的,谢谢。编辑代码以消除所有额外的nubs。
【解决方案2】:
combine :: Example -> Example -> Example
combine x Empty = x
combine Empty x = x
combine (Arg xs) (Arg ys) = Arg $ union xs ys

union xs ys = nub $ xs ++ ys
nub [] = []
nub (x:xs) = if x `elem` xs then nub xs else x : nub xs

same :: Example -> Example -> Example
same _ Empty = Empty
same Empty _ = Empty
same (Arg xs) (Arg ys) = Arg $ intersect xs ys

intersect _ [] = [] -- unnecessary but practical!
intersect [] _ = []
intersect (x:xs) ys = if x `elem` ys then x : intersect xs ys else intersect xs ys

作为 Robin cmets,您有几个不同的问题。首先,您需要匹配所有案例,其次您需要将结果包装回您的数据类型,第三您需要删除联合中的重复项,第四您的“设置交集”操作仅适用于一些非常强的假设输入列表的结构。 unionintersect(也可在 Data.List 中使用)足以用于演示目的而无需调用例如。 Data.IntSet.toList . Data.IntSet.fromList 虽然这样会更快。您的版本(如果稍作修正)将输出出现在两个列表中相同位置的元素。

关于函子的常见教程通常以Maybe 开头,这可能有助于您理解这一点,因为ExampleMaybe [Int] 同构。

您可能希望使用Arg (x : xs) 解构函数的示例是一个函数,该函数从两个列表中的每个索引处获取较小的元素。您可以尝试递归地自己编写,即。不使用zip

编辑:重大更改和更正

【讨论】:

  • 已更正 combine 以符合您的规范。这有点不自然,因为通常Empty 做的事情与Arg [] 不同。 nub 获取一个列表并删除重复条目。 $ 是函数应用程序,我在这里使用它来消除对括号的需要; a $ b c = a (b c)a $ b $ c = a (b (c))。以递归方式编写same 的问题在于,您基本上需要对列表进行排序才能做到这一点,但我可以在 1 秒内编写另一个版本。
  • 1) 你是神,谢谢。 2) 代替$ 我可以用[] 代替吗? 3)再次感谢你,这救了我的命。我一直在努力理解代数数据类型语法。我理解这个想法和模式匹配,但被语法困住了。
  • 2) 不,你不能,它们完全不同!您应该启动 GHCi 并与他们一起玩。 :t 命令告诉你一些东西的类型并且非常有用。
  • 我得到了 combine 的工作,但我仍然遇到问题在 same 上使用递归返回一个只有类似 Int 而没有重复的 [Int] 列表。
  • 3) 最简单的学习方法就是尝试。尝试编译时查看错误消息,尝试了解编译器反对什么,并为有类似问题的初学者搜索错误,看看人们怎么说。错误消息永远不会提供完整的信息,但学习阅读它们对于了解代码错误的位置非常宝贵。
猜你喜欢
  • 2018-09-09
  • 2018-05-12
  • 2019-11-08
  • 2019-05-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-10
  • 2020-07-22
相关资源
最近更新 更多