Có sự khác biệt giữa foreach và bản đồ?


218

Ok đây là một câu hỏi khoa học máy tính, hơn là một câu hỏi dựa trên một ngôn ngữ cụ thể, nhưng có sự khác biệt giữa một hoạt động bản đồ và một hoạt động foreach? Hay họ chỉ đơn giản là tên khác nhau cho cùng một điều?


Thật kỳ lạ, nếu tôi nhận được một Iterator[String]từ scala.io.Source.fromFile("/home/me/file").getLines()và áp dụng .foreach(s => ptintln(s))cho nó, nó được in ok nhưng sẽ trống ngay sau đó. Đồng thời nếu tôi áp dụng .map(ptintln(_))cho nó - nó chỉ trống và không có gì được in.
Ivan

Câu trả lời:


294

Khác nhau.

foreach lặp lại một danh sách và áp dụng một số thao tác với các tác dụng phụ cho từng thành viên trong danh sách (chẳng hạn như lưu từng cái vào cơ sở dữ liệu chẳng hạn)

ánh xạ lặp lại qua một danh sách, biến đổi từng thành viên của danh sách đó và trả về một danh sách khác có cùng kích thước với các thành viên được chuyển đổi (chẳng hạn như chuyển đổi danh sách các chuỗi thành chữ hoa)


Cảm ơn, tôi nghĩ đó là một cái gì đó dọc theo những dòng này nhưng không chắc chắn!
Robert Gould

19
nhưng bạn cũng có thể có các tác dụng phụ xảy ra trong chức năng bản đồ. Tôi thích câu trả lời này: stackoverflow.com/a/4927981/375688
TinyTimZamboni

6
Một điểm quan trọng cần đề cập đến (điều này đặc biệt đúng trong Scala) là một cuộc gọi đến mapnào không dẫn đến việc thực hiện của logic cơ bản của nó cho đến khi danh sách chuyển đổi dự kiến được triệu tập. Ngược lại, foreachhoạt động của được tính toán ngay lập tức.
bigT

124

Sự khác biệt quan trọng giữa chúng là maptích lũy tất cả các kết quả vào một bộ sưu tập, trong khi foreachtrả về không có gì. mapthường được sử dụng khi bạn muốn chuyển đổi một tập hợp các phần tử bằng một hàm, trong khi foreachchỉ cần thực hiện một hành động cho mỗi phần tử.


Cảm ơn! Bây giờ tôi đã hiểu sự khác biệt. Nó đã trở nên mơ hồ đối với tôi trong một thời gian khá lâu
Robert Gould

Cũng rất quan trọng!
Мати ереререр

40

Nói tóm lại, foreachlà để áp dụng một thao tác trên từng phần tử của một tập hợp các phần tử, trong khi đó maplà để chuyển đổi một bộ sưu tập thành một bộ sưu tập khác.

Có hai sự khác biệt đáng kể giữa foreachmap.

  1. foreachkhông có hạn chế về mặt khái niệm đối với hoạt động mà nó áp dụng, ngoài việc có thể chấp nhận một yếu tố làm đối số. Nghĩa là, hoạt động có thể không làm gì, có thể có tác dụng phụ, có thể trả về giá trị hoặc có thể không trả về giá trị. Tất cả foreachquan tâm là lặp đi lặp lại một tập hợp các phần tử và áp dụng thao tác trên từng phần tử.

    mapmặt khác, có một hạn chế đối với hoạt động: nó mong muốn hoạt động trả về một phần tử và có thể cũng chấp nhận một phần tử làm đối số. Các maphoạt động lặp lại trên một tập hợp các yếu tố, áp dụng các hoạt động trên mỗi phần tử, và cuối cùng là lưu trữ các kết quả của mỗi lời gọi của hoạt động vào bộ sưu tập khác. Nói cách khác, map biến một bộ sưu tập thành một bộ sưu tập khác.

  2. foreachlàm việc với một bộ sưu tập các yếu tố Đây là bộ sưu tập đầu vào.

    map hoạt động với hai bộ sưu tập các phần tử: bộ sưu tập đầu vào và bộ sưu tập đầu ra.

