简体   繁体   English

如何忠实于Scala中的函数式表达

[英]How to stay true to functional style in Scala for expressions

I've struggled to find a way to stay true to functional style in for expressions when I need to collect multiple parameters of an object into a List. 当我需要将一个对象的多个参数收集到一个列表中时,我一直在努力寻找一种方法来忠实于表达式的功能风格。

As an example, say I have a Notification object, which has both a fromId (the user id the notification is from) and an objectOwnerId (the id of the user who created the original object). 例如,假设我有一个Notification对象,它同时具有fromId(通知来自的用户ID)和objectOwnerId(创建原始对象的用户的ID)。 These can differ in facebook style notifications ("X also commented on Y's post"). 这些在Facebook样式通知中可能会有所不同(“ X也评论了Y的帖子”)。

I can collect the userIds with a for expression like so 我可以使用for表达式收集userIds,如下所示

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

however say I want to collect both the fromIds and the objectOwnerIds into a single list, is there any way to do this in a single for expression without the user of vars? 但是说我想将fromIds和objectOwnerIds都收集到一个列表中,有没有办法在没有vars用户的情况下在单个for表达式中做到这一点?

I've done something like this in the past: 我过去做过这样的事情:

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

but it feels like there must be a better way. 但感觉必须有更好的方法。 The use of a var, and the need to call distinct after I complete the collection are both ugly. 使用var以及在我完成收集后需要调用unique都是很丑的。 I could avoid the distinct with some conditionals, but I'm trying to learn the proper functional methods to do things. 我可以通过一些条件来避免出现区别,但是我正在尝试学习执行操作的正确函数方法。

Thanks in advance for any help! 在此先感谢您的帮助!

For such cases, there is foldLeft: 在这种情况下,存在foldLeft:

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

or in short form: 或简称:

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

A set doesn't hold duplicates. 一组不包含重复项。 After the fold you can convert the set to another collection if you want. 折叠后,您可以根据需要将集合转换为另一个集合。

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

Apply distinct afterwards if required. 如有需要,请在之后应用distinct If the id can only be duplicated on a single notification, you can apply distinct on the second generator instead. 如果ID只能在单个通知被复制,你可以申请distinct第二发生器来代替。

当然,不仅仅是产生fromId,而是产生一个元组

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

Well, here is my answer to the following: 好吧,这是我对以下内容的回答:

How to map from [Y(x1, x2), Y(x3, x4)] to [x1,x2,x3,x4]? 如何从[Y(x1,x2),Y(x3,x4)]映射到[x1,x2,x3,x4]?

Use flatMap (see Collection.Traversable , but note it's actually first defined higher up). 使用flatMap (请参阅Collection.Traversable ,但请注意,它实际上是在上flatMap )。

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)

Also, since for..yield is filter , map and flatMap in one (but also see "sugar for flatMap?" that points out that this isn't as efficient as it could be: there is an extra map ): 另外,因为for..yieldfiltermapflatMap的一个 (还请参见“ flatMap的糖吗?” ,它指出效率不如可能:有一个额外的map ):

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

I would likely pick one of the other answers , however, as this does not directly address the final problem being solved. 但是,我可能会选择其他答案之一 ,因为这不能直接解决要解决的最终问题。

Happy coding. 快乐的编码。

You can also use Stream to transform the pairs into a stream of individual items: 您还可以使用Stream将对转换为单个项目的流:

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

But like pst said, this doesn't solve your final problem of getting the distinct values, but once you have the stream it's trivial: 就像pst所说的那样,这并不能解决您获得唯一值的最终问题,但是一旦有了流,它就变得微不足道了:

val result = toStream(ys).toList.removeDuplicates

Or a slight modification to the earlier suggestions to use flatten - add a function that turns a Y into a List: 或对先前使用flatten的建议进行了一些修改-添加一个将Y转换为List的函数:

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

Then you can do: 然后,您可以执行以下操作:

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

I agree with Dave's solution but another approach is to fold over the list, producing your map of id to User object as you go. 我同意Dave的解决方案,但另一种方法是将其折叠在列表上,并随即生成ID到User对象的映射。 The function to apply In the fold will query the db for both users and add them to the map being accumulated. 折叠中要应用的功能将查询两个用户的数据库,并将它们添加到要累积的地图中。

What about simple map ? 简单map呢? AFAIK for yield gets converted to series of flatMap and map anyway. 无论如何, for yield AFAIK都会转换为一系列flatMapmap Your problem could be solved simply as follows: 您的问题可以简单地通过以下方式解决:

notifications.map(n => (n.fromId, n.objectOwnerId)).distinct

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM