sự khác biệt giữa FoldLeft và lessLeft trong Scala


196

Tôi đã học được sự khác biệt cơ bản giữa foldLeftreduceLeft

nếp gấp:

  • giá trị ban đầu phải được thông qua

giảm sức mạnh:

  • lấy phần tử đầu tiên của bộ sưu tập làm giá trị ban đầu
  • ném ngoại lệ nếu bộ sưu tập trống

Có sự khác biệt nào khác không?

Bất kỳ lý do cụ thể để có hai phương pháp với chức năng tương tự?



Sẽ thật tuyệt nếu bạn chỉnh sửa câu hỏi thành "sự khác biệt giữa gấp và giảm trong Scala".
pedram bashiri

Câu trả lời:


302

Vài điều cần đề cập ở đây, trước khi đưa ra câu trả lời thực tế:

  • Câu hỏi của bạn không liên quan gì left, đó là về sự khác biệt giữa giảm và gấp
  • Sự khác biệt hoàn toàn không phải là việc thực hiện, chỉ cần nhìn vào chữ ký.
  • Câu hỏi không liên quan gì đến Scala nói riêng, đó là về hai khái niệm lập trình chức năng.

Quay lại câu hỏi của bạn:

Đây là chữ ký của foldLeft (cũng có thể foldRightlà điểm mà tôi sẽ thực hiện):

def foldLeft [B] (z: B)(f: (B, A) => B): B

Và đây là chữ ký của reduceLeft (một lần nữa hướng không quan trọng ở đây)

def reduceLeft [B >: A] (f: (B, A) => B): B

Hai cái này trông rất giống nhau và do đó gây ra sự nhầm lẫn. reduceLeftlà một trường hợp đặc biệt củafoldLeft (điều này có nghĩa là đôi khi bạn có thể diễn đạt điều tương tự bằng cách sử dụng một trong hai).

Khi bạn gọi reduceLeftsay trên một List[Int]nghĩa đen, nó sẽ giảm toàn bộ danh sách các số nguyên thành một giá trị duy nhất, sẽ thuộc loạiInt (hoặc siêu kiểu Int, do đó [B >: A]).

Khi bạn gọi foldLeftnói trên mộtList[Int] nó sẽ gấp toàn bộ danh sách (tưởng tượng cuộn một mảnh giấy) thành một giá trị duy nhất, nhưng giá trị này không nhất thiết phải liên quan đến Int(do đó [B]).

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

def listWithSum(numbers: List[Int]) = numbers.foldLeft((List.empty[Int], 0)) {
   (resultingTuple, currentInteger) =>
      (currentInteger :: resultingTuple._1, currentInteger + resultingTuple._2)
}

Phương pháp này lấy một List[Int]và trả về một Tuple2[List[Int], Int]hoặc (List[Int], Int). Nó tính toán tổng và trả về một tuple với danh sách các số nguyên và đó là tổng. Bằng cách này, danh sách được trả lại, bởi vì chúng tôi đã sử dụng foldLeftthay vì foldRight.

Xem One Fold để thống trị tất cả để có giải thích sâu hơn.


Bạn có thể giải thích tại sao Blà một siêu kiểu A? Có vẻ như Bthực sự nên là một kiểu con A, không phải là siêu kiểu. Ví dụ, giả sử Banana <: Fruit <: Food, nếu chúng ta có một danh sách Fruits, có vẻ như nó có thể chứa một số Bananas, nhưng nếu nó chứa bất kỳ Foods nào thì loại đó sẽ là gì Food, đúng không? Vì vậy, trong trường hợp này, nếu Blà một siêu kiểu Avà có một danh sách chứa cả Bs và As, thì danh sách đó phải là loại Bchứ không phải A. Bạn có thể giải thích sự khác biệt này?
socom1880

Tôi không chắc chắn nếu tôi hiểu chính xác câu hỏi của bạn. Điều mà câu trả lời 5 tuổi của tôi nói về chức năng giảm là List[Banana]có thể rút gọn thành một Bananahoặc một Fruithoặc một Food. Bởi vì Fruit :> Bananavà 'Thức ăn:> Chuối'.
agilesteel

Vâng ... điều đó thực sự có ý nghĩa cảm ơn bạn. Ban đầu tôi đã hiểu nó là "một danh sách các loại Bananacó thể chứa một Fruit", không có nghĩa gì cả. Giải thích của bạn có ý nghĩa - fhàm được truyền vào reduce()có thể dẫn đến một Fruithoặc a Food, có nghĩa là Btrong chữ ký phải là một siêu lớp, không phải là một lớp con.
socom1880

193

reduceLeftchỉ là một phương pháp thuận tiện. Nó tương đương với

list.tail.foldLeft(list.head)(_)

11
Tốt, câu trả lời ngắn gọn :) Có thể muốn sửa chính tả của reducelftmặc dù
Hanxue

10
Câu trả lời tốt. Điều này cũng nhấn mạnh tại sao foldhoạt động trên một danh sách trống trong khi reducekhông.
Mansoor Siddiqui

44

foldLeftlà chung chung hơn, bạn có thể sử dụng nó để tạo ra một cái gì đó hoàn toàn khác với những gì bạn đặt vào ban đầu. Trong khi đó, reduceLeftchỉ có thể tạo ra kết quả cuối cùng của cùng loại hoặc siêu loại của bộ sưu tập. Ví dụ:

List(1,3,5).foldLeft(0) { _ + _ }
List(1,3,5).foldLeft(List[String]()) { (a, b) => b.toString :: a }

Các foldLeftsẽ áp dụng việc đóng cửa với kết quả xếp cuối cùng (lần đầu tiên sử dụng giá trị ban đầu) và giá trị tiếp theo.

reduceLeftmặt khác trước tiên sẽ kết hợp hai giá trị từ danh sách và áp dụng các giá trị đó vào bao đóng. Tiếp theo, nó sẽ kết hợp phần còn lại của các giá trị với kết quả tích lũy. Xem:

List(1,3,5).reduceLeft { (a, b) => println("a " + a + ", b " + b); a + b }

Nếu danh sách trống foldLeftcó thể trình bày giá trị ban đầu là kết quả pháp lý. reduceLeftmặt khác không có giá trị pháp lý nếu nó không thể tìm thấy ít nhất một giá trị trong danh sách.


5

Lý do cơ bản cả hai đều nằm trong thư viện tiêu chuẩn Scala có lẽ là vì cả hai đều nằm trong thư viện chuẩn Haskell (được gọi foldlfoldl1). Nếu reduceLeftkhông, nó thường được định nghĩa là một phương thức thuận tiện trong các dự án khác nhau.


5

Để tham khảo, reduceLeftsẽ lỗi nếu áp dụng cho một container rỗng với lỗi sau.

java.lang.UnsupportedOperationException: empty.reduceLeft

Làm lại mã để sử dụng

myList foldLeft(List[String]()) {(a,b) => a+b}

là một lựa chọn tiềm năng. Một cách khác là sử dụng reduceLeftOptionbiến thể trả về kết quả được bọc Tùy chọn.

myList reduceLeftOption {(a,b) => a+b} match {
  case None    => // handle no result as necessary
  case Some(v) => println(v)
}

2

Từ các nguyên tắc lập trình chức năng trong Scala (Martin Oderky):

Hàm reduceLeftđược định nghĩa dưới dạng hàm tổng quát hơn , foldLeft.

foldLeftgiống như reduceLeftnhưng lấy một bộ tích lũy z , như một tham số bổ sung, được trả về khi foldLeftđược gọi trong danh sách trống:

(List (x1, ..., xn) foldLeft z)(op) = (...(z op x1) op ...) op x

[trái ngược với reduceLeft, nó ném một ngoại lệ khi được gọi trong danh sách trống.]

Khóa học (xem bài giảng 5.5) cung cấp các định nghĩa trừu tượng về các chức năng này, minh họa cho sự khác biệt của chúng, mặc dù chúng rất giống nhau trong việc sử dụng phương thức khớp và đệ quy mẫu.

abstract class List[T] { ...
  def reduceLeft(op: (T,T)=>T) : T = this match{
    case Nil     => throw new Error("Nil.reduceLeft")
    case x :: xs => (xs foldLeft x)(op)
  }
  def foldLeft[U](z: U)(op: (U,T)=>U): U = this match{
    case Nil     => z
    case x :: xs => (xs foldLeft op(z, x))(op)
  }
}

Lưu ý rằng foldLefttrả về một giá trị của loại U, không nhất thiết phải cùng loại với List[T], nhưng lessLeft trả về một giá trị cùng loại với danh sách).


0

Để thực sự hiểu những gì bạn đang làm với nếp gấp / giảm, hãy kiểm tra điều này: http://wiki.tcl.tk/17983 giải thích rất tốt. một khi bạn có được khái niệm về nếp gấp, giảm sẽ đi kèm với câu trả lời ở trên: list.tail. FoldLeft (list.head) (_)

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.