【问题标题】:How to stay true to functional style in Scala for expressions如何在 Scala 中保持对表达式的函数式风格
【发布时间】:2011-12-31 19:20:01
【问题描述】:

当我需要将对象的多个参数收集到列表中时,我一直在努力寻找一种方法来保持表达式中的函数式风格。

例如,假设我有一个 Notification 对象,它同时具有 fromId(通知来自的用户 ID)和 objectOwnerId(创建原始对象的用户的 ID)。这些在 facebook 样式通知中可能有所不同(“X 还评论了 Y 的帖子”)。

我可以用这样的 for 表达式收集用户 ID

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

但是说我想将 fromIds 和 objectOwnerIds 收集到一个列表中,有没有办法在没有 vars 用户的情况下在单个 for 表达式中执行此操作?

我过去做过这样的事情:

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

但感觉必须有更好的方法。 var 的使用,以及在我完成集合后需要调用 distinct 都很难看。我可以通过一些条件来避免不同,但我正在尝试学习正确的函数方法来做事。

提前感谢您的帮助!

【问题讨论】:

  • 我认为真正的问题是,“如何从 [Y(x1, x2), Y(x3, x4)] 映射到 [x1,x2,x3,x4]?有时称为“平面地图”。
  • @pst 假设不使用 var 就无法获得单个列表,那么是的,这就是问题所在。
  • var/mutation 在这种情况下是正交的。
  • @pst 你能澄清一下这是什么意思吗?我对 scala 很陌生。
  • 有一种方法可以在没有var/mutation 的情况下获得这样的列表。 flatMap 操作是如何字面上进行请求的转换。基本问题是相同的,无论初始假设如何(如果没有var/mutations 就无法执行操作),这就是为什么它(问题)与此类假设正交.. 尽管这可能是一个不好用的术语在这里。

标签: list scala functional-programming for-loop


【解决方案1】:

对于这种情况,有 foldLeft:

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

或简称:

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

集合不包含重复项。折叠后,您可以根据需要将集合转换为另一个集合。

【讨论】:

  • 这看起来很有希望。请原谅我的无知,但 /: 是什么?
  • 这是 foldLeft 的缩写形式。我更新了我的答案以提及这一点。
  • 感谢大家的建议。此解决方案似乎是从对象列表中获取多个字段的非配对列表的最直接方法。
【解决方案2】:
val userIds = for { 
  notification <- notifications 
  id <- List(notification.fromId, notification.objectOwnerId)
} yield id

如果需要,之后申请distinct。如果 id 只能在单个通知上重复,您可以在第二个生成器上应用 distinct

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

    • 问题是,我不希望它们配对。我想使用不同 id 的列表来执行 db 查询并创建一个从 id 到 User 对象的 Map。不使用元组就没有办法创建列表吗?
    • @Kareem 您可以调用 idPairs.unzip() 将 idPairs 变成两个单独的列表。
    • 但是我必须在之后合并它们。肯定会工作,但似乎迂回。显然我在吹毛求疵,但只是对最好的方法感到好奇。
    【解决方案4】:

    好吧,这是我对以下问题的回答:

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

    使用flatMap(请参阅Collection.Traversable,但请注意它实际上是首先在更高层定义的)。

    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)
    

    另外,因为for..yield is filter, map and flatMap in one(但也请参阅"sugar for flatMap?" 指出这没有尽可能高效:还有一个额外的map):

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

    但是,我可能会选择其他答案之一,因为这并不能直接解决最终要解决的问题。

    编码愉快。

    【讨论】:

      【解决方案5】:

      您还可以使用 Stream 将这些对转换为单个项目的流:

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

      但就像 pst 所说,这并不能解决您获取不同值的最终问题,但是一旦您有了流,它就变得微不足道了:

      val result = toStream(ys).toList.removeDuplicates
      

      或者对之前使用 flatten 的建议稍作修改 - 添加一个将 Y 转换为 List 的函数:

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

      那么你可以这样做:

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

      【讨论】:

        【解决方案6】:

        我同意 Dave 的解决方案,但另一种方法是折叠列表,生成您的 id 映射到 User 对象。在折叠中应用的功能将查询两个用户的数据库并将它们添加到正在累积的地图中。

        【讨论】:

        • 这也可以,但是您对每个不同的用户都有一个数据库查询。似乎总是有取舍。通过收集列表,我可以查询数据库一次并映射它: val users :Map[Id, User] = User.findAllById(ids).map(u => u.id -> u).toMap[Id, User]
        • fold 函数可以测试目前已经构建好的地图,只有在地图中没有用户的情况下才能从数据库中获取用户。
        • 快速添加我的评论。是的,如果你想要一个批量查询,你需要按照你说的去做。
        【解决方案7】:

        简单的map呢? AFAIK for yield 无论如何都会转换为flatMapmap 的系列。您的问题可以简单地解决如下:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2011-03-20
          • 1970-01-01
          • 1970-01-01
          • 2022-11-30
          • 2016-10-25
          • 1970-01-01
          • 2012-01-30
          相关资源
          最近更新 更多