【问题标题】:using filter function to produce a list outputting non-common elements of two lists使用过滤器函数生成一个列表,输出两个列表的非公共元素
【发布时间】:2021-03-13 18:29:44
【问题描述】:

所以我需要一个函数,它接受两个列表 x 和 y 并输出列表 x 中但不在列表 y 中的元素列表。 IE。输出应该是这样的

*Main> filterVars ["a","c","h","d"] ["c","b","a"]

      ["h","d"]

我想用 Haskell 中的过滤功能来练习。我的代码如下;

    filterVars :: [a] -> [a] -> [a]
    filterVars = filter IsInList (y:ys)
     where
IsiIList (x:xs) (y:ys) :: [a] -> [a] -> Bool
IsInList [] _ = []
IsiInList(x:xs) (y:ys)
                        | elem (x (y:ys)) = IsiInList (xs (y:ys))
                        | otherwise = x : IsiInList (xs (y:ys))

但是我得到以下错误;

   Invalid type signature: IsInList (x : xs) (y : ys) :: ...
Should be of form <variable> :: <type>
     | IsInList (x:xs) (y:ys) :: [a] -> [a] -> Bool    

我的逻辑如下:

当使用参数 (x:xs) (y:ys) 调用函数 filterVars 时,它会检查以查看过滤条件 IsInList。

提供过滤条件的函数 IsInList 接受参数 (x:xs) 和 (y:ys)。如果列表 (x:xs) 的第一个元素是 (y:ys) 的元素,则丢弃并移动到 x 列表中的下一个元素。

对于 (x:xs) 的元素不在列表 (y:ys) 中的情况,存储此值并移动到列表 x 的下一个元素,直到完全解析完列表 x。

返回包含列表 x 中不在列表 y 中的元素的列表

我的困惑源于我缺乏使用 Haskell 的经验。首先,函数 filterVars 必须需要两个列表并输出一个列表。但是,当我定义函数时,我使用过滤器函数和列表 y 作为参数。我知道过滤器输出一个列表,所以我只想知道我的初始设置是否正确(也想深入了解 Haskell 中的执行流程)

其次,我不确定错误的含义以及如何解决它。我的逻辑有意义吗?这可以使用列表还是我的标记方式来完成?

【问题讨论】:

  • 如果一个元素在第一个列表中出现多次,你希望发生什么?
  • Data.List.Ordered.minus 就是这样做的(但只有在你 sort 两个参数列表之后)。你可以查阅它的源代码。 filter 不是一个好的选择,因为它会强制您使用 (`elem` y) 导致二次码,但 minus 在线性 sort 阶段之后是线性的。
  • 请注意,在Data.List 中也已经存在一个未排序列表的函数作为运算符 `\`(但请参阅有关处理重复项的文档)。

标签: list haskell recursion functional-programming


【解决方案1】:

您的代码存在一些句法和语义错误。我将解释语法问题,然后进入您的解决方案的语义。

  • 在 Haskell 中,函数、变量和类型变量的名称必须以 小写 字母开头;大写名称指的是类型、数据构造函数或模块。因此需要将IsInList重命名为isInList,并修正名称拼写错误。

  • 定义上的where 子句后跟一个变量绑定块 (<em>pattern</em> = <em>expression</em>)。一个块可以用花括号分隔,项目用分号分隔({;;}),但更常见的是使用基于缩进的布局。因此,您需要缩进 where 块的内容。一个简单的规则是在 any 布局关键字之后放置一个换行符 + 缩进(带空格)——wheredooflet——只要你有一个多行块。

  • 函数的类型签名写成<em>name</em> :: <em>type</em>,但是你在isInList (x:xs) (y:ys) :: [a] -&gt; [a] -&gt; Bool的左侧写了一个看起来像pattern的东西。如果你想在这里包含一个类型签名,你应该写isInList :: [a] -&gt; [a] -&gt; Bool。但是,我将只删除签名,原因我将在下面解释。

通过这些修复,您的代码如下所示:

filterVars :: [a] -> [a] -> [a]
filterVars = filter isInList (y:ys)
  where
    isInList [] _ = []
    isInList (x:xs) (y:ys)
      | elem (x (y:ys)) = isInList (xs (y:ys))
      | otherwise = x : isInList (xs (y:ys))

这是我对这段代码的阅读:

  • filterVars 等于使用两个参数调用filterisInListy : ysyys 不在此范围内,因为您尚未将它们作为参数或模式变量添加到 filterVars 的定义中。

  • isInList 接受两个参数;如果第一个是空列表,则返回一个空列表。这不太对,因为filter 的类型为(a -&gt; Bool) -&gt; [a] -&gt; [a],也就是说,它的第一个参数应该是一个函数,它测试过滤列表中的一个元素并返回一个Bool 来指示它是否应该包含在结果。

  • 如果isInList 有两个非空列表,则后面跟着一个守卫来决定继续处理哪种情况。您正在尝试使用elem 函数来测试x 是否在列表y : ys 中作为这里的条件,但是您调用它的方式,您将y : ys 作为参数传递给x ,就好像它是一个函数一样,然后在其结果上调用elem。如果你想将两个参数传递给一个函数,你只需用空格分隔它们,并在必要时包含括号以进行分组,例如:elem x (y : ys)。当您可能打算使用isInList xs (y:ys) 时,您对isInList (xs (y:ys)) 的两次调用都有相同的问题。

在这一点上,我很清楚您混淆了两件事:使用filter 过滤列表,以及通过编写递归函数对其进行过滤。

如果你想用filter 来写这个,你应该从问题陈述开始:“一个函数,它接受两个列表,x 和 y 并输出 [s] 列表 x 但不在列表中的元素列表列出 y”。你想写一个函数,它接受两个参数,xy,并通过一个函数过滤x,给定x 的元素,如果该元素不在y 中,则返回true。

这可以从字面上理解:

filterVars :: [a] -> [a] -> [a]

-- Select the elements of ‘x’ not in ‘y’…
filterVars x y = filter notInY x
  where

    -- …where an element ‘e’ is not in ‘y’ if
    -- it’s ‘not’ the case that ‘e’ is an ‘elem’ of ‘y’.
    notInY e = not (elem e y)

但这有一个问题:这个函数说它可以获取 any 类型 a 的值列表,并使用 elem 测试它们在列表中的存在,但这不是不是真的:我们可以提供像Double -&gt; String 这样的类型,根本无法比较!实际上,elem 仅限于在具有相等性的类型集合中的类型,Eq 类型类,因此我们必须在签名中添加一个Eq 约束以启用elem 的使用:

filterVars :: (Eq a) => [a] -> [a] -> [a]

现在,关于 where 块中的本地类型签名,Haskell 中的规则是,如果您使用类型变量编写多态类型签名(通常是单个字母,如 abfm…) 那么这些变量对于该类型签名是 local 的。所以如果你写这个:

filterVars :: (Eq a) => [a] -> [a] -> [a]
filterVars x y = filter notInY x
  where
    notInY :: a -> Bool
    notInY e = not (elem e y)

相当于这样:

filterVars :: (Eq a1) => [a1] -> [a1] -> [a1]
filterVars x y = filter notInY x
  where
    notInY :: a2 -> Bool
    notInY e = not (elem e y)

notInY 签名中的afilterVars 签名中的a不同,因此这会导致类型错误。解决方法是去掉本地类型签名或者使用ScopedTypeVariables扩展来显式定义a的范围:

{-# LANGUAGE ScopedTypeVariables #-}

filterVars :: forall a. (Eq a) => [a] -> [a] -> [a]
filterVars x y = filter notInY x
  where
    notInY :: a -> Bool
    notInY e = not (elem e y)

foralla 引入了显式作用域,因此此定义中类型签名中的任何a 均指代相同 a。 (显式 forall 就像许多命令式编程语言中用于定义泛型参数的 &lt;T&gt; 语法一样。)

就风格而言,您也可以用几种不同的方式编写相同的结构,并且您应该确保您理解这些:

filterVars x y = filter (notIn y) x
  where
    notIn list e = not (elem e list)

filterVars x y = filter (\ e -> not (elem e y)) x

filterVars x y = filter (`notElem` y) x

这是非常典型的 Haskell 代码,也是一个很好的简洁解决方案。但是既然你正在学习 Haskell,你应该尝试用显式递归来解决这个问题,有效地将 filter 的定义内联到你的代码中。为此,您确实希望像在 isInList 函数中尝试那样进行模式匹配。您可以从以下任一结构开始,使用 case 表达式或多个函数方程,无论您喜欢哪一种:

filterVars :: (Eq a) => [a] -> [a] -> [a]
filterVars x y = case x of

  [] -> {- 1 -}

  e : rest
    | {- 2 -} -> {- 3 -}
    | otherwise -> {- 4 -}

filterVars :: (Eq a) => [a] -> [a] -> [a]
filterVars [] y = {- 1 -}
filterVars (e : rest) y
  | {- 2 -} = {- 3 -}
  | otherwise = {- 4 -}

并填写以下详细信息,上面代码中用{- … -}cmets标记:

  1. [a];当x 为空时,您会返回什么,即当您直接调用filterVars [] ["c", "b", "a"] 或作为递归的基本情况时会发生什么?

  2. Bool;当x 非空时,包含一些元素e 和一些(可能为空的)余数rest,你用什么条件来测试e 是否是y 的元素? (例如,filterVars ["a", "d", "b"] ["c", "b", "a"]e"a"rest["d", "b"]。)

  3. [a];当e y 的一个元素时,你会返回什么?您应该保留还是丢弃该元素?输入的rest 是做什么用的?

  4. [a];当eynot 时,你会返回什么?那你输入的rest怎么办呢?

回想一下,您始终可以使用 等式推理 逐步执行 Haskell 代码,方法是替换 = 符号两侧的内容,因为 = 实际上意味着相等!例如,如果您的函数有一个案例f [] = [],那么每当您看到f [],您可以替换它为[];如果它有一个案例f (x : xs) = x + f xs,当你看到对f [1, 2, 3]的调用时,这与f (1 : [2, 3])相同,同样f (1 : (2 : (3 : []))),所以你可以匹配x = 1xs = 2 : (3 : []) 并在右侧替换它们的值 x + f xs1 + f [2, 3]

【讨论】:

  • 好吧,这更有意义。唯一我不太明白的是 Haskell 如何知道元素“e”来自列表 x?此外,递归发生在哪里?我似乎无法理解它如何逐个元素地遍历列表 x,并将每个元素与整个 y 列表进行比较。 elem 函数的签名将单个变量与列表进行比较,但是我们提供了两个列表,形式为 e、y。
  • @Zain:知道e来自x,因为你在x上匹配到define e。像case x of { [] -&gt; (A); e : rest -&gt; (B) } 这样的匹配类似于if null x then (A) else let { e = head x; rest = tail x } in (B)。我没有为你写出来,但在我的例子中递归调用将出现在案例 3 和 4 中,就像你已经在 isInList 中写的一样。您对e 的类型有误:如果x 的类型为[a] 某种类型a,那么e 不是 类型为[a],它只是@ 987654451@。 (这可能发生是一个列表类型,例如您的示例中的String,但这与此处无关。)
猜你喜欢
  • 2012-12-07
  • 2021-02-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-03
  • 1970-01-01
  • 1970-01-01
  • 2018-07-14
相关资源
最近更新 更多