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") }