Sự khác biệt cơ bản giữa gấp và giảm trong Kotlin là gì? Khi nào nên sử dụng?


130

Tôi khá bối rối với cả hai chức năng này fold()reduce()trong Kotlin, bất cứ ai cũng có thể cho tôi một ví dụ cụ thể để phân biệt cả hai chức năng này không?


2
gấpgiảm .
Zoe

4
Hãy xem điều này để thảo luận cơ bản sâu sắc về chủ đề này
GhostCat

2
@LunarWatcher, tôi đã xem những tài liệu đó, nhưng không nhận được, đó là câu hỏi được đăng, bạn có thể đưa ra ví dụ không?
TapanHP

1
@MattKlein đã hoàn thành
Jayson Minard

Câu trả lời:


280

fold nhận một giá trị ban đầu và lệnh gọi đầu tiên của lambda mà bạn truyền tới nó sẽ nhận giá trị ban đầu đó và phần tử đầu tiên của bộ sưu tập làm tham số.

Ví dụ: lấy mã sau để tính tổng của danh sách các số nguyên:

listOf(1, 2, 3).fold(0) { sum, element -> sum + element }

Cuộc gọi đầu tiên đến lambda sẽ có các tham số 01.

Có khả năng vượt qua trong một giá trị ban đầu là hữu ích nếu bạn phải cung cấp một số loại giá trị hoặc tham số mặc định cho hoạt động của mình. Ví dụ: nếu bạn đang tìm kiếm giá trị tối đa trong danh sách, nhưng vì lý do nào đó muốn trả về ít nhất 10, bạn có thể làm như sau:

listOf(1, 6, 4).fold(10) { max, element ->
    if (element > max) element else max
}

reducekhông lấy một giá trị ban đầu, mà thay vào đó bắt đầu bằng phần tử đầu tiên của bộ sưu tập dưới dạng bộ tích lũy (được gọi sumtrong ví dụ sau).

Ví dụ: hãy thực hiện lại tổng số nguyên:

listOf(1, 2, 3).reduce { sum, element -> sum + element }

Cuộc gọi đầu tiên đến lambda ở đây sẽ có các tham số 12.

Bạn có thể sử dụng reducekhi hoạt động của bạn không phụ thuộc vào bất kỳ giá trị nào ngoài các giá trị trong bộ sưu tập bạn đang áp dụng.


47
Lời giải thích hay! Tôi cũng nói rằng, bộ sưu tập trống không thể giảm, nhưng có thể được gấp lại.
Miha_x64

Hãy xem, m ở cấp độ mới bắt đầu trong Kotlin, ví dụ đầu tiên bạn đưa ra bạn có thể giải thích nó nhiều hơn với một số bước và câu trả lời cuối cùng không? sẽ giúp ích rất nhiều
TapanHP

3
@TapanHP emptyList<Int>().reduce { acc, s -> acc + s }sẽ tạo ra một ngoại lệ, nhưng emptyList<Int>().fold(0) { acc, s -> acc + s }vẫn ổn.
Miha_x64

31
giảm cũng buộc sự trở lại của lambda phải cùng loại với các thành viên trong danh sách, điều này không đúng với nếp gấp. Đây là một hệ quả quan trọng của việc tạo thành phần đầu tiên của danh sách, giá trị ban đầu của bộ tích lũy.
andresp

4
@andresp: giống như một ghi chú cho sự hoàn chỉnh: nó không phải là cùng một loại. Các thành viên trong danh sách cũng có thể là một kiểu con của trình tích lũy: điều này không hoạt động listOf<Int>(1, 2).reduce { acc: Number, i: Int -> acc.toLong() + i }(kiểu danh sách là Int trong khi kiểu trình tích lũy được khai báo là Số và thực sự là Dài)
Boris

11

Sự khác biệt lớn về chức năng mà tôi sẽ gọi ra (được đề cập trong các bình luận về câu trả lời khác, nhưng có thể khó hiểu) là điều đó reduce sẽ tạo ra một ngoại lệ nếu được thực hiện trên một bộ sưu tập trống.

listOf<Int>().reduce { x, y -> x + y }
// java.lang.UnsupportedOperationException: Empty collection can't be reduced.

Điều này là do .reducekhông biết giá trị nào sẽ trả về trong trường hợp "không có dữ liệu".

Tương phản điều này với .fold, yêu cầu bạn cung cấp "giá trị bắt đầu", đây sẽ là giá trị mặc định trong trường hợp bộ sưu tập trống:

val result = listOf<Int>().fold(0) { x, y -> x + y }
assertEquals(0, result)

Vì vậy, ngay cả khi bạn không muốn tổng hợp bộ sưu tập của mình thành một yếu tố duy nhất thuộc loại khác (không liên quan) (chỉ .foldcho phép bạn làm), nếu bộ sưu tập bắt đầu của bạn có thể trống thì bạn phải kiểm tra bộ sưu tập của mình kích thước đầu tiên và sau đó .reduce, hoặc chỉ sử dụng.fold

val collection: List<Int> = // collection of unknown size

val result1 = if (collection.isEmpty()) 0
              else collection.reduce { x, y -> x + y }

val result2 = collection.fold(0) { x, y -> x + y }

assertEquals(result1, result2)
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.