use crate::{BlissError, BlissResult, Song, NUMBER_FEATURES};
use ndarray::{Array, Array1, Array2, Axis};
use ndarray_stats::QuantileExt;
use noisy_float::prelude::*;
use std::collections::HashMap;
pub trait DistanceMetric: Fn(&Array1<f32>, &Array1<f32>) -> f32 {}
impl<F> DistanceMetric for F where F: Fn(&Array1<f32>, &Array1<f32>) -> f32 {}
pub fn euclidean_distance(a: &Array1<f32>, b: &Array1<f32>) -> f32 {
let m = Array::eye(NUMBER_FEATURES);
(a - b).dot(&m).dot(&(a - b)).sqrt()
}
pub fn cosine_distance(a: &Array1<f32>, b: &Array1<f32>) -> f32 {
let similarity = a.dot(b) / (a.dot(a).sqrt() * b.dot(b).sqrt());
1. - similarity
}
pub fn closest_to_first_song(
first_song: &Song,
#[allow(clippy::ptr_arg)] songs: &mut Vec<Song>,
distance: impl DistanceMetric,
) {
songs.sort_by_cached_key(|song| n32(first_song.custom_distance(song, &distance)));
}
pub fn closest_to_first_song_by_key<F, T>(
first_song: &T,
#[allow(clippy::ptr_arg)] songs: &mut Vec<T>,
distance: impl DistanceMetric,
key_fn: F,
) where
F: Fn(&T) -> Song,
{
let first_song = key_fn(first_song);
songs.sort_by_cached_key(|song| n32(first_song.custom_distance(&key_fn(song), &distance)));
}
pub fn song_to_song(first_song: &Song, songs: &mut Vec<Song>, distance: impl DistanceMetric) {
let mut new_songs = Vec::with_capacity(songs.len());
let mut song = first_song.to_owned();
while !songs.is_empty() {
let distances: Array1<f32> =
Array::from_shape_fn(songs.len(), |i| song.custom_distance(&songs[i], &distance));
let idx = distances.argmin().unwrap();
song = songs[idx].to_owned();
new_songs.push(song.to_owned());
songs.retain(|s| s != &song);
}
*songs = new_songs;
}
pub fn song_to_song_by_key<F, T: std::cmp::PartialEq + Clone>(
first_song: &T,
songs: &mut Vec<T>,
distance: impl DistanceMetric,
key_fn: F,
) where
F: Fn(&T) -> Song,
{
let mut new_songs: Vec<T> = Vec::with_capacity(songs.len());
let mut bliss_song = key_fn(&first_song.to_owned());
while !songs.is_empty() {
let distances: Array1<f32> = Array::from_shape_fn(songs.len(), |i| {
bliss_song.custom_distance(&key_fn(&songs[i]), &distance)
});
let idx = distances.argmin().unwrap();
let song = songs[idx].to_owned();
bliss_song = key_fn(&songs[idx]).to_owned();
new_songs.push(song.to_owned());
songs.retain(|s| s != &song);
}
*songs = new_songs;
}
pub fn dedup_playlist(songs: &mut Vec<Song>, distance_threshold: Option<f32>) {
dedup_playlist_custom_distance(songs, distance_threshold, euclidean_distance);
}
pub fn dedup_playlist_by_key<T, F>(songs: &mut Vec<T>, distance_threshold: Option<f32>, key_fn: F)
where
F: Fn(&T) -> Song,
{
dedup_playlist_custom_distance_by_key(songs, distance_threshold, euclidean_distance, key_fn);
}
pub fn dedup_playlist_custom_distance(
songs: &mut Vec<Song>,
distance_threshold: Option<f32>,
distance: impl DistanceMetric,
) {
songs.dedup_by(|s1, s2| {
n32(s1.custom_distance(s2, &distance)) < distance_threshold.unwrap_or(0.05)
|| (s1.title.is_some()
&& s2.title.is_some()
&& s1.artist.is_some()
&& s2.artist.is_some()
&& s1.title == s2.title
&& s1.artist == s2.artist)
});
}
pub fn dedup_playlist_custom_distance_by_key<F, T>(
songs: &mut Vec<T>,
distance_threshold: Option<f32>,
distance: impl DistanceMetric,
key_fn: F,
) where
F: Fn(&T) -> Song,
{
songs.dedup_by(|s1, s2| {
let s1 = key_fn(s1);
let s2 = key_fn(s2);
n32(s1.custom_distance(&s2, &distance)) < distance_threshold.unwrap_or(0.05)
|| (s1.title.is_some()
&& s2.title.is_some()
&& s1.artist.is_some()
&& s2.artist.is_some()
&& s1.title == s2.title
&& s1.artist == s2.artist)
});
}
pub fn closest_album_to_group(group: Vec<Song>, pool: Vec<Song>) -> BlissResult<Vec<Song>> {
let mut albums_analysis: HashMap<&str, Array2<f32>> = HashMap::new();
let mut albums = Vec::new();
let pool = pool
.into_iter()
.filter(|s| !group.contains(s))
.collect::<Vec<_>>();
for song in &pool {
if let Some(album) = &song.album {
if let Some(analysis) = albums_analysis.get_mut(album as &str) {
analysis
.push_row(song.analysis.as_arr1().view())
.map_err(|e| {
BlissError::ProviderError(format!("while computing distances: {}", e))
})?;
} else {
let mut array = Array::zeros((1, song.analysis.as_arr1().len()));
array.assign(&song.analysis.as_arr1());
albums_analysis.insert(album, array);
}
}
}
let mut group_analysis = Array::zeros((group.len(), NUMBER_FEATURES));
for (song, mut column) in group.iter().zip(group_analysis.axis_iter_mut(Axis(0))) {
column.assign(&song.analysis.as_arr1());
}
let first_analysis = group_analysis
.mean_axis(Axis(0))
.ok_or_else(|| BlissError::ProviderError(String::from("Mean of empty slice")))?;
for (album, analysis) in albums_analysis.iter() {
let mean_analysis = analysis
.mean_axis(Axis(0))
.ok_or_else(|| BlissError::ProviderError(String::from("Mean of empty slice")))?;
let album = album.to_owned();
albums.push((album, mean_analysis.to_owned()));
}
albums.sort_by_key(|(_, analysis)| n32(euclidean_distance(&first_analysis, analysis)));
let mut playlist = group;
for (album, _) in albums {
let mut al = pool
.iter()
.filter(|s| s.album.is_some() && s.album.as_ref().unwrap() == &album.to_string())
.map(|s| s.to_owned())
.collect::<Vec<Song>>();
al.sort_by(|s1, s2| {
let track_number1 = s1
.track_number
.to_owned()
.unwrap_or_else(|| String::from(""));
let track_number2 = s2
.track_number
.to_owned()
.unwrap_or_else(|| String::from(""));
if let Ok(x) = track_number1.parse::<i32>() {
if let Ok(y) = track_number2.parse::<i32>() {
return x.cmp(&y);
}
}
s1.track_number.cmp(&s2.track_number)
});
playlist.extend_from_slice(&al);
}
Ok(playlist)
}
pub fn closest_album_to_group_by_key<T: PartialEq + Clone, F>(
group: Vec<T>,
pool: Vec<T>,
key_fn: F,
) -> BlissResult<Vec<T>>
where
F: Fn(&T) -> Song,
{
let mut albums_analysis: HashMap<String, Array2<f32>> = HashMap::new();
let mut albums = Vec::new();
let pool = pool
.into_iter()
.filter(|s| !group.contains(s))
.collect::<Vec<_>>();
for song in &pool {
let song = key_fn(song);
if let Some(album) = song.album {
if let Some(analysis) = albums_analysis.get_mut(&album as &str) {
analysis
.push_row(song.analysis.as_arr1().view())
.map_err(|e| {
BlissError::ProviderError(format!("while computing distances: {}", e))
})?;
} else {
let mut array = Array::zeros((1, song.analysis.as_arr1().len()));
array.assign(&song.analysis.as_arr1());
albums_analysis.insert(album.to_owned(), array);
}
}
}
let mut group_analysis = Array::zeros((group.len(), NUMBER_FEATURES));
for (song, mut column) in group.iter().zip(group_analysis.axis_iter_mut(Axis(0))) {
let song = key_fn(song);
column.assign(&song.analysis.as_arr1());
}
let first_analysis = group_analysis
.mean_axis(Axis(0))
.ok_or_else(|| BlissError::ProviderError(String::from("Mean of empty slice")))?;
for (album, analysis) in albums_analysis.iter() {
let mean_analysis = analysis
.mean_axis(Axis(0))
.ok_or_else(|| BlissError::ProviderError(String::from("Mean of empty slice")))?;
let album = album.to_owned();
albums.push((album, mean_analysis.to_owned()));
}
albums.sort_by_key(|(_, analysis)| n32(euclidean_distance(&first_analysis, analysis)));
let mut playlist = group;
for (album, _) in albums {
let mut al = pool
.iter()
.filter(|s| {
let s = key_fn(s);
s.album.is_some() && s.album.as_ref().unwrap() == &album.to_string()
})
.map(|s| s.to_owned())
.collect::<Vec<T>>();
al.sort_by(|s1, s2| {
let s1 = key_fn(s1);
let s2 = key_fn(s2);
let track_number1 = s1
.track_number
.to_owned()
.unwrap_or_else(|| String::from(""));
let track_number2 = s2
.track_number
.to_owned()
.unwrap_or_else(|| String::from(""));
if let Ok(x) = track_number1.parse::<i32>() {
if let Ok(y) = track_number2.parse::<i32>() {
return x.cmp(&y);
}
}
s1.track_number.cmp(&s2.track_number)
});
playlist.extend_from_slice(&al);
}
Ok(playlist)
}
#[cfg(test)]
mod test {
use super::*;
use crate::Analysis;
use ndarray::arr1;
use std::path::Path;
#[derive(Debug, Clone, PartialEq)]
struct CustomSong {
something: bool,
bliss_song: Song,
}
#[test]
fn test_dedup_playlist_custom_distance() {
let first_song = Song {
path: Path::new("path-to-first").to_path_buf(),
analysis: Analysis::new([
1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
]),
..Default::default()
};
let first_song_dupe = Song {
path: Path::new("path-to-dupe").to_path_buf(),
analysis: Analysis::new([
1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
]),
..Default::default()
};
let second_song = Song {
path: Path::new("path-to-second").to_path_buf(),
analysis: Analysis::new([
2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 1.9, 1., 1., 1.,
]),
title: Some(String::from("dupe-title")),
artist: Some(String::from("dupe-artist")),
..Default::default()
};
let third_song = Song {
path: Path::new("path-to-third").to_path_buf(),
title: Some(String::from("dupe-title")),
artist: Some(String::from("dupe-artist")),
analysis: Analysis::new([
2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2.5, 1., 1., 1.,
]),
..Default::default()
};
let fourth_song = Song {
path: Path::new("path-to-fourth").to_path_buf(),
artist: Some(String::from("no-dupe-artist")),
title: Some(String::from("dupe-title")),
analysis: Analysis::new([
2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 0., 1., 1., 1.,
]),
..Default::default()
};
let fifth_song = Song {
path: Path::new("path-to-fourth").to_path_buf(),
analysis: Analysis::new([
2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 0.001, 1., 1., 1.,
]),
..Default::default()
};
let mut playlist = vec![
first_song.to_owned(),
first_song_dupe.to_owned(),
second_song.to_owned(),
third_song.to_owned(),
fourth_song.to_owned(),
fifth_song.to_owned(),
];
dedup_playlist_custom_distance(&mut playlist, None, euclidean_distance);
assert_eq!(
playlist,
vec![
first_song.to_owned(),
second_song.to_owned(),
fourth_song.to_owned(),
],
);
let mut playlist = vec![
first_song.to_owned(),
first_song_dupe.to_owned(),
second_song.to_owned(),
third_song.to_owned(),
fourth_song.to_owned(),
fifth_song.to_owned(),
];
dedup_playlist_custom_distance(&mut playlist, Some(20.), cosine_distance);
assert_eq!(playlist, vec![first_song.to_owned()]);
let mut playlist = vec![
first_song.to_owned(),
first_song_dupe.to_owned(),
second_song.to_owned(),
third_song.to_owned(),
fourth_song.to_owned(),
fifth_song.to_owned(),
];
dedup_playlist(&mut playlist, Some(20.));
assert_eq!(playlist, vec![first_song.to_owned()]);
let mut playlist = vec![
first_song.to_owned(),
first_song_dupe.to_owned(),
second_song.to_owned(),
third_song.to_owned(),
fourth_song.to_owned(),
fifth_song.to_owned(),
];
dedup_playlist(&mut playlist, None);
assert_eq!(
playlist,
vec![
first_song.to_owned(),
second_song.to_owned(),
fourth_song.to_owned(),
]
);
let first_song = CustomSong {
bliss_song: first_song,
something: true,
};
let second_song = CustomSong {
bliss_song: second_song,
something: true,
};
let first_song_dupe = CustomSong {
bliss_song: first_song_dupe,
something: true,
};
let third_song = CustomSong {
bliss_song: third_song,
something: true,
};
let fourth_song = CustomSong {
bliss_song: fourth_song,
something: true,
};
let fifth_song = CustomSong {
bliss_song: fifth_song,
something: true,
};
let mut playlist = vec![
first_song.to_owned(),
first_song_dupe.to_owned(),
second_song.to_owned(),
third_song.to_owned(),
fourth_song.to_owned(),
fifth_song.to_owned(),
];
dedup_playlist_custom_distance_by_key(&mut playlist, None, euclidean_distance, |s| {
s.bliss_song.to_owned()
});
assert_eq!(
playlist,
vec![
first_song.to_owned(),
second_song.to_owned(),
fourth_song.to_owned(),
],
);
let mut playlist = vec![
first_song.to_owned(),
first_song_dupe.to_owned(),
second_song.to_owned(),
third_song.to_owned(),
fourth_song.to_owned(),
fifth_song.to_owned(),
];
dedup_playlist_custom_distance_by_key(&mut playlist, Some(20.), cosine_distance, |s| {
s.bliss_song.to_owned()
});
assert_eq!(playlist, vec![first_song.to_owned()]);
let mut playlist = vec![
first_song.to_owned(),
first_song_dupe.to_owned(),
second_song.to_owned(),
third_song.to_owned(),
fourth_song.to_owned(),
fifth_song.to_owned(),
];
dedup_playlist_by_key(&mut playlist, Some(20.), |s| s.bliss_song.to_owned());
assert_eq!(playlist, vec![first_song.to_owned()]);
let mut playlist = vec![
first_song.to_owned(),
first_song_dupe.to_owned(),
second_song.to_owned(),
third_song.to_owned(),
fourth_song.to_owned(),
fifth_song.to_owned(),
];
dedup_playlist_by_key(&mut playlist, None, |s| s.bliss_song.to_owned());
assert_eq!(
playlist,
vec![
first_song.to_owned(),
second_song.to_owned(),
fourth_song.to_owned(),
]
);
}
#[test]
fn test_song_to_song() {
let first_song = Song {
path: Path::new("path-to-first").to_path_buf(),
analysis: Analysis::new([
1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
]),
..Default::default()
};
let first_song_dupe = Song {
path: Path::new("path-to-dupe").to_path_buf(),
analysis: Analysis::new([
1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
]),
..Default::default()
};
let second_song = Song {
path: Path::new("path-to-second").to_path_buf(),
analysis: Analysis::new([
2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 1.9, 1., 1., 1.,
]),
..Default::default()
};
let third_song = Song {
path: Path::new("path-to-third").to_path_buf(),
analysis: Analysis::new([
2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2.5, 1., 1., 1.,
]),
..Default::default()
};
let fourth_song = Song {
path: Path::new("path-to-fourth").to_path_buf(),
analysis: Analysis::new([
2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 0., 1., 1., 1.,
]),
..Default::default()
};
let mut songs = vec![
first_song.to_owned(),
third_song.to_owned(),
first_song_dupe.to_owned(),
second_song.to_owned(),
fourth_song.to_owned(),
];
song_to_song(&first_song, &mut songs, euclidean_distance);
assert_eq!(
songs,
vec![
first_song.to_owned(),
first_song_dupe.to_owned(),
second_song.to_owned(),
third_song.to_owned(),
fourth_song.to_owned(),
],
);
let first_song = CustomSong {
bliss_song: first_song,
something: true,
};
let second_song = CustomSong {
bliss_song: second_song,
something: true,
};
let first_song_dupe = CustomSong {
bliss_song: first_song_dupe,
something: true,
};
let third_song = CustomSong {
bliss_song: third_song,
something: true,
};
let fourth_song = CustomSong {
bliss_song: fourth_song,
something: true,
};
let mut songs: Vec<CustomSong> = vec![
first_song.to_owned(),
first_song_dupe.to_owned(),
third_song.to_owned(),
fourth_song.to_owned(),
second_song.to_owned(),
];
song_to_song_by_key(&first_song, &mut songs, euclidean_distance, |s| {
s.bliss_song.to_owned()
});
assert_eq!(
songs,
vec![
first_song,
first_song_dupe,
second_song,
third_song,
fourth_song,
],
);
}
#[test]
fn test_sort_closest_to_first_song() {
let first_song = Song {
path: Path::new("path-to-first").to_path_buf(),
analysis: Analysis::new([
1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
]),
..Default::default()
};
let first_song_dupe = Song {
path: Path::new("path-to-dupe").to_path_buf(),
analysis: Analysis::new([
1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
]),
..Default::default()
};
let second_song = Song {
path: Path::new("path-to-second").to_path_buf(),
analysis: Analysis::new([
2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 1.9, 1., 1., 1.,
]),
..Default::default()
};
let third_song = Song {
path: Path::new("path-to-third").to_path_buf(),
analysis: Analysis::new([
2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2.5, 1., 1., 1.,
]),
..Default::default()
};
let fourth_song = Song {
path: Path::new("path-to-fourth").to_path_buf(),
analysis: Analysis::new([
2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 0., 1., 1., 1.,
]),
..Default::default()
};
let fifth_song = Song {
path: Path::new("path-to-fifth").to_path_buf(),
analysis: Analysis::new([
2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 0., 1., 1., 1.,
]),
..Default::default()
};
let mut songs = vec![
first_song.to_owned(),
first_song_dupe.to_owned(),
second_song.to_owned(),
third_song.to_owned(),
fourth_song.to_owned(),
fifth_song.to_owned(),
];
closest_to_first_song(&first_song, &mut songs, euclidean_distance);
let first_song = CustomSong {
bliss_song: first_song,
something: true,
};
let second_song = CustomSong {
bliss_song: second_song,
something: true,
};
let first_song_dupe = CustomSong {
bliss_song: first_song_dupe,
something: true,
};
let third_song = CustomSong {
bliss_song: third_song,
something: true,
};
let fourth_song = CustomSong {
bliss_song: fourth_song,
something: true,
};
let fifth_song = CustomSong {
bliss_song: fifth_song,
something: true,
};
let mut songs: Vec<CustomSong> = vec![
first_song.to_owned(),
first_song_dupe.to_owned(),
second_song.to_owned(),
third_song.to_owned(),
fourth_song.to_owned(),
fifth_song.to_owned(),
];
closest_to_first_song_by_key(&first_song, &mut songs, euclidean_distance, |s| {
s.bliss_song.to_owned()
});
assert_eq!(
songs,
vec![
first_song,
first_song_dupe,
second_song,
fourth_song,
fifth_song,
third_song
],
);
}
#[test]
fn test_euclidean_distance() {
let a = arr1(&[
1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 0.,
]);
let b = arr1(&[
0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0.,
]);
assert_eq!(euclidean_distance(&a, &b), 4.242640687119285);
let a = arr1(&[0.5; 20]);
let b = arr1(&[0.5; 20]);
assert_eq!(euclidean_distance(&a, &b), 0.);
assert_eq!(euclidean_distance(&a, &b), 0.);
}
#[test]
fn test_cosine_distance() {
let a = arr1(&[
1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 0.,
]);
let b = arr1(&[
0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0.,
]);
assert_eq!(cosine_distance(&a, &b), 0.7705842661294382);
let a = arr1(&[0.5; 20]);
let b = arr1(&[0.5; 20]);
assert_eq!(cosine_distance(&a, &b), 0.);
assert_eq!(cosine_distance(&a, &b), 0.);
}
#[test]
fn test_closest_to_group() {
let first_song = Song {
path: Path::new("path-to-first").to_path_buf(),
analysis: Analysis::new([0.; 20]),
album: Some(String::from("Album")),
artist: Some(String::from("Artist")),
track_number: Some(String::from("01")),
..Default::default()
};
let second_song = Song {
path: Path::new("path-to-second").to_path_buf(),
analysis: Analysis::new([0.1; 20]),
album: Some(String::from("Another Album")),
artist: Some(String::from("Artist")),
track_number: Some(String::from("10")),
..Default::default()
};
let third_song = Song {
path: Path::new("path-to-third").to_path_buf(),
analysis: Analysis::new([10.; 20]),
album: Some(String::from("Album")),
artist: Some(String::from("Another Artist")),
track_number: Some(String::from("02")),
..Default::default()
};
let fourth_song = Song {
path: Path::new("path-to-fourth").to_path_buf(),
analysis: Analysis::new([20.; 20]),
album: Some(String::from("Another Album")),
artist: Some(String::from("Another Artist")),
track_number: Some(String::from("01")),
..Default::default()
};
let fifth_song = Song {
path: Path::new("path-to-fifth").to_path_buf(),
analysis: Analysis::new([40.; 20]),
artist: Some(String::from("Third Artist")),
album: None,
..Default::default()
};
let pool = vec![
first_song.to_owned(),
fourth_song.to_owned(),
third_song.to_owned(),
second_song.to_owned(),
fifth_song.to_owned(),
];
let group = vec![first_song.to_owned(), third_song.to_owned()];
assert_eq!(
vec![
first_song.to_owned(),
third_song.to_owned(),
fourth_song.to_owned(),
second_song.to_owned()
],
closest_album_to_group(group, pool.to_owned()).unwrap(),
);
let first_song = CustomSong {
bliss_song: first_song,
something: true,
};
let second_song = CustomSong {
bliss_song: second_song,
something: true,
};
let third_song = CustomSong {
bliss_song: third_song,
something: true,
};
let fourth_song = CustomSong {
bliss_song: fourth_song,
something: true,
};
let fifth_song = CustomSong {
bliss_song: fifth_song,
something: true,
};
let pool = vec![
first_song.to_owned(),
fourth_song.to_owned(),
third_song.to_owned(),
second_song.to_owned(),
fifth_song.to_owned(),
];
let group = vec![first_song.to_owned(), third_song.to_owned()];
assert_eq!(
vec![
first_song.to_owned(),
third_song.to_owned(),
fourth_song.to_owned(),
second_song.to_owned()
],
closest_album_to_group_by_key(group, pool.to_owned(), |s| s.bliss_song.to_owned())
.unwrap(),
);
}
}