2011-12-31 19 views
5

Tôi đã cố gắng tìm cách giữ đúng kiểu dáng chức năng trong biểu thức khi tôi cần thu thập nhiều tham số của một đối tượng vào Danh sách.Làm thế nào để giữ đúng với phong cách chức năng trong Scala cho các biểu thức

Ví dụ: giả sử tôi có đối tượng Thông báo có cả từII (id người dùng thông báo) và objectOwnerId (id của người dùng đã tạo đối tượng gốc). Đây có thể khác nhau trong thông báo kiểu facebook ("X cũng bình luận trên bài viết của Y").

tôi có thể thu thập các userIds với một biểu hiện như vậy

val userIds = for { notification <- notifications } yield notification.fromId 

tuy nhiên nói rằng tôi muốn thu thập cả fromIds và objectOwnerIds vào một danh sách duy nhất, là có cách nào để làm điều này trong một duy nhất cho biểu hiện mà không có người dùng của vars?

tôi đã thực hiện một cái gì đó như thế này trong quá khứ:

var ids = List() 
for { 
    notification <- notifications 
    ids = ids ++ List(notification.fromId, notification.objectOwnerId) 
} 
ids = ids.distinct 

nhưng nó cảm thấy như có phải là một cách tốt hơn. Việc sử dụng một var, và sự cần thiết phải gọi riêng biệt sau khi tôi hoàn thành bộ sưu tập đều xấu xí. Tôi có thể tránh sự khác biệt với một số điều kiện, nhưng tôi đang cố gắng tìm hiểu các phương pháp chức năng thích hợp để làm việc.

Cảm ơn bạn đã giúp đỡ!

+0

Tôi nghĩ câu hỏi thực là, "cách ánh xạ từ [Y (x1, x2), Y (x3, x4)] đến [x1, x2, x3, x4]? " đôi khi được gọi là "flatMap". –

+0

@pst giả định không có cách nào để có được một danh sách duy nhất mà không sử dụng var, sau đó có đó là câu hỏi. – Kareem

+0

'var'/mutation là trực giao trong trường hợp này. –

Trả lời

9

Đối với những trường hợp này, có foldLeft:

(notifications foldLeft Set.empty[Id]) { (set, notification) => 
    set ++ Seq(notification.fromId, notification.ownerId) 
} 

hoặc ở dạng ngắn:

(Set.empty[Id] /: notifications) { (set, notification) => 
    set ++ Seq(notification.fromId, notification.ownerId) 
} 

Tập hợp không giữ bản sao. Sau khi gấp, bạn có thể chuyển đổi tập hợp thành bộ sưu tập khác nếu bạn muốn.

+0

Điều này có vẻ đầy hứa hẹn. Xin lỗi vì sự thiếu hiểu biết của tôi nhưng cái gì là /:? – Kareem

+0

Đó là dạng shortLeft. Tôi đã cập nhật câu trả lời của mình để đề cập đến điều này. – sschaef

+0

Cảm ơn mọi người vì lời khuyên của bạn. Giải pháp này có vẻ là cách trực tiếp nhất để có được một danh sách không ghép nối của nhiều trường từ một Danh sách các đối tượng. – Kareem

4

Chắc chắn, thay vì chỉ có lãi suất các fromId, mang lại một tuple

val idPairs:List[(String, String)] = for(notification <- notifications) yield(notification.fromId, notification.objectOwnerId) 
+0

Vấn đề là, tôi không muốn họ ghép nối. Tôi muốn sử dụng danh sách các id riêng biệt để thực hiện truy vấn db và tạo một Ánh xạ từ id đến đối tượng User. Không có cách nào để tạo Danh sách mà không cần sử dụng một bộ tuple? – Kareem

+1

@Kareem Bạn chỉ có thể gọi idPairs.unzip() để chuyển idPairs thành hai danh sách riêng biệt. – Destin

+0

Nhưng sau đó tôi phải hợp nhất chúng sau. Chắc chắn sẽ làm việc, nhưng có vẻ như vòng xoay. Rõ ràng tôi đang chơi đùa nhưng chỉ tò mò về cách tốt nhất. – Kareem

0

Tôi đồng ý với giải pháp của Dave nhưng cách tiếp cận khác là xếp lại danh sách, tạo bản đồ id của bạn cho đối tượng Người dùng khi bạn thực hiện. Hàm để áp dụng Trong màn hình đầu tiên sẽ truy vấn db cho cả người dùng và thêm chúng vào bản đồ đang được tích lũy.

+0

Điều đó cũng sẽ hoạt động, nhưng sau đó bạn có truy vấn db cho từng người dùng riêng biệt. Có vẻ như luôn có một sự cân bằng. Bằng cách thu thập danh sách tôi có thể truy vấn db một lần và ánh xạ nó: người dùng val: Bản đồ [Id, Người dùng] = User.findAllById (ids) .map (u => u.id -> u) .toMap [Id, User] – Kareem

+0

Chức năng nếp gấp có thể kiểm tra bản đồ đã được xây dựng cho đến nay và chỉ nhận được người dùng từ db nếu họ chưa có trên bản đồ. –

+0

Đã thêm nhận xét của tôi vào một cách nhanh chóng. Có, nếu bạn muốn có một truy vấn hàng loạt duy nhất, bạn cần phải làm như bạn nói. –

1

Vâng, đây là câu trả lời của tôi như sau:

Làm thế nào để lập bản đồ từ [Y (x1, x2), Y (x3, x4)] tới [x1, x2, x3, x4]?

Sử dụng flatMap (xem Collection.Traversable, nhưng lưu ý rằng trước tiên nó thực sự được xác định cao hơn).

case class Y(a: Int, b: Int) 
var in = List(Y(1,2), Y(3,4)) 
var out = in.flatMap(x => List(x.a, x.b)) 

> defined class Y 
> in: List[Y] = List(Y(1,2), Y(3,4)) 
> out: List[Int] = List(1, 2, 3, 4) 

Ngoài ra, vì for..yield is filter, map and flatMap in one (nhưng cũng thấy "sugar for flatMap?" đó chỉ ra rằng đây không phải là hiệu quả vì nó có thể là: có thêm một map):

var out = for { i <- in; x <- Seq(i.a, i.b) } yield x 

tôi sẽ có khả năng chọn Tuy nhiên, một trong những câu trả lời khác là vì điều này không giải quyết trực tiếp vấn đề cuối cùng đang được giải quyết.

Mã hóa vui vẻ.

5
val userIds = for { 
    notification <- notifications 
    id <- List(notification.fromId, notification.objectOwnerId) 
} yield id 

Áp dụng distinct sau đó nếu được yêu cầu. Nếu id chỉ có thể được sao chép trên một thông báo, bạn có thể áp dụng distinct trên trình tạo thứ hai thay thế.

1

Bạn cũng có thể sử dụng Suối chuyển đổi các cặp vào một dòng của các cá nhân:

def toStream(xs: Iterable[Y]): Stream[Int] = { 
    xs match { 
    case Y(a, b) :: t => a #:: b #:: toStream(t) 
    case _ => Stream.empty 
    } 
} 

Nhưng giống như pst cho biết, điều này không giải quyết vấn đề cuối cùng của bạn nhận được các giá trị khác biệt, nhưng một khi bạn có dòng đó là tầm thường:

val result = toStream(ys).toList.removeDuplicates 

Hoặc một sửa đổi nhỏ để những gợi ý trước đó để sử dụng làm phẳng - thêm một chức năng có thể biến một Y vào một danh sách:

def yToList(y: Y) = List(y.a, y.b) 

Sau đó, bạn có thể làm:

val ys = List(Y(1, 2), Y(3, 4)) 
(ys map yToList flatten).removeDuplicates 
-1

gì về đơn giản map? AFAIK for yield được chuyển đổi thành chuỗi flatMapmap. Vấn đề của bạn có thể được giải quyết đơn giản như sau:

notifications.map(n => (n.fromId, n.objectOwnerId)).distinct 
Các vấn đề liên quan