Как отфильтровать вложенные списки и карты в Scala - PullRequest
0 голосов
/ 10 сентября 2018

Я пытаюсь прочитать файл 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 названий?

Заранее спасибо за помощь

Ответы [ 3 ]

0 голосов
/ 10 сентября 2018

Как и предполагал другой ответ, вы должны использовать библиотеку 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 названий?

Я вижу там две небезопасные операции, такие как InstanceOf и e ("title"), последняя из-за того, что метод Map не используется .get (key), он выдаст исключение, если ключ не найден.

  1. Как получить Список, содержащий количество платных пользователей для каждого из трех последних названий?

Оценивается выше в val с именем "selectedTitleToPaidUsersMap"

  1. Как получить список, содержащий количество пользователей, прошедших курс по каждому из трех последних названий?

Оценивается выше в val с именем "usersWhoCompletedAllChosenTitles"

0 голосов
/ 10 сентября 2018

Я предлагаю вам использовать библиотеку 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, которой вы можете управлять по своему усмотрению. Например, если вы хотите найти, для какого заголовка существует пользователь, чей идентификатор делится на 5, это делается так:

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

Ответы на ваши три вопроса такие же, как и на одной строке, но я оставляю их вам в качестве упражнения.

0 голосов
/ 10 сентября 2018

Вы можете использовать библиотеку 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])
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...