将列表中的每个数字乘以一个数

3

我是Haskell的新手,正在尝试编写一个函数,可以将一个整数与列表中的每个元素相乘。

例如:

mult 2 [2,4,6]

返回:

[4,8,12]
3个回答

18

将问题拆解成概念步骤:

  • 你想对列表的每个元素执行某个操作,这是由map函数提供的一般操作。使用它,你可以忽略列表,只考虑需要对单个元素执行的操作。

  • 你想将两个数字相乘。在任何一个函数使用中,其中一个数字将是常量,因此我们可以将其作为函数的参数给定一个名称:mult x = ...。现在我们可以将x视为常量,只关注另一个数字。

  • 另一个数字不是常量,因此你需要一个函数,而不仅仅是一个简单的表达式。Haskell 提供了“操作符部分应用”来使用像(*)这样的中缀操作符,因此使用x我们得到 (x *)

  • 回溯最后几步,你现在给x赋予了一个名称,并创建了一个函数,然后将其传递给map


mult x = map (x *)

...现在你已经完成了,如果你想的话。但对于初学者来说,将列表作为显式参数可能更清晰:

mult x ys = map (x *) ys

两种形式都是做同样的事情。


9

我最初的回答有点轻描淡写,所以让我们再试一次。我的意图是双重的:一方面为了弥补我对新手写了一个傲慢的、睡眠不足的回答,:-),另一方面是尝试暗示无点风格的观点:

mult_l = map . (*)

我该代码做什么?

将其视为流水线方式会很有帮助:

  • 这段代码将(*)传入map中。

  • (*)的类型是什么?它是Num a => a -> a -> a。这意味着它做了什么?它接受一个数字(称之为x),并给你另一个函数,这个函数将——如果给定另一个数字(称之为y)——计算x“乘以”y。(我把“乘以”放在括号里,因为Num是一个类型类...)

  • 现在你要将(*)map组合起来。 map是什么?好吧,让我们看看它的类型:(a -> b) -> [a] -> [b]。所以现在,map接受一个函数作为参数,并将该函数应用于列表的每个成员。

现在想一想组合是如何工作的:(f . g) x = f (g x)
如果给定一个参数,比如说2:
mult_l 2

真的很

(map . (*)) 2

这实际上只是:真的

map (* 2)

现在,map需要两个参数,一个是函数(告诉它“你想让我做什么?”),另一个是列表(告诉它“你想对哪个进行操作?”)。map然后逐个将列表中的每个项,按点应用于该函数的元素。
这意味着如果你拿到像这样的东西:
map (* 2) [1,2,3]

然后map会取出列表,查看第一个元素,然后乘以2,查看第二个元素,等等...
因此,map . (*)类型Num a => a -> [a] -> [a],因为它接收一个数字x,然后将高阶函数\x -> (* x)传递给map
现在对于你的挑战问题(因为你是Haskell新手),如何编写map?我会给你一个提示:
  • 如果要在空列表上映射函数,则返回空列表。
  • 如果要在列表头部及其尾部上映射函数,则取出头部,将函数应用于头部,然后对列表的其余部分执行相同的操作。
从中可以得到map的递归定义,以及相关的归纳原理。

你可能会想知道的另一件事是:为什么我必须写map . (*)而不是只写map (*)。如果你思考一段时间,你可能会对无点风格的概念有更深入的了解。


2
事实上,专业高尔夫球手会编写“map . (*)”并完成它。达到这一点需要一定的复杂性,因此感谢C.A.McCann记录了大部分旅程。 - pigworker

8

由于没有人谈论列表推导,因此只是为了介绍另一种方法。

mult x ys = [i*x | i <- ys]

纯粹出于我的兴趣,为什么要踩这个帖子呢?我同意它的解释方式不同,对于初学者来说可能有些超纲,但是@Satvik并没有声称它是针对初学者的,这是一个很好的替代方案。 - Kristopher Micinski
@KristopherMicinski 我个人更喜欢使用 map 而不是列表推导式。我只是在建议还有其他的方法可以实现,你可以选择自己喜欢的方式。 - Satvik
我明白,但我的问题仍然存在。虽然这对我来说也不是“规范”的方式,但我认为它并不值得被踩。这不是一种本质上错误的方法,展示另一个目的也是有益的。 - Kristopher Micinski

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接