Add artist members and multiple writers

main
mat ess 2023-04-18 00:46:55 -04:00
parent cc949e07f3
commit db3641200c
3 changed files with 158 additions and 54 deletions

View File

@ -1,21 +1,47 @@
{
"Taylor Swift": [
"artists": [
{
"title": "Love Story",
"composer": "Taylor Swift",
"performer": "Taylor Swift"
}
],
"black midi": [
{
"title": "Love Story",
"composer": "Taylor Swift",
"performer": "black midi"
"name": "Taylor Swift",
"members": [
"Taylor Swift"
],
"catalog": [
{
"title": "Love Story",
"writers": [
"Taylor Swift"
],
"performer": "Taylor Swift"
}
]
},
{
"title": "Love Story",
"composer": "black midi",
"performer": "black midi"
"name": "black midi",
"members": [
"Cameron Picton",
"Matt Kelvin",
"Morgan Simpson",
"Geordie Greep"
],
"catalog": [
{
"title": "Love Story",
"writers": [
"Taylor Swift"
],
"performer": "black midi"
},
{
"title": "Love Story",
"writers": [
"Cameron Picton",
"Matt Kelvin",
"Morgan Simpson",
"Geordie Greep"
],
"performer": "black midi"
}
]
}
]
}

View File

@ -1,32 +1,16 @@
use nanoserde::DeJson;
use std::{
collections::{HashMap, HashSet},
hash::Hash,
collections::HashSet,
hash::{Hash, Hasher},
};
#[derive(Debug, Clone)]
#[derive(Debug, Clone, DeJson)]
pub struct Library {
artists: HashSet<Artist>,
}
impl Library {
pub fn new(json: HashMap<String, Vec<Song>>) -> Self {
let artists = json
.into_iter()
.map(|(name, catalog)| Artist {
name,
catalog: catalog.into_iter().collect(),
})
.collect();
Library { artists }
}
pub fn find_covers(&self) -> HashSet<&Song> {
let names = self
.artists
.iter()
.map(|artist| artist.name.clone())
.collect::<HashSet<_>>();
self.artists
.iter()
.fold(HashSet::new(), |mut covers, artist| {
@ -34,19 +18,20 @@ impl Library {
covers
})
.into_iter()
.filter(|song| names.contains(&song.composer))
.filter(|song| song.is_library_cover(self))
.collect()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, DeJson)]
struct Artist {
name: String,
members: HashSet<String>,
catalog: HashSet<Song>,
}
impl Hash for Artist {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
fn hash<H: Hasher>(&self, state: &mut H) {
self.name.hash(state);
}
}
@ -55,27 +40,61 @@ impl Artist {
fn covered_songs(&self) -> HashSet<&Song> {
self.catalog
.iter()
.filter(|song| song.composer != self.name)
.filter(|song| song.is_covered_by(self))
.collect()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, DeJson)]
#[derive(Debug, Clone, PartialEq, Eq, DeJson)]
pub struct Song {
pub title: String,
pub performer: String,
pub composer: String,
pub writers: HashSet<String>,
}
impl Hash for Song {
fn hash<H: Hasher>(&self, state: &mut H) {
self.title.hash(state);
self.performer.hash(state);
}
}
impl Song {
fn is_library_cover(&self, library: &Library) -> bool {
library
.artists
.iter()
.any(|artist| self.is_cover_of(artist))
}
fn is_covered_by(&self, artist: &Artist) -> bool {
self.performer == artist.name && self.writers.is_disjoint(&artist.members)
}
fn is_cover_of(&self, artist: &Artist) -> bool {
self.performer != artist.name && !self.writers.is_disjoint(&artist.members)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn bm_members() -> HashSet<String> {
[
"Cameron Picton".to_string(),
"Matt Kelvin".to_string(),
"Morgan Simpson".to_string(),
"Geordie Greep".to_string(),
]
.into()
}
fn bmbmbm() -> Song {
Song {
title: "bmbmbm".to_string(),
performer: "black midi".to_string(),
composer: "black midi".to_string(),
writers: bm_members(),
}
}
@ -83,7 +102,7 @@ mod tests {
Song {
title: "Love Story".to_string(),
performer: performer.to_string(),
composer: "Taylor Swift".to_string(),
writers: ["Taylor Swift".to_string()].into(),
}
}
@ -91,28 +110,75 @@ mod tests {
Song {
title: "21st Century Schizoid Man".to_string(),
performer: "black midi".to_string(),
composer: "Greg Lake, Ian McDonald, Michael Giles, Peter Sinfield & Robert Fripp"
.to_string(),
writers: [
"Greg Lake".to_string(),
"Ian McDonald".to_string(),
"Michael Giles".to_string(),
"Peter Sinfield".to_string(),
"Robert Fripp".to_string(),
]
.into(),
}
}
fn exile() -> Song {
Song {
title: "Exile".to_string(),
performer: "Taylor Swift".to_string(),
writers: [
"Taylor Swift".to_string(),
"William Bowery".to_string(),
"Justin Vernon".to_string(),
]
.into(),
}
}
fn bm() -> Artist {
Artist {
name: "black midi".to_string(),
catalog: HashSet::from([bmbmbm(), love_story("black midi"), schizoid_man()]),
members: bm_members(),
catalog: [bmbmbm(), love_story("black midi"), schizoid_man()].into(),
}
}
fn ts() -> Artist {
Artist {
name: "Taylor Swift".to_string(),
catalog: HashSet::from([love_story("Taylor Swift")]),
members: ["Taylor Swift".to_string()].into(),
catalog: [love_story("Taylor Swift"), exile()].into(),
}
}
fn bon_iver() -> Artist {
Artist {
name: "Bon Iver".to_string(),
members: [
"Justin Vernon".to_string(),
"Sean Carey".to_string(),
"Michael Noyce".to_string(),
"Matthew McCaughan".to_string(),
]
.into(),
catalog: [
Song {
title: "Skinny Love".to_string(),
performer: "Bon Iver".to_string(),
writers: ["Justin Vernon".to_string()].into(),
},
Song {
title: "Holocene".to_string(),
performer: "Bon Iver".to_string(),
writers: ["Justin Vernon".to_string()].into(),
},
]
.into(),
}
}
fn library() -> Library {
Library {
artists: HashSet::from([bm(), ts()]),
artists: HashSet::from([bm(), ts(), bon_iver()]),
}
}
@ -121,8 +187,22 @@ mod tests {
assert!(bm().covered_songs() == HashSet::from([&love_story("black midi"), &schizoid_man()]))
}
#[test]
fn test_covered_songs_no_covers() {
assert!(bon_iver().covered_songs().is_empty())
}
#[test]
fn test_find_covers() {
assert!(library().find_covers() == HashSet::from([&love_story("black midi")]))
}
#[test]
fn test_find_covers_one_artist() {
assert!(Library {
artists: [bon_iver()].into()
}
.find_covers()
.is_empty())
}
}

View File

@ -1,22 +1,20 @@
use nanoserde::DeJson;
use std::{collections::HashMap, env, fs};
use std::{env, fs};
mod library;
use crate::library::{Library, Song};
fn main() {
let file_path = env::args()
.nth(1)
.expect("Usage: cover path/to/library.json");
let data = fs::read_to_string(file_path).expect("Failed to open library file");
let json: HashMap<String, Vec<Song>> =
DeJson::deserialize_json(&data).expect("Failed to deserialize library");
let library = Library::new(json);
let json = fs::read_to_string(file_path).expect("Failed to open library file");
let library: library::Library =
DeJson::deserialize_json(&json).expect("Failed to deserialize library file");
for cover in library.find_covers() {
println!("Found cover!");
println!(
"{}, by {}, covered by {}",
cover.title, cover.composer, cover.performer
"{}, by {:?}, covered by {}",
cover.title, cover.writers, cover.performer
)
}
}