Browse Source

song list

master
alistair 3 years ago
parent
commit
cc65cf502d
  1. 165
      telegram_bot.cpp

165
telegram_bot.cpp

@ -11,9 +11,11 @@ @@ -11,9 +11,11 @@
#include <exception>
#include <istream>
#include <sstream>
#include <stdexcept>
#include <string>
#include <nlohmann/json.hpp>
#include <memory>
#include <cmath>
#include "Base64.hpp"
#include <tuple>
@ -325,7 +327,7 @@ class songdb { @@ -325,7 +327,7 @@ class songdb {
* As is, song lists are unique to telegram groups
*/
int64_t get_song_list_id(int64_t group_id) {
spdlog::debug("get_song_list_id {}", group_id);
spdlog::debug("{} {}", __PRETTY_FUNCTION__, __LINE__);
std::string list_query = "SELECT * FROM songlists WHERE groupid = ?;";
/* A constraint of this implementation is that every group can only have
@ -413,6 +415,7 @@ class songdb { @@ -413,6 +415,7 @@ class songdb {
sqlite3_finalize(statement);
return e;
}
track_entry e {id, name, artist};
sqlite3_finalize(statement);
return e;
@ -614,7 +617,7 @@ class songdb { @@ -614,7 +617,7 @@ class songdb {
struct base_weight_vector {
std::vector<int64_t> person_order;
std::vector<size_t> song_order;
std::vector<int64_t> song_order;
std::vector<std::vector<double>> weights;
};
@ -622,6 +625,7 @@ class songdb { @@ -622,6 +625,7 @@ class songdb {
base_weight_vector
get_base_weights (int64_t song_list)
{
spdlog::debug("{} {}", __PRETTY_FUNCTION__, __LINE__);
std::vector<vote> list = get_votes_list(song_list);
std::set<int64_t> chat_members;
@ -687,6 +691,139 @@ class songdb { @@ -687,6 +691,139 @@ class songdb {
}
double
dot_product(const std::vector<double> &a, const std::vector<double> &b)
{
assert(a.size() == b.size());
double dot = 0;
for (int i = 0; i < a.size(); i++) {
dot += a.at(i) * b.at(i);
}
double dot2 = sqrt(dot);
return dot2;
}
double
weight_badness_inner_product(const std::vector<double> &current_badness, const std::vector<double> &song_goodness)
{
return dot_product(current_badness,song_goodness);
}
std::vector<double>
update_badness(std::vector<double> old_badness, std::vector<double> song_goodness)
{
auto new_badness = old_badness;
for (int i = 0; i < old_badness.size(); i++) {
new_badness[i] = new_badness[i] - song_goodness[i];
}
return new_badness;
}
/**
* The returned base weight vector has the songs in the sorted order,
* the weights field is the badness vector used for the next song in the
* list.
*
* /param num: the number songs to return
*/
base_weight_vector
get_top_songs(base_weight_vector input, std::vector<double> starting_badness, int num)
{
spdlog::debug("{} {}", __PRETTY_FUNCTION__, __LINE__);
if (num > input.song_order.size()) {
num = input.song_order.size();
}
base_weight_vector result {};
auto current_badness = starting_badness;
result.person_order = input.person_order;
struct score {
int64_t song;
double score;
std::vector<double> base_weight;
};
// create scores vector
std::vector<score> scores {};
for (int i = 0; i < input.song_order.size(); i++) {
scores.push_back({input.song_order.at(i),
0, input.weights.at(i)});
}
for (int i = 0; i < num; i++) {
// Compute scores based on badness
for (int j = 0; j < scores.size(); j++) {
scores[j].score = weight_badness_inner_product(current_badness, scores[j].base_weight);
}
// sort scores
std::sort(scores.rbegin(), scores.rend(),
[](const score &a, const score &b)
{
return a.score > b.score;
}
);
// chose the song with the best score
auto chosen = scores.at(scores.size() - 1);
result.song_order.push_back(chosen.song);
// update badness vector
current_badness = update_badness(current_badness, chosen.base_weight);
result.weights.push_back(current_badness);
// run algorithm again on the subset not containing the chosen
// score, with the updated badness vector
scores.pop_back();
}
return result;
}
std::string get_top_5_songs(int64_t telegram_group) {
spdlog::debug("{} {}", __PRETTY_FUNCTION__, __LINE__);
int64_t song_list = get_song_list_id(telegram_group);
auto songs = get_base_weights(song_list);
if (songs.weights.size() == 0) {
return {};
}
std::vector<double> starting_badness {};
for (int i = 0; i < songs.person_order.size(); i++) {
starting_badness.push_back(1);
}
auto chosen = get_top_songs(songs, starting_badness, 5);
std::string slist = "Top 5 Songs:\n\n";
for (int i = 0; i < chosen.song_order.size(); i++) {
int64_t songid = chosen.song_order.at(i);
auto song = get_song(songid);
if (!song) {
throw std::runtime_error("Invalid state achieved.");
}
slist += song->name;
slist += " ";
slist += song->artist;
slist += "\n";
}
return slist;
}
std::vector<track_entry>
generate_track_list(int64_t song_list)
{
@ -696,7 +833,7 @@ class songdb { @@ -696,7 +833,7 @@ class songdb {
spdlog::info("song {} nppl {}", base_weights.song_order[i], base_weights.weights.size());
}
std::vector<track_entry> retlist;
std::vector<track_entry> retlist {};
return retlist;
}
@ -758,6 +895,14 @@ int main() { @@ -758,6 +895,14 @@ int main() {
s = new kare::spotify(spotid, spotsecret);
signal(SIGINT, [](int s) {
spdlog::info("Shutting down...");
exit(0);
});
Bot bot(teletoken);
@ -884,12 +1029,24 @@ int main() { @@ -884,12 +1029,24 @@ int main() {
bot.getEvents().onCommand("start", [&bot, &data](Message::Ptr message) {
data.generate_track_list(1);
bot.getApi().sendMessage(message->chat->id, "Hi!");
});
bot.getEvents().onCommand("list", [&bot, &data](Message::Ptr message) {
try {
std::string response = data.get_top_5_songs(message->chat->id);
bot.getApi().sendMessage(message->chat->id, response);
} catch (std::exception const &e) {
spdlog::error("exp: {}", e.what());
spdlog::dump_backtrace();
}
});
std::string * a = new std::string("hello world");

Loading…
Cancel
Save