204 lines
6.0 KiB
Scala

package TopSongs
import java.io.File
import com.github.tototoshi.csv._
trait Person {
val name: String
}
case class Singer(name: String) extends Person
case class Writer(name: String) extends Person
case class Producer(name: String) extends Person
// The parametrized type Artist can be a Singer, a Writer or a Producer
// They do a different creation action
class Artist[A <: Person](val person: A) {
def create(): String = {
person match {
case s: Singer => s"${s.name} sing"
case w: Writer => s"${w.name} write"
case p: Producer => s"${p.name} produce"
case _ => s"${person.name} create"
}
}
}
// Defined a MusicGender as en enum
enum MusicGenre:
case Rock, Soul, Blues, Folk, Funk, Reggae, HipHop, NewWave, Electro, Metal
// Define an Album with a union between a String and a MusicGenre
class Album(title: String, label: String, genre: String|MusicGenre) {
def getGenre(): String | MusicGenre = {
genre
}
}
class Streak(val s: String) {
val streak: Option[Int] = {
val regex = "\\d+".r
if(s.equalsIgnoreCase("Did not chart")) {
Some(0)
} else {
regex.findFirstIn(s) match {
case Some(value) => Some(Integer.parseInt(value))
case None => None
}
}
}
}
class Position(val p: String) {
val pos: Option[Int] = {
val regex = "\\d+".r
regex.findFirstIn(p) match {
case Some(value) => Some(Integer.parseInt(value))
case None => None
}
}
}
// Defined a type alias Rank which is a tuple of Streak and Position
// Streak and Position are linked together, it's why there are regrouped in the Rank type alias
type Rank = (Streak, Position)
// Defined an intersection type of Artist & Writer & Producer
// Defined with an alias : God
// It's a way to define a type that is a combination of other types
// If an Artist is also a Writer and a Producer, it's basically a God of the music
type God = Singer & Writer & Producer
class Song(
val title: String,
val description: Option[String],
val singer: List[Artist[Singer]],
val writer: List[Artist[Writer]],
val producer: List[Artist[Producer]],
val album: Option[Album],
val rank: Rank
)
case class TopSongs(songs: List[Song] = List()) {
def addSong(song: Song): TopSongs = {
TopSongs(song :: songs)
}
def printSongs(): Unit = {
songs.map(song => {
val title = song.title
val singer = song.singer
val producers = song.producer.map(_.create()).mkString(", ")
val streak = song.rank._1.streak.getOrElse("no")
val pos = song.rank._2.pos.getOrElse("NA")
singer match {
case s if s.exists(_.person.isInstanceOf[God]) => println(s"$title by God ${singer.map(_.person.name).mkString(", ")} spent $streak weeks on the charts on Pos. $pos")
case _ => println(
s"$title by ${singer.map(_.person.name).mkString(", ")}. " +
s"$producers this song that " +
s"spent $streak weeks " +
s"on the charts on Pos. $pos"
)
}
})
}
// This function filter the songs by a specific music gender
def filterByGenre(genre: MusicGenre): List[Song] = {
songs.filter(song => song.album.exists(_.getGenre() == genre))
}
// This function counts the number of songs by each artist
// It uses flatMap to extract the artist names from the songs
// Then it groups the songs by artist name and counts the occurrences
// Finally, it returns a map with the artist names as keys and the counts as values
def countSongsByArtist(): Map[String, Int] = {
songs.flatMap(_.singer.map(_.person.name))
.groupBy(identity)
.view.mapValues(_.size)
.toMap
}
// This function return the song spent the most weeks on the chart
// It uses reduce to find the song with the maximum streak value
def longestStreak(): Song = {
songs.reduce((s1, s2) => {
if (s1.rank._1.streak.getOrElse(0) > s2.rank._1.streak.getOrElse(0)) s1 else s2
})
}
// This function returns the top N songs by weeks on chart
// It sorts the songs by the streak value in descending order and takes the top N songs
def topNSongsByWeeks(n: Int): List[Song] = {
songs.sortBy(song => -song.rank._1.streak.getOrElse(0)).take(n)
}
}
@main def main(): Unit =
// create a new TopSongs object
var topSongs = TopSongs()
val reader = CSVReader.open(new File("src/main/resources/songs.csv"))
val allRows = reader.allWithHeaders()
for (row <- allRows) {
val title = row("title")
val description = row("description")
val singer = List(Artist[Singer](Singer(row("artist"))))
val writers = List(Artist[Writer](Writer(row("writers"))))
val producers = List(Artist[Producer](Producer(row("producer"))))
val album = Some(Album(row("appears on"), "NA", null))
val rank = (Streak(row("streak")), Position(row("position")))
val s = Song(
title = title,
description = Some(description),
singer = singer,
writer = writers,
producer = producers,
album = album,
rank = rank
)
// add the song to the TopSongs object
topSongs = topSongs.addSong(s)
}
reader.close()
val foo: Boolean = topSongs.songs.exists(s => s.rank._1.streak.contains(1))
// print the songs
topSongs.printSongs()
println("----------")
// print the number of songs by artist
val songsByArtist = topSongs.countSongsByArtist()
println("Number of songs by artist:")
songsByArtist.foreach {
case (artist, count) => println(s"$artist: $count")
}
println("----------")
// print the longest streak
val longestStreakSong = topSongs.longestStreak()
println(s"Longest streak: ${longestStreakSong.title} by ${longestStreakSong.singer.map(_.person.name).mkString(", ")} - ${longestStreakSong.rank._1.streak.getOrElse(0)} weeks")
println("----------")
// print the top N songs by weeks on chart
val topNSongs = topSongs.topNSongsByWeeks(5)
println("Top N songs by weeks on chart:")
topNSongs.foreach { song =>
println(s"${song.title} by ${song.singer.map(_.person.name).mkString(", ")} - ${song.rank._1.streak.getOrElse(0)} weeks")
}