Không có gì sai khi liên quan đến hai thuật toán: trên thực tế, bạn có thể xem hai thuật toán phân cấp, nơi mapchuyên môn hóa foreach. Đó là, bạn có thể sử dụng foreachvà có thao tác chuyển đổi đối số của nó và chèn nó vào một bộ sưu tập khác. Vì vậy, foreachthuật toán là một sự trừu tượng, khái quát hóa của mapthuật toán. Trong thực tế, vì foreachkhông có hạn chế nào đối với hoạt động của nó, chúng ta có thể nói rằng đó foreachlà cơ chế lặp đơn giản nhất ngoài kia và nó có thể làm bất cứ điều gì mà một vòng lặp có thể làm. map, cũng như các thuật toán chuyên dụng khác, có tính biểu cảm: nếu bạn muốn ánh xạ (hoặc biến đổi) bộ sưu tập này sang bộ sưu tập khác, ý định của bạn sẽ rõ ràng hơn nếu bạn sử dụng mapso với khi bạn sử dụng foreach.

Chúng ta có thể mở rộng cuộc thảo luận này hơn nữa và xem xét copythuật toán: một vòng lặp nhân bản một bộ sưu tập. Thuật toán này cũng là một chuyên ngành của foreachthuật toán. Bạn có thể định nghĩa một thao tác, được đưa ra một phần tử, sẽ chèn cùng phần tử đó vào một bộ sưu tập khác. Nếu bạn sử dụng foreachvới thao tác đó, bạn thực sự đã thực hiện copythuật toán, mặc dù giảm rõ ràng, biểu cảm hoặc nhân chứng. Chúng ta hãy đưa nó đi xa hơn: Chúng ta có thể nói rằng đó maplà một chuyên môn hóa copy, bản thân nó là một chuyên môn hóa foreach. mapcó thể thay đổi bất kỳ yếu tố nào mà nó lặp đi lặp lại. Nếu mapkhông thay đổi bất kỳ phần tử nào thì nó chỉ sao chép các phần tử và sử dụng bản sao sẽ thể hiện ý định rõ ràng hơn.

Các foreachthuật toán riêng của mình có thể hoặc không có thể có một giá trị trả về, tùy thuộc vào ngôn ngữ. Trong C ++, ví dụ, foreachtrả về thao tác mà nó nhận được ban đầu. Ý tưởng là hoạt động có thể có trạng thái và bạn có thể muốn hoạt động đó quay lại để kiểm tra xem nó đã phát triển như thế nào qua các yếu tố. mapcũng vậy, có thể hoặc không thể trả về giá trị. Trong C ++ transform(tương đương với mapở đây) tình cờ trả lại một iterator vào cuối bộ chứa đầu ra (bộ sưu tập). Trong Ruby, giá trị trả về maplà chuỗi đầu ra (bộ sưu tập). Vì vậy, giá trị trả về của các thuật toán thực sự là một chi tiết triển khai; tác dụng của chúng có thể hoặc không thể là những gì chúng trở lại.


Để biết ví dụ về cách .forEach()có thể được sử dụng để thực hiện .map(), hãy xem tại đây: stackoverflow.com/a/39159854/1524693
Chinoto Vokro

32

Array.protototype.mapphương pháp & Array.protototype.forEachđều khá giống nhau.

Chạy mã sau: 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));
});

Họ đưa ra kết quả chính xác.

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

Nhưng vòng xoắn xuất hiện khi bạn chạy đoạn mã sau: -

Ở đây tôi chỉ cần gán kết quả của giá trị trả về từ các phương thức bản đồ và 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();

Bây giờ kết quả là một cái gì đó khó khăn!

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

Phần kết luận

Array.prototype.maptrả về một mảng nhưng Array.prototype.forEachkhông. Vì vậy, bạn có thể thao tác mảng trả về bên trong hàm gọi lại được truyền cho phương thức bản đồ và sau đó trả về nó.

Array.prototype.forEach chỉ đi qua mảng đã cho để bạn có thể thực hiện công việc của mình trong khi đi bộ mảng.


11

sự khác biệt 'dễ thấy nhất' là bản đồ tích lũy kết quả trong một bộ sưu tập mới, trong khi việc tìm kiếm chỉ được thực hiện cho chính việc thực hiện.

nhưng có một vài giả định bổ sung: vì 'mục đích' của bản đồ là danh sách các giá trị mới, nên nó không thực sự quan trọng đối với thứ tự thực hiện. trong thực tế, một số môi trường thực thi tạo mã song song hoặc thậm chí giới thiệu một số ghi nhớ để tránh gọi các giá trị lặp lại hoặc lười biếng, để tránh gọi một số.

foreach, mặt khác, được gọi cụ thể cho các tác dụng phụ; do đó thứ tự là quan trọng và thường không thể song song.


11

Câu trả lời ngắn: mapforEachkhác nhau. Ngoài ra, nói một cách không chính thức, maplà một superset nghiêm ngặt forEach.

Câu trả lời dài: Đầu tiên, chúng ta hãy đưa ra một mô tả dòng forEachmap:

  • forEach lặp lại trên tất cả các phần tử, gọi hàm được cung cấp trên mỗi phần tử.
  • map lặp lại trên tất cả các phần tử, gọi hàm được cung cấp trên mỗi phần tử và tạo ra một mảng được chuyển đổi bằng cách ghi nhớ kết quả của mỗi lệnh gọi hàm.

Trong nhiều ngôn ngữ, forEachthường được gọi là chỉ each. Các cuộc thảo luận sau đây chỉ sử dụng JavaScript để tham khảo. Nó thực sự có thể là bất kỳ ngôn ngữ khác.

Bây giờ, hãy sử dụng từng chức năng này.

Sử dụng forEach:

Nhiệm vụ 1: Viết hàm printSquares, chấp nhận một mảng các số arrvà in bình phương của mỗi phần tử trong đó.

Giải pháp 1:

var printSquares = function (arr) {
    arr.forEach(function (n) {
        console.log(n * n);
    });
};

Sử dụng map:

Nhiệm vụ 2: Viết một hàm selfDot, chấp nhận một mảng các số arrvà trả về một mảng trong đó mỗi phần tử là bình phương của phần tử tương ứng arr.

Ngoài ra: Ở đây, về mặt tiếng lóng, chúng tôi đang cố gắng bình phương mảng đầu vào. Chính thức đặt, chúng tôi đang cố gắng tính toán sản phẩm chấm với chính nó.

Giải pháp 2:

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

Làm thế nào là mapmột superset của forEach?

Bạn có thể sử dụng mapđể giải quyết cả hai nhiệm vụ, Nhiệm vụ 1Nhiệm vụ 2 . Tuy nhiên, bạn không thể sử dụng forEachđể giải quyết Nhiệm vụ 2 .

Trong Giải pháp 1 , nếu bạn chỉ cần thay thế forEachbằng map, giải pháp sẽ vẫn hợp lệ. Tuy nhiên, trong Giải pháp 2 , thay thế mapbằng forEachsẽ phá vỡ giải pháp làm việc trước đây của bạn.

Thực hiện forEachtheo map:

Một cách khác để nhận ra maptính ưu việt của nó là thực hiện forEachtheo nghĩa map. Vì chúng tôi là những lập trình viên giỏi, chúng tôi sẽ không đắm chìm trong ô nhiễm không gian tên. Chúng tôi sẽ gọi cho chúng tôi forEach, chỉ each.

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

Bây giờ, nếu bạn không thích những prototypeđiều vô nghĩa, ở đây bạn đi:

var each = function (arr, func) {
    arr.map(func); // Or map(arr, func);
};

Vì vậy, umm .. Tại sao forEachthậm chí còn tồn tại?

Câu trả lời là hiệu quả. Nếu bạn không quan tâm đến việc chuyển đổi một mảng thành một mảng khác, tại sao bạn nên tính toán mảng đã chuyển đổi? Chỉ để đổ nó? Dĩ nhiên là không! Nếu bạn không muốn chuyển đổi, bạn không nên thực hiện chuyển đổi.

Vì vậy, trong khi bản đồ có thể được sử dụng để giải quyết Nhiệm vụ 1 , có lẽ không nên. Đối với mỗi là ứng cử viên phù hợp cho điều đó.


Câu trả lời gốc:

Mặc dù tôi rất đồng ý với câu trả lời của @madlep, tôi muốn chỉ ra rằng đó map()là một siêu tập hợp nghiêm ngặt của forEach().

Có, map()thường được sử dụng để tạo ra một mảng mới. Tuy nhiên, nó có thể cũng được sử dụng để thay đổi mảng hiện tại.

Đây là một ví dụ:

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!!"]

Trong ví dụ trên, ađược đặt thuận tiện sao a[i] === icho i < a.length. Mặc dù vậy, nó thể hiện sức mạnh củamap() .

Đây là mô tả chính thức củamap() . Lưu ý rằng map()thậm chí có thể thay đổi mảng mà nó được gọi! Kêumap() .

Hy vọng điều này sẽ giúp.


Chỉnh sửa ngày 10 tháng 11 năm 2015: Thêm công phu.


-1, vì điều này chỉ hợp lệ trong javascript; trong khi câu hỏi đặc biệt nói về các khái niệm khoa học ngôn ngữ và khoa học máy tính, không triển khai hạn chế.
Javier

1
@Javier: Hmm .., tôi đã đồng ý với bạn về câu trả lời của tôi là cụ thể về JavaScript. Nhưng hãy tự hỏi điều này: Nếu một ngôn ngữ có mapchức năng riêng nhưng không forEach; bạn không thể đơn giản là không sử dụng mapthay vì forEach? Mặt khác, nếu một ngôn ngữ có forEachnhưng không map, bạn phải thực hiện ngôn ngữ của riêng bạn map. Bạn không thể đơn giản sử dụng forEachthay vì map. Nói cho tôi biết bạn nghĩ gì.
Sumukh Barve

3

Dưới đây là một ví dụ trong Scala sử dụng danh sách: danh sách trả về bản đồ, foreach không trả về gì.

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

Vì vậy, bản đồ trả về danh sách kết quả từ việc áp dụng hàm f cho từng thành phần danh sách:

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 chỉ áp dụng f cho mỗi phần tử:

scala> var sum = 0
sum: Int = 0

scala> list foreach (sum += _)

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

2

Nếu bạn đang nói về Javascript nói riêng, thì điểm khác biệt là mapchức năng lặp trong khi forEachlà trình lặp.

Sử dụng mapkhi bạn muốn áp dụng một thao tác cho từng thành viên trong danh sách và lấy lại kết quả dưới dạng danh sách mới, mà không ảnh hưởng đến danh sách ban đầu.

Sử dụng forEachkhi bạn muốn làm một cái gì đó trên cơ sở từng yếu tố của danh sách. Bạn có thể thêm những thứ vào trang, ví dụ. Về cơ bản, thật tuyệt vời khi bạn muốn "tác dụng phụ".

Sự khác biệt khác: forEachkhông trả về gì (vì đây thực sự là hàm điều khiển luồng) và hàm được truyền vào sẽ tham chiếu đến chỉ mục và toàn bộ danh sách, trong khi bản đồ trả về danh sách mới và chỉ truyền vào phần tử hiện tại.


0

ForEach cố gắng áp dụng một chức năng như ghi vào db, v.v. trên từng phần tử của RDD mà không trả lại bất cứ điều gì.

Nhưng map()áp dụng một số chức năng trên các yếu tố của rdd và trả về rdd. Vì vậy, khi bạn chạy phương thức bên dưới, nó sẽ không bị lỗi ở dòng 3 nhưng trong khi thu thập rdd sau khi áp dụng foreach, nó sẽ thất bại và đưa ra một lỗi thông báo

Tệp "<stdin>", dòng 5, trong <mô-đun>

AttributionError: Đối tượng 'noneType' không có thuộc tính 'thu thập'

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())
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.