0

这就是我想要做的:

INPUT: [1,2,3,-1,-2,-3]
OUTPUT:[1,1,1,-1,-1,-1]

我试过这个:

signNum (x:n) = map(if x>0 
            then 1 
            else -1)n

谁能告诉我我在哪里犯了逻辑错误?

4

4 回答 4

2

第一个问题是map需要一个函数。因此,您必须将if语句包装在 lambda 中。但是,这仍然不会完全符合您的要求。而不是将列表分成头部和尾部,您真的希望将您的函数映射到整个列表。

请记住,map它只需要一个函数并将其应用于每个元素。由于您想将每个元素转换为1or -1,您只需要将适当的函数映射到您的列表。

所以最后,你得到:

sigNum ls = map (\ x -> if x > 0 then 1 else - 1) ls
于 2012-10-14T17:02:44.023 回答
1

在这种情况下,将函数分解为更小的部分可能更容易。

在最低级别,可以计算signum单个数字的 ,即:

signum :: (Num a, Ord a) => a -> a
signum x = if x > 0 then 1 else -1

一旦你有了这个,你就可以在一个数字列表中使用它,就像你对任何函数一样:

signNum ls = map signum ls

(ps 是什么signum 0意思?您当前的定义有signum 0 = -1.

如果您需要扩展功能以包含这种情况,最好使用警卫:

signum x | x < 0 = -1
         | x == 0 = 0
         | otherwise = 1

或案例陈述:

signum x = case compare x 0 of 
             LT -> -1
             EQ -> 0
             GT -> 1

)

于 2012-10-14T17:13:04.677 回答
1

您的评论表明您希望能够通过理解来做到这一点。

如何使用理解

如果你确实想通过理解来做到这一点,你可以这样做

signNum ls = [ if x>0 then 1 else -1| x <- ls ]

如何不使用理解

...但你不能把条件放在右手边

brokenSignNum ls = [ 1| x <- ls, x > 0 ]

因为在右侧放置一个条件会删除任何不满足条件的东西 - 你所有的否定都会被忽略!这将缩短您的列表而不是替换元素。我们试试看

brokenSignNum2 ls = [ 1| x <- ls, x > 0 ] ++ [ -1| x <- ls, x <= 0 ]

这与您的原始列表长度相同,但所有正面都在前面。

摘要:您必须将此条件表达式放在左侧,因为这是唯一可以发生替换的地方 - 在右侧它会删除。

零是负数吗?

请注意,您的 if 语句将 0 视为负数。你确定你想要那个吗?也许你会更好地单独定义一个数字的符号:

sign x | x == 0 = 0   -- if x is zero, use zero
       | x > 0 =  1   -- use 1 for positives
       | x < 0 = -1   -- use -1 for negatives

workingSignNum1 ls = [sign x | x <- ls]

但是sign(几乎)与函数相同signum,所以我们不妨使用它

workingSignNum2 ls = [signum x | x <- ls]

让它更整洁

现在,对于基本上意味着“替换xsign x所有列表ls”的大量语法。我们经常做这种事情,所以我们可以写一个函数来做:

replaceUsing :: (a -> b) -> [a] -> [b]
replaceUsing f xs = [f x | x <- xs]

但是已经有一个功能可以做到这一点!它被称为map。所以我们可以在列表中使用 map :

quiteSlickSignNum :: Num a => [a] -> [a]
quiteSlickSignNum ls = map signum ls

甚至更光滑:

slickSignNum :: Num a => [a] -> [a]
slickSignNum = map signum

这就是我定义它的方式。

为什么说sign几乎一样signum

sign接受一个数字并返回一个数字 , 1, 0or -1,但是 的类型是1什么?
好吧,1具有类型Num a => a,因此您可以将其与任何数字类型一起使用。这意味着 sign接受任何类型的数字并返回任何类型的数字,所以它的类型是

sign :: (Num a,Num b) => a -> b

所以我的版本sign可以给你一个不同的类型。如果你尝试一下,你会发现3 * sign 4.5给你3,不是3.0,所以你可以从中得到一个整数,但如果你这样做3.14 * sign 7.4,你会得到3.14,所以你也可以得到一个小数类型。相比之下,

signum :: Num a => a -> a

所以它只能把你给它的类型还给你——3 * signum 4.5给你3.0

于 2012-10-15T06:15:19.527 回答
0

错误消息“no instance for Num”是新 Haskelers 最难破译的错误消息之一。首先,这是您尝试编写的函数的完全多态类型签名(我将其添加到源文件中以得到与您相同的错误):

signNum :: (Ord a, Num a) => [a] -> [a]

发现错误

现在,编译错误消息说:

无法从 prog.hs:3:17 处的文字 '1' 产生的上下文 (Ord a, Num a) 推断出 (Num (a -> a))

请注意,错误消息为我们提供了问题的位置。它说“文字 1”file_name.hs:line_number:column_number是问题所在。

signNum (x:n) = map(if x>0 
            then 1 -- <-- here's the problem! (according to that message)
            else -1)n

了解错误

现在,错误消息还建议了一些可能的修复,但是每当您遇到“没有实例Num”时,建议的“可能修复”几乎总是错误的,因此请忽略它们。(我希望 GHC 能够为Num这样的相关内容提供更好的错误消息)。

回想一下错误消息所说的内容:

无法推断 (Num (a -> a)) ... 源自文字 '1' ...

这意味着您将文字1放在上下文预期类型的​​某处
a -> a1显然不是函数,所以要么上下文错误,要么数字 1 错误。

那么字面量 1 的上下文是什么?

找到错误(准确)

(if x > 0
  then <<hole>>
  else -1)

Haskell 中的 if 语句产生一个值。if 语句的分支必须具有相同的类型,if 语句的类型由分支的类型决定。

在这里,另一个分支的值为-1,它是一个数字。因此,我们期望<<hole>>具有相同的类型:数字。好吧,这显然不是问题(因为 1是一个数字),所以让我们看一下表达式的上下文。

map <<hole>> n

map函数需要一个函数作为它的第一个参数。但是,我们知道<<hole>>会产生一个数字。尤里卡!这是差异:我们map在它期望函数的地方给出了一个数字。

纠正错误

显而易见的解决方案——现在我们确切地知道问题出在哪里和在哪里——是给出map一个函数,而不是一个数字。有关详细信息,请参阅其他各种答案。

于 2012-10-15T16:01:12.907 回答