【问题标题】:How to filter nested Lists and Maps in scala如何在scala中过滤嵌套列表和地图
【发布时间】:2018-09-11 03:12:49
【问题描述】:

我正在尝试读取 json 文件以计算 scala 中的一些指标。我设法阅读了该文件并执行了一些外部过滤器,但我无法理解如何过滤嵌套列表和映射。

这是一个示例代码(真实的json更长):

  val rawData = """[
  {
    "technology": "C",
    "users": [
    {
      "rating": 5,
      "completed": false,
      "user": {
        "id": 11111,
        "paid": true
      }
    },
    {
      "rating": 4,
      "completed": false,
      "user": {
        "id": 22222,
        "paid": false
      }
    }
    ],
    "title": "CS50"
  },
  {
    "technology": "C++",
    "users": [
    {
      "rating": 3,
      "completed": true,
      "user": {
        "id": 33333,
        "paid": false
      }
    },
    {
      "rating": 5,
      "completed": true,
      "user": {
        "id": 44444,
        "paid": false
      }
    }
    ],
    "title": "Introduction to C++"
  },
  {
    "technology": "Haskell",
    "users": [
    {
      "rating": 5,
      "completed": false,
      "user": {
        "id": 55555,
        "paid": false
      }
    },
    {
      "rating": null,
      "completed": true,
      "user": {
        "id": 66666,
        "paid": false
      }
    }
    ],
    "title": "Course on Haskell"
  }
  ]"""

  val data = rawData.toString.split("\n").toSeq.map(_.trim).filter(_ != "").mkString("")

我设法得到一个包含 3 个标题的列表:

import scala.util.parsing.json._
val parsedData = JSON.parseFull(data)
val listTitles = parsedData.get.asInstanceOf[List[Map[String, Any]]].map( { case e: Map[String, Any] => e("title").toString }  )

这是我的 3 个问题:

  1. 这是获取 3 个标题列表的好方法吗?
  2. 如何获取包含每个付费用户数量的列表 后三个标题?
  3. 如何获取包含拥有用户数量的列表 完成后 3 个标题的课程?

提前感谢您的帮助

【问题讨论】:

标签: json scala dictionary filter


【解决方案1】:

正如其他答案所建议的那样,您应该使用 play-json 库。它真的很强大,并且有很多功能,包括对象映射和解析以及错误处理。

  import play.api.libs.json._
  import play.api.data.validation.ValidationError

  case class User(id: String, paid: Boolean)
  object User {
    implicit val format: OFormat[User] = Json.format[User]
  }

  case class UserCourseStat(rating: Int, completed: Boolean, user: User)
  object UserCourseStat {
    implicit val format: OFormat[UserCourseStat] = Json.format[UserCourseStat]
  }

  case class Data(technology: String, title: String, users: List[UserCourseStat])
  object Data {
    implicit val format: OFormat[Data] = Json.format[Data]
  }

  val jsString = """[{"technology":"C","users":[{"rating":5,"completed":false,"user":{"id":11111,"paid":true}},{"rating":4,"completed":false,"user":{"id":22222,"paid":false}}],"title":"CS50"},{"technology":"C++","users":[{"rating":3,"completed":true,"user":{"id":33333,"paid":false}},{"rating":5,"completed":true,"user":{"id":44444,"paid":false}}],"title":"Introduction to C++"},{"technology":"Haskell","users":[{"rating":5,"completed":false,"user":{"id":55555,"paid":false}},{"rating":null,"completed":true,"user":{"id":66666,"paid":false}}],"title":"Course on Haskell"}]"""

  val rowData: JsValue = Json.parse(jsString)

  rowData.validate[List[Data]] match {
    case JsSuccess(dataList: List[Data], _) =>
      val chosenTitles = List("Course on Haskell", "Introduction to C++", "CS50")

      //map of each chosen title to sequence of it's users
      val chosenTitleToUsersMap = chosenTitles.map { title =>
        title -> dataList.filter(_.title == title)
          .flatMap(_.users.map(_.user))
          .toSet
      }.toMap
      //map of each chosen title to sequence of it's paid users
      val chosenTitleToPaidUsersMap = chosenTitleToUsersMap.map { case (title, users) =>
        title -> users.filter(_.paid)
      }

      //Calculate users who have completed each of the chosen title
      val allUsers = dataList.flatMap(_.users.map(_.user)).toSet

      val usersWhoCompletedAllChosenTitles = allUsers.filter{ user =>
        chosenTitles.forall { title =>
          chosenTitleToUsersMap.get(title).flatten.contains(user)
        }
      }

    case JsError(errors: Seq[(JsPath, Seq[ValidationError])]) =>
      //handle the error case
      ???
  }

关于您提出的 3 个问题:

  1. 这是获取 3 个标题列表的好方法吗?

我可以看到有两个不安全的操作,asInstanceOf 和 e("title"),后一个是因为没有使用 Map 的 .get(key) 方法,如果找不到 key 会抛出异常。

  1. 如何获取包含后 3 个标题的付费用户数量的列表?

在名为“chosenTitleToPaidUsersMap”的 val 中评估

  1. 如何获得一个列表,其中包含已完成后 3 个标题每个课程的用户数量?

在名为“usersWhoCompletedAllChosenTitles”的 val 中评估

【讨论】:

    【解决方案2】:

    您可以使用 play-json 库来解析和检索您想要的字段。例如:

    import play.api.libs.json.Json
    
    val rawData1 = Json.parse("""[{"technology":"C","users":[{"rating":5,"completed":false,"user":{"id":11111,"paid":true}},{"rating":4,"completed":false,"user":{"id":22222,"paid":false}}],"title":"CS50"},{"technology":"C++","users":[{"rating":3,"completed":true,"user":{"id":33333,"paid":false}},{"rating":5,"completed":true,"user":{"id":44444,"paid":false}}],"title":"Introduction to C++"},{"technology":"Haskell","users":[{"rating":5,"completed":false,"user":{"id":55555,"paid":false}},{"rating":null,"completed":true,"user":{"id":66666,"paid":false}}],"title":"Course on Haskell"}]""")
    
    val resultedList = (rawData1 \\ "title").toList.map(_.as[String])
    

    【讨论】:

      【解决方案3】:

      我建议您使用json4s 库。它允许您将数据提取到案例类中:

      import org.json4s.jackson.JsonMethods.parseOpt
      import org.json4s.DefaultFormats
      implicit val formats = DefaultFormats
      
      case class Tech(technology: String, users: Seq[TechUser], title: String)
      case class TechUser(rating: Option[Int], completed: Boolean, user: UserInfo)
      case class UserInfo(id: Int, paid: Boolean)
      
      val rawData = """..."""
      val Some(json) = parseOpt(rawData)
      val Some(data) = json.extractOpt[List[Tech]]
      

      完成此操作后,data 是一个常规的 Scala 数据结构,您可以随意操作它。例如,如果您想查找哪个标题有一个 id 可被 5 整除的用户,它是这样完成的:

      data.find(_.users.exists(_.user.id % 5 == 0)).map(_.title)
      // Result: Some("Course on Haskell")
      

      你的三个问题的答案就像这个一样,但我把它们留给你作为练习。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-08-29
        • 1970-01-01
        • 2015-07-21
        • 1970-01-01
        • 2016-02-05
        相关资源
        最近更新 更多