我是 Scala 的新手。我很好奇如何對串列的子項進行分組。
例如,我有關于小說類別及其評分的資料。我想獲得某個類別的總評分。
資料格式: (Option[List(categories)], Option[rating])
var a = List(List(List("romance", "thriller"), 4), List(List("adventure", "thriller"), 3))
我想獲得 Key(Category) => Value(Their ratings) 的映射
romance => (4,3)
thriller => (4,3)
adventure => (3)
我試圖這樣做a.groupby(_._1),但它只在它們具有完全相同的類別時才分組。我試圖搜索其他帖子,但找不到任何類似的問題。
uj5u.com熱心網友回復:
這種資料結構極難使用,這可能是導致您出現問題的原因。你有一個串列,它又包含串列,每個串列又包含兩個元素,第一個是另一個串列,第二個元素是一個數字。
這有很多很多問題。首先,幾乎沒有一個串列實際上是串列。
最里面的串列不是串列,因為元素的順序無關緊要。應該是一套吧。
中間的串列有很大的問題:因為它同時包含一個串列和一個數字,所以它不能有一個有用的型別。我們能做的最好的事情是List[Any]在 Scala 2 或List[String | Integer]Scala 3 中。在 Scala 3 中推斷的實際型別是List[Matchable],這基本上是無用的。它會“污染”你以后嘗試做的一切。它可能應該是一對,即 a (Set[String], Integer)。
并且外部串列實際上也不需要是串列,它可以是任何序列;我們并不特別關心它是什么特定型別的序列。我們關心的只是我們可以迭代它。事實上,我們不關心順序,我們甚至不關心順序(并行評估所有元素就可以了),所以可以說,正確的型別應該是Iterable.
因此,讓我們首先修復該資料結構:
val ratings = Iterable(
Set("romance", "thriller") -> 4,
Set("adventure", "thriller") -> 3
)
這種資料結構更容易使用,而且更接近于你試圖建模的語意。
為了獲得流派的個人評分,我們需要將這種表示展平,因此我們得到類似
Iterable(
"romance" -> 4,
"thriller" -> 4,
"adventure" -> 3,
"thriller" -> 3
)
我們可以通過flatMappingratings和mapping 每個流派到一對(流派,評級)來做到這一點:
val individualRatings = for
(set, rating) <- ratings
genre <- set
yield genre -> rating
現在,我們可以groupBy使用流派,它會給我們一些類似的東西
Map(
"romance" -> ("romance" -> Seq(4)),
"thriller" -> ("thriller" -> Seq(4, 3)),
"adventure" -> ("adventure" -> Seq(3))
)
這意味著我們只需要mapValues他們的第二個元素,所以我們最終得到這樣的結果:
Map(
"romance" -> Seq(4),
"thriller" -> Seq(4, 3),
"adventure" -> Seq(3)
)
最后,我們需要將reduce評級串列轉換為單個評級。
實際上有一個有用的方法Iterable.groupMapReduce可以有效地結合這三個操作:
val totals = individualRatings.groupMapReduce(_._1)(_._2)(_ _)
最終結果是這樣的:
Map(
"adventure" -> 3,
"romance" -> 4,
"thriller" -> 7
)
Note: even if you have no control over the data coming in, it still makes sense to "fix" the data as soon as it enters the system, and only ever work with the fixed data, instead of constantly having to deal with the broken data all the time.
You could, for example, create the data structure I outlined above from your data using something like this:
val ratings = for
(genres: Iterable[String]) :: (rating: Int) :: Nil <- a
yield genres.toSet -> rating
Note that because of type erasure, this is actually not type-safe: at runtime, the element type of the List is erased, so it is impossible to know that the first element is a List[String] and the second element is an Integer. They are both treated as Matchables, even with the type test in the pattern.
However, this is not necessarily the best we can do. Scala is an object-oriented programming language, after all, not a list-of-lists-of-lists-of-strings-or-numbers-oriented language.
What we have done, is we have improved the data structure from a list-of-lists-of-lists-of-strings-or-numbers to an iterable-of-pairs-of-sets-of-strings-and-numbers. What we would really like to have is an iterable-of-ratings.
I will leave that as an exercise for the reader for now.
uj5u.com熱心網友回復:
您首先要將資料轉換為List[(Category, Rating)],然后您可以使用groupMap按類別分組。
val a = List(
(List("romance", "thriller"), 4),
(List("adventure", "thriller"), 3)
)
a.flatMap{
case (cs, r) => cs.map(_ -> r)
}
.groupMap(_._1)(_._2)
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/344785.html
標籤:斯卡拉
