2009-07-21 20 views
30

Giả sử tôi cózipWith (lập bản đồ trên nhiều Seq) trong Scala

val foo : Seq[Double] = ... 
val bar : Seq[Double] = ... 

và tôi muốn tạo ra một seq nơi baz (i) = foo (i) + bar (i). Một cách tôi có thể nghĩ ra để làm điều này là

val baz : Seq[Double] = (foo.toList zip bar.toList) map ((f: Double, b : Double) => f+b) 

Tuy nhiên, điều này cảm thấy cả hai xấu xí và không hiệu quả - Tôi phải chuyển đổi cả seqs vào danh sách (trong đó phát nổ với danh sách lười biếng), tạo danh sách tạm thời này của các bộ, chỉ để lập bản đồ trên nó và để cho nó được GCed. Có lẽ suối giải quyết vấn đề lười biếng, nhưng trong mọi trường hợp, điều này cảm thấy như không cần thiết xấu xí. Trong lisp, chức năng bản đồ sẽ lập bản đồ trên nhiều trình tự. Tôi sẽ viết

(mapcar (lambda (f b) (+ f b)) foo bar) 

Và không có danh sách tạm thời nào được tạo ở bất cứ đâu. Có một bản đồ-over-nhiều-danh sách chức năng trong Scala, hoặc là zip kết hợp với destructuring thực sự là 'đúng' cách để làm điều này?

Trả lời

15

Hàm bạn muốn được gọi là zipWith, nhưng nó không phải là một phần của thư viện chuẩn. Nó sẽ được trong 2.8 (UPDATE: Rõ ràng là không, xem ý kiến).

foo zipWith((f: Double, b : Double) => f+b) bar 

Xem this Trac ticket.

+2

Xin lỗi, không có zipWith trên Scala 2.8. –

+3

Chỉ cần được rõ ràng (và tôi chắc chắn Daniel sẽ đồng ý), Scala không có gì để xin lỗi cho ở đây - những gì bạn nhận được với Scala thậm chí còn tốt hơn. Xem câu trả lời của Martin dưới đây và của Daniel. Sẽ tốt hơn nếu ai đó có thể đưa ra câu trả lời được chấp thuận của Martin cho câu hỏi này ... – AmigoNico

3

Danh sách lười biếng không phải là bản sao của danh sách - nó giống như một đối tượng duy nhất. Trong trường hợp cài đặt zip lười, mỗi lần nó được yêu cầu cho mục tiếp theo, nó lấy một mục từ mỗi danh sách đầu vào và tạo ra một bộ từ chúng, và sau đó bạn chia tách bộ tách ra với mẫu phù hợp trong lambda của bạn.

Vì vậy, không bao giờ cần tạo bản sao hoàn chỉnh của toàn bộ (các) danh sách đầu vào trước khi bắt đầu hoạt động trên chúng. Nó tóm tắt một mô hình phân bổ rất giống với bất kỳ ứng dụng nào đang chạy trên JVM - rất nhiều phân bổ rất ngắn nhưng nhỏ, mà JVM được tối ưu hóa để giải quyết.

Cập nhật: để rõ ràng, bạn cần sử dụng Luồng (danh sách lười) không phải Danh sách. Luồng của Scala có mã zip hoạt động theo cách lười biếng và vì vậy bạn không nên chuyển đổi mọi thứ thành danh sách. Lý tưởng nhất là thuật toán của bạn phải có khả năng làm việc trên hai luồng vô hạn mà không bị nổ tung (giả sử nó không làm bất kỳ folding, tất nhiên, nhưng chỉ đọc và tạo luồng).

+0

Tôi biết danh sách lười biếng là gì, nhưng không quen thuộc với Scala. foo.toList không lười, phải không? Trong mọi trường hợp, đến từ một nền CL, nó cảm thấy rất lạ rằng không có một chức năng mapMultiple, vì vậy lý do của tôi để hỏi câu hỏi này chỉ là để tìm ra cách Scala chính xác để làm điều này là gì. Hiệu suất thực sự khá quan trọng; đây là trong vòng lặp bên trong của tôi, và trong khi tôi có thể cố gắng tối ưu hóa sau này, tôi muốn mã nó một cách hợp lý trước tiên. – bsdfish

+0

Tôi đang nói với bạn rằng bạn đã chính xác khi bạn nói "có thể luồng giải quyết vấn đề" - sử dụng phiên bản luồng của zip. Nếu bạn nghĩ rằng các phân bổ nhỏ đang gây áp lực lên GC, hãy viết một mệnh lệnh bắt buộc bằng ngôn ngữ dựa trên JVM mà bạn chọn và thời gian để xem nó có đúng không (tôi thường ngạc nhiên trước các máy ảo xử lý rất nhiều phân bổ ngắn hạn ngắn). –

9

Vâng, đó là thiếu zip, là sự thiếu hụt trong 2.7 của Seala. Scala 2.8 có một thiết kế bộ sưu tập tốt, để thay thế cách thức đặc biệt mà các bộ sưu tập hiện diện trong 2.7 trở thành (lưu ý rằng chúng không được tạo ra cùng một lúc, với một thiết kế thống nhất).

Bây giờ, khi bạn muốn tránh tạo bộ sưu tập tạm thời, bạn nên sử dụng "chiếu" trên Scala 2.7 hoặc "xem" trên Scala 2.8. Điều này sẽ cung cấp cho bạn một loại bộ sưu tập mà một số hướng dẫn nhất định, đặc biệt là bản đồ, flatMap và bộ lọc, không nghiêm ngặt. Trên Scala 2.7, phép chiếu của một List là một Stream. Trên Scala 2.8, có một SequenceView của một Sequence, nhưng có một zipWith ngay trong Sequence, bạn thậm chí sẽ không cần nó. Đã nói rằng, như đã đề cập, JVM được tối ưu hóa để xử lý phân bổ đối tượng tạm thời, và, khi chạy ở chế độ máy chủ, tối ưu hóa thời gian chạy có thể làm điều kỳ diệu. Vì vậy, không tối ưu hóa sớm.Kiểm tra mã trong các điều kiện nó sẽ được chạy - và nếu bạn chưa định chạy nó ở chế độ máy chủ, thì hãy suy nghĩ lại nếu mã được mong đợi là dài hạn, và chọn khi nào/ở đâu nếu cần.

EDIT

gì đang thực sự sẽ có sẵn trên Scala 2.8 là thế này:

(foo,bar).zipped.map(_+_) 
0

UPDATE: Nó đã được chỉ ra (trong ý kiến) rằng đây "câu trả lời" doesn 't thực sự giải quyết các câu hỏi được hỏi. Câu trả lời này sẽ lập bản đồ trên mỗi kết hợp của foobar, sản xuất N x M yếu tố này, thay vì phút (M, N) theo yêu cầu. Vì vậy, đây là sai, nhưng để lại cho hậu thế vì đó là thông tin tốt.


Cách tốt nhất để làm điều này là với flatMap kết hợp với map. Mã nói to hơn từ:

foo flatMap { f => bar map { b => f + b } } 

Điều này sẽ tạo ra một Seq[Double] duy nhất, chính xác như bạn mong đợi. Mô hình này là rất phổ biến mà Scala thực sự bao gồm một số ma thuật cú pháp mà thực hiện nó:

for { 
    f <- foo 
    b <- bar 
} yield f + b 

Hoặc cách khác:

for (f <- foo; b <- bar) yield f + b 

Các for { ... } cú pháp thực sự là con đường thành ngữ nhất để làm điều này. Bạn có thể tiếp tục thêm mệnh đề trình tạo (ví dụ: b <- bar) nếu cần. Do đó, nếu đột nhiên trở thành baSeq mà bạn phải lập bản đồ, bạn có thể dễ dàng mở rộng cú pháp của mình cùng với các yêu cầu của bạn (để viết một cụm từ).

+4

Tôi sẽ không bỏ phiếu cho câu hỏi này ngay bây giờ, nhưng nó hoàn toàn sai. Điều đó sẽ dẫn đến các thành phần NxN và câu hỏi được đặt ra chỉ cho kết quả trong chỉ N phần tử. Bạn đang thêm mỗi kết hợp của các yếu tố từ foo và thanh, nhưng những gì được yêu cầu là foo (i) + bar (i). –

+1

Điểm tốt. Đó là một chút vào buổi sáng sớm, vì vậy rõ ràng bộ não của tôi đã không hoạt động đúng. Tôi sẽ xóa câu trả lời này, vì nó thực sự không cung cấp những gì người khởi tạo hỏi. –

+1

Thực ra, tôi sẽ chỉ cập nhật câu trả lời. Đó là thông tin tốt, không áp dụng cho câu hỏi này. –

74

Trong Scala 2.8:

val baz = (foo, bar).zipped map (_ + _) 

Và nó hoạt động trong hơn hai toán hạng trong cùng một cách. I E. sau đó bạn có thể làm theo này với:

(foo, bar, baz).zipped map (_ * _ * _) 
+0

Nó dường như không hoạt động với nhiều hơn ba toán hạng. Đúng không? – Debilski

+14

Đúng, 'zipped' được xác định chỉ trên' Tuple2' và 'Tuple3'. Tóm tắt về tinh thần là một trong những biên giới cuối cùng trong Scala (và hầu hết các langauges kiểu tĩnh khác). HLists cung cấp một khả năng ... – retronym

+7

@retronym cũng có phương pháp '<*>'/'<$>' chúng tôi thực hiện với ZipLists trong Haskell, nơi bạn không thực sự cần phải trừu tượng hóa về tính chất do tính đồng nhất của các hàm được thu thập. Vì vậy, nếu tôi muốn zipWith một 5-tham số 'f', tôi có thể làm nhiều hơn hoặc ít hơn' f <$> xs <*> ys <*> zs <*> ps <*> qs'. Thật không may là các chức năng bị quấy rầy khó khăn hơn nhiều để giải quyết trong Scala: (có lẽ một số ý tưởng có thể được thực hiện, vì cách tiếp cận này có vẻ thanh lịch hơn so với cái gọi là "HList'. –

1

Khi phải đối mặt với một nhiệm vụ tương tự, tôi đã thêm các ma cô sau để Iterable s:

implicit class IterableOfIterablePimps[T](collOfColls: Iterable[Iterable[T]]) { 
    def mapZipped[V](f: Iterable[T] => V): Iterable[V] = new Iterable[V] { 
    override def iterator: Iterator[V] = new Iterator[V] { 
     override def next(): V = { 
     val v = f(itemsLeft.map(_.head)) 
     itemsLeft = itemsLeft.map(_.tail) 
     v 
     } 

     override def hasNext: Boolean = itemsLeft.exists(_.nonEmpty) 

     private var itemsLeft = collOfColls 
    } 
    } 
} 

Có điều này, người ta có thể làm điều gì đó như:

val collOfColls = List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9)) 
collOfColls.mapZipped { group => 
    group // List(1, 4, 7), then List(2, 5, 8), then List(3, 6, 9) 
} 

Lưu ý rằng bạn nên xem xét cẩn thận loại bộ sưu tập được chuyển thành lồng nhau Iterable, vì tailhead sẽ được gọi lại thường xuyên trên i t. Vì vậy, lý tưởng bạn nên vượt qua bộ sưu tập Iterable[List] hoặc other với nhanh tailhead.

Ngoài ra, mã này dự kiến ​​các bộ sưu tập lồng nhau có cùng kích thước. Đó là trường hợp sử dụng của tôi, nhưng tôi nghi ngờ điều này có thể được cải thiện, nếu cần thiết.

Các vấn đề liên quan