foreach和map有什么区别?

244

嗯,这更像是一个计算机科学问题,而不是针对特定语言的问题,但是map操作和foreach操作之间有区别吗?还是它们只是同一种东西的不同名称?


有趣的是,如果我从scala.io.Source.fromFile("/home/me/file").getLines()获取一个Iterator[String]并对其应用.foreach(s => ptintln(s)),它会被正确打印,但在此之后就变为空了。同时,如果我对其应用.map(ptintln(_)),它只会变为空,什么也不会被打印出来。 - Ivan
9个回答

330
不同。
foreach遍历列表并对每个列表成员执行一些具有副作用的操作(例如将每个成员保存到数据库中)。
map遍历列表,为该列表的每个成员创建一个转换后的元素,并返回具有转换后元素的相同大小的另一个列表(例如将字符串列表转换为大写)。

谢谢,我也觉得是这样,但不确定! - Robert Gould
21
但是在 map 函数中也可能发生副作用。我喜欢这个答案:https://dev59.com/lnA75IYBdhLWcg3w6Nr8#4927981 - TinyTimZamboni
7
有一个重要的点需要提到(特别是在Scala中),那就是调用 map 并不会立即执行其底层逻辑,直到我们需要访问预期转换后的列表时才会执行。相反, foreach 的操作会立即计算。 - solimant

141

它们之间的重要区别是 map 将所有结果累积到一个集合中,而 foreach 不返回任何内容。当您想使用函数转换元素集合时,通常使用 map,而当您只需要为每个元素执行操作时,则使用 foreach


谢谢!现在我明白了区别。这个问题困扰我有一段时间了。 - Robert Gould

40
简而言之,foreach用于对元素集合中的每个元素应用操作,而map用于将一个集合转换为另一个集合。 foreachmap之间有两个重要区别。
1. foreach在应用操作时没有概念上的限制,除了可能接受元素作为参数。也就是说,操作可以不做任何事情,可能会产生副作用,可能返回一个值或者不返回值。所有foreach关心的只是迭代元素集合,并在每个元素上应用操作。
map则对操作有一定的限制:它希望操作返回一个元素,并且可能也接受一个元素作为参数。map操作迭代元素集合,在每个元素上应用操作,并最终将每次操作调用的结果存储到另一个集合中。换句话说,map将一个集合转换为另一个集合。
2. foreach仅使用一个元素集合,即输入集合。
map需要两个元素集合:输入集合和输出集合。

这两种算法之间的关系并没有错误:实际上,您可以将这两种算法视为分层结构,其中 mapforeach 的特化。也就是说,您可以使用 foreach,并让操作将其参数转换并插入到另一个集合中。因此,foreach 算法是 map 算法的抽象、概括。实际上,由于 foreach 对其操作没有限制,我们可以安全地说,foreach 是最简单的循环机制,它可以做任何循环可以做的事情。而 map 以及其他更专业的算法则是为了表达能力:如果您希望将一个集合映射(或转换)到另一个集合中,如果使用 map,则意图更加清晰明确。

我们可以进一步扩展这个讨论,并考虑 copy 算法:一个克隆集合的循环。这个算法也是 foreach 算法的一种特化。您可以定义一个操作,给定一个元素,将该元素插入到另一个集合中。如果您使用该操作的 foreach,则实际上执行了 copy 算法,尽管表述不如明确、表达能力弱。更进一步:我们可以说 mapcopy 的特化,而 copy 则是 foreach 的特化。 map 可以更改其迭代的任何元素。如果 map 没有更改任何元素,则仅是复制了元素,使用 copy 将更清晰地表达意图。

foreach算法本身可能有返回值,也可能没有,这取决于编程语言。例如,在C++中,foreach返回原始接收到的操作。其想法是该操作可能具有状态,并且您可能希望将该操作返回以检查它如何随元素而演变。同样,map也可能有或没有返回值。在C++中,map的等价物transform恰好返回输出容器(集合)的末尾迭代器。在Ruby中,map的返回值是输出序列(集合)。因此,算法的返回值实际上是一种实现细节;它们的效果可能与它们返回的内容相同,也可能不同。


有关如何使用.forEach()实现.map()的示例,请参见此处:http://stackoverflow.com/a/39159854/1524693。 - Chinoto Vokro

34

Array.prototype.map 方法和 Array.prototype.forEach 方法非常相似。

运行以下代码:http://labs.codecademy.com/bw1/6#:workspace

var arr = [1, 2, 3, 4, 5];

arr.map(function(val, ind, arr){
    console.log("arr[" + ind + "]: " + Math.pow(val,2));
});

console.log();

arr.forEach(function(val, ind, arr){
    console.log("arr[" + ind + "]: " + Math.pow(val,2));
});

它们给出完全相同的结果。

arr[0]: 1
arr[1]: 4
arr[2]: 9
arr[3]: 16
arr[4]: 25

arr[0]: 1
arr[1]: 4
arr[2]: 9
arr[3]: 16
arr[4]: 25

但是当你运行以下代码时,有了一个转折:

在这里,我只是简单地分配了来自map和forEach方法的返回值结果。

var arr = [1, 2, 3, 4, 5];

var ar1 = arr.map(function(val, ind, arr){
    console.log("arr[" + ind + "]: " + Math.pow(val,2));
    return val;
});

console.log();
console.log(ar1);
console.log();

var ar2 = arr.forEach(function(val, ind, arr){
    console.log("arr[" + ind + "]: " + Math.pow(val,2));
    return val;
});

console.log();
console.log(ar2);
console.log();

现在的结果有些棘手!

arr[0]: 1
arr[1]: 4
arr[2]: 9
arr[3]: 16
arr[4]: 25

[ 1, 2, 3, 4, 5 ]

arr[0]: 1
arr[1]: 4
arr[2]: 9
arr[3]: 16
arr[4]: 25

undefined

结论

Array.prototype.map 返回一个数组,而Array.prototype.forEach不返回任何东西。因此,你可以在传递给 map 方法的回调函数中操作返回的数组,然后返回它。

Array.prototype.forEach 只会遍历给定的数组,因此你可以在遍历时进行操作。


12
简短回答:map和forEach不同。而且,非正式地说,map是forEach的严格超集。
长回答:首先,让我们给forEach和map提供一个简洁的描述:
- forEach遍历所有元素,在每个元素上调用提供的函数。 - map遍历所有元素,在每个元素上调用提供的函数,并通过记住每个函数调用的结果生成转换后的数组。
在许多语言中,forEach通常被称为each。以下讨论仅以JavaScript作为参考。它实际上可以是任何其他语言。
现在,让我们使用这些功能。
使用forEach:
任务1:编写一个名为printSquares的函数,该函数接受一个数字数组arr,并打印其中每个元素的平方。
解决方案1:
var printSquares = function (arr) {
    arr.forEach(function (n) {
        console.log(n * n);
    });
};

使用map

任务2:编写一个名为selfDot的函数,该函数接受一个数字数组arr,并返回一个数组,其中每个元素都是arr中对应元素的平方。

注:在俚语中,我们试图将输入数组平方。形式上来说,我们试图计算它与自身的点积。

解决方案2:

var selfDot = function (arr) {
    return arr.map(function (n) {
        return n * n;
    });
};

如何理解mapforEach的超集?

使用map可以解决任务1任务2,但使用forEach无法解决任务2

解决方案1中,如果将forEach替换为map,则解决方案仍然有效。然而,在解决方案2中,用forEach替换map会破坏之前有效的解决方案。

通过map实现forEach

另一种理解map优越性的方式是通过map来实现forEach。作为良好的程序员,我们不会污染命名空间。我们将把我们的forEach称为each

Array.prototype.each = function (func) {
    this.map(func);
};

现在,如果你不喜欢那些原型的废话,这里有一个更好的选择:class
var each = function (arr, func) {
    arr.map(func); // Or map(arr, func);
};

那么,嗯...为什么需要forEach呢?

答案是效率。如果您不想将一个数组转换为另一个数组,为什么要计算转换后的数组呢?只是为了丢弃它吗?当然不是!如果你不想进行转换,你就不应该做转换。

因此,虽然map可以用来解决任务1,但它可能不应该这样做。对于每个元素都是正确的候选者。


原始答案:

虽然我基本上同意@madlep的答案,但我想指出map()forEach()的严格超集。

是的,map()通常用于创建新数组。但是,它也可以用于更改当前数组。

以下是一个例子:

var a = [0, 1, 2, 3, 4], b = null;
b = a.map(function (x) { a[x] = 'What!!'; return x*x; });
console.log(b); // logs [0, 1, 4, 9, 16] 
console.log(a); // logs ["What!!", "What!!", "What!!", "What!!", "What!!"]

在上面的示例中,a 被方便地设置为 a[i] === i,其中 i < a.length。即使如此,它也展示了 map() 的强大之处。 这里是 map() 的官方描述。请注意,map() 甚至可能更改调用它的数组!万岁 map()
希望这有所帮助。
编辑于2015年11月10日:添加了详细说明。

“-1”只在JavaScript中有效;而问题特别涉及语言无关性和计算机科学概念,不限于具体实现。 - Javier
1
@Javier:嗯...我得同意你关于我的答案是JavaScript特定的观点。但是请问自己:如果一种语言有本地的map函数,但没有forEach,那么你不能简单地使用map代替forEach吗?相反,如果一种语言有forEach但没有map,那么你就必须实现自己的map。你不能简单地使用forEach代替map。告诉我你的想法。 - Sumukh Barve

11

最明显的区别是map会在新集合中累积结果,而foreach只是执行本身。

但还有一些额外的假设:由于map的“目的”是新值列表,因此执行顺序并不重要。实际上,某些执行环境会生成并行代码,甚至引入一些记忆化来避免调用重复的值,或者采用惰性求值的方式来完全避免某些调用。

另一方面,foreach是专门为了副作用而调用的;因此顺序很重要,并且通常无法并行化。


3

以下是使用Scala中列表的示例:map返回一个列表,foreach不返回任何内容。

def map(f: IntInt): List[Int]
def foreach(f: IntUnit): Unit

所以map返回应用函数f到每个列表元素后的列表:

scala> val list = List(1, 2, 3)
list: List[Int] = List(1, 2, 3)

scala> list map (x => x * 2)
res0: List[Int] = List(2, 4, 6)

Foreach 只是将 f 应用于每个元素:

scala> var sum = 0
sum: Int = 0

scala> list foreach (sum += _)

scala> sum
res2: Int = 6 // res1 is empty

2
如果您特别谈论JavaScript,那么map是一个循环函数,而forEach是一个迭代器。
当您想对列表的每个成员应用操作并将结果作为新列表返回而不影响原始列表时,请使用map
当您想基于列表的每个元素进行一些操作时,请使用forEach。例如,您可能正在向页面添加内容。从本质上讲,它非常适合需要“副作用”的情况。
其他区别: forEach不返回任何内容(因为它实际上是一个控制流函数),传入的函数会引用索引和整个列表,而map返回新列表并仅传入当前元素。

0

ForEach尝试对RDD的每个元素应用一个函数,例如写入到数据库等,而不返回任何内容。

但是map()将某些函数应用于rdd的元素并返回rdd。因此,当您运行下面的方法时,它不会在第3行失败,但在应用foreach后收集rdd时,它将失败并抛出一个错误,该错误说

File "<stdin>", line 5, in <module>

AttributeError: 'NoneType' object has no attribute 'collect'

nums = sc.parallelize([1,2,3,4,5,6,7,8,9,10])
num2 = nums.map(lambda x: x+2)
print ("num2",num2.collect())
num3 = nums.foreach(lambda x : x*x)
print ("num3",num3.collect())

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