Fix speed of "song to song" sorting method
This commit is contained in:
parent
833d8b020b
commit
dd997510d3
6 changed files with 43 additions and 23 deletions
2
.github/workflows/rust.yml
vendored
2
.github/workflows/rust.yml
vendored
|
@ -23,7 +23,7 @@ jobs:
|
||||||
toolchain: nightly-2021-04-01
|
toolchain: nightly-2021-04-01
|
||||||
override: false
|
override: false
|
||||||
- name: Packages
|
- name: Packages
|
||||||
run: sudo apt-get install build-essential yasm libavutil-dev libavcodec-dev libavformat-dev libavfilter-dev libavfilter-dev libavdevice-dev libswresample-dev libfftw3-dev ffmpeg
|
run: sudo apt-get update && sudo apt-get install build-essential yasm libavutil-dev libavcodec-dev libavformat-dev libavfilter-dev libavfilter-dev libavdevice-dev libswresample-dev libfftw3-dev ffmpeg
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cargo build --verbose
|
run: cargo build --verbose
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## bliss 0.4.0
|
||||||
|
* Make the song-to-song custom sorting method faster.
|
||||||
|
* Rename `to_vec` and `to_arr1` to `as_vec` and `as_arr1` .
|
||||||
|
* Add a playlist_dedup function.
|
||||||
|
|
||||||
## bliss 0.3.5
|
## bliss 0.3.5
|
||||||
* Add custom sorting methods for playlist-making.
|
* Add custom sorting methods for playlist-making.
|
||||||
|
|
||||||
|
|
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -882,9 +882,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.8.3"
|
version = "0.8.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e"
|
checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"rand_chacha",
|
"rand_chacha",
|
||||||
|
|
|
@ -12,6 +12,7 @@ use crate::Library;
|
||||||
use crate::Song;
|
use crate::Song;
|
||||||
use crate::NUMBER_FEATURES;
|
use crate::NUMBER_FEATURES;
|
||||||
use ndarray::{Array, Array1};
|
use ndarray::{Array, Array1};
|
||||||
|
use ndarray_stats::QuantileExt;
|
||||||
use noisy_float::prelude::*;
|
use noisy_float::prelude::*;
|
||||||
|
|
||||||
/// Convenience trait for user-defined distance metrics.
|
/// Convenience trait for user-defined distance metrics.
|
||||||
|
@ -39,34 +40,35 @@ pub fn cosine_distance(a: &Array1<f32>, b: &Array1<f32>) -> f32 {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sort `songs` in place by putting songs close to `first_song` first
|
/// Sort `songs` in place by putting songs close to `first_song` first
|
||||||
/// using the `distance` metric. Deduplicate identical songs.
|
/// using the `distance` metric.
|
||||||
pub fn closest_to_first_song(
|
pub fn closest_to_first_song(
|
||||||
first_song: &Song,
|
first_song: &Song,
|
||||||
songs: &mut Vec<Song>,
|
songs: &mut Vec<Song>,
|
||||||
distance: impl DistanceMetric,
|
distance: impl DistanceMetric,
|
||||||
) {
|
) {
|
||||||
songs.sort_by_cached_key(|song| n32(first_song.custom_distance(song, &distance)));
|
songs.sort_by_cached_key(|song| n32(first_song.custom_distance(song, &distance)));
|
||||||
songs.dedup_by_key(|song| n32(first_song.custom_distance(song, &distance)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sort `songs` in place using the `distance` metric and ordering by
|
/// Sort `songs` in place using the `distance` metric and ordering by
|
||||||
/// the smallest distance between each song. Deduplicate identical songs.
|
/// the smallest distance between each song.
|
||||||
///
|
///
|
||||||
/// If the generated playlist is `[song1, song2, song3, song4]`, it means
|
/// If the generated playlist is `[song1, song2, song3, song4]`, it means
|
||||||
/// song2 is closest to song1, song3 is closest to song2, and song4 is closest
|
/// song2 is closest to song1, song3 is closest to song2, and song4 is closest
|
||||||
/// to song3.
|
/// to song3.
|
||||||
|
///
|
||||||
|
/// Note that this has a tendency to go from one style to the other very fast,
|
||||||
|
/// and it can be slow on big libraries.
|
||||||
pub fn song_to_song(first_song: &Song, songs: &mut Vec<Song>, distance: impl DistanceMetric) {
|
pub fn song_to_song(first_song: &Song, songs: &mut Vec<Song>, distance: impl DistanceMetric) {
|
||||||
let mut new_songs = vec![first_song.to_owned()];
|
let mut new_songs = Vec::with_capacity(songs.len());
|
||||||
let mut song = first_song.to_owned();
|
let mut song = first_song.to_owned();
|
||||||
loop {
|
|
||||||
if songs.is_empty() {
|
while !songs.is_empty() {
|
||||||
break;
|
let distances: Array1<f32> =
|
||||||
}
|
Array::from_shape_fn(songs.len(), |i| song.custom_distance(&songs[i], &distance));
|
||||||
songs
|
let idx = distances.argmin().unwrap();
|
||||||
.retain(|s| n32(song.custom_distance(s, &distance)) != 0.);
|
song = songs[idx].to_owned();
|
||||||
songs.sort_by_key(|s| n32(song.custom_distance(s, &distance)));
|
|
||||||
song = songs.remove(0);
|
|
||||||
new_songs.push(song.to_owned());
|
new_songs.push(song.to_owned());
|
||||||
|
songs.retain(|s| s != &song);
|
||||||
}
|
}
|
||||||
*songs = new_songs;
|
*songs = new_songs;
|
||||||
}
|
}
|
||||||
|
@ -126,7 +128,13 @@ mod test {
|
||||||
song_to_song(&first_song, &mut songs, euclidean_distance);
|
song_to_song(&first_song, &mut songs, euclidean_distance);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
songs,
|
songs,
|
||||||
vec![first_song, second_song, third_song, fourth_song],
|
vec![
|
||||||
|
first_song,
|
||||||
|
first_song_dupe.to_owned(),
|
||||||
|
second_song,
|
||||||
|
third_song,
|
||||||
|
fourth_song
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,7 +195,14 @@ mod test {
|
||||||
closest_to_first_song(&first_song, &mut songs, euclidean_distance);
|
closest_to_first_song(&first_song, &mut songs, euclidean_distance);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
songs,
|
songs,
|
||||||
vec![first_song, second_song, fourth_song, third_song],
|
vec![
|
||||||
|
first_song,
|
||||||
|
first_song_dupe,
|
||||||
|
second_song,
|
||||||
|
fourth_song,
|
||||||
|
fifth_song,
|
||||||
|
third_song
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
//! MPD](https://github.com/Polochon-street/blissify-rs) could also be useful.
|
//! MPD](https://github.com/Polochon-street/blissify-rs) could also be useful.
|
||||||
#[cfg(doc)]
|
#[cfg(doc)]
|
||||||
use crate::distance;
|
use crate::distance;
|
||||||
use crate::distance::{closest_to_first_song, DistanceMetric, euclidean_distance};
|
use crate::distance::{closest_to_first_song, euclidean_distance, DistanceMetric};
|
||||||
use crate::{BlissError, BlissResult, Song};
|
use crate::{BlissError, BlissResult, Song};
|
||||||
use log::{debug, error, info};
|
use log::{debug, error, info};
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
|
|
10
src/song.rs
10
src/song.rs
|
@ -142,7 +142,7 @@ impl fmt::Debug for Analysis {
|
||||||
debug_struct.field(&format!("{:?}", feature), &self[feature]);
|
debug_struct.field(&format!("{:?}", feature), &self[feature]);
|
||||||
}
|
}
|
||||||
debug_struct.finish()?;
|
debug_struct.finish()?;
|
||||||
f.write_str(&format!(" /* {:?} */", &self.to_vec()))
|
f.write_str(&format!(" /* {:?} */", &self.as_vec()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,7 +161,7 @@ impl Analysis {
|
||||||
/// Return an ndarray `Array1` representing the analysis' features.
|
/// Return an ndarray `Array1` representing the analysis' features.
|
||||||
///
|
///
|
||||||
/// Particularly useful if you want to make a custom distance metric.
|
/// Particularly useful if you want to make a custom distance metric.
|
||||||
pub fn to_arr1(&self) -> Array1<f32> {
|
pub fn as_arr1(&self) -> Array1<f32> {
|
||||||
arr1(&self.internal_analysis)
|
arr1(&self.internal_analysis)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,7 +169,7 @@ impl Analysis {
|
||||||
///
|
///
|
||||||
/// Particularly useful if you want iterate through the values to store
|
/// Particularly useful if you want iterate through the values to store
|
||||||
/// them somewhere.
|
/// them somewhere.
|
||||||
pub fn to_vec(&self) -> Vec<f32> {
|
pub fn as_vec(&self) -> Vec<f32> {
|
||||||
self.internal_analysis.to_vec()
|
self.internal_analysis.to_vec()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,7 +187,7 @@ impl Analysis {
|
||||||
/// Note that almost all distance metrics you will find obey these
|
/// Note that almost all distance metrics you will find obey these
|
||||||
/// properties, so don't sweat it too much.
|
/// properties, so don't sweat it too much.
|
||||||
pub fn custom_distance(&self, other: &Self, distance: impl DistanceMetric) -> f32 {
|
pub fn custom_distance(&self, other: &Self, distance: impl DistanceMetric) -> f32 {
|
||||||
distance(&self.to_arr1(), &other.to_arr1())
|
distance(&self.as_arr1(), &other.as_arr1())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -654,7 +654,7 @@ mod tests {
|
||||||
-0.9820945,
|
-0.9820945,
|
||||||
-0.95968974,
|
-0.95968974,
|
||||||
];
|
];
|
||||||
for (x, y) in song.analysis.to_vec().iter().zip(expected_analysis) {
|
for (x, y) in song.analysis.as_vec().iter().zip(expected_analysis) {
|
||||||
assert!(0.01 > (x - y).abs());
|
assert!(0.01 > (x - y).abs());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue