import random from dataclasses import dataclass from .song import Song USER_SCORE_WEIGHT = 0.7 ARTIST_WEIGHT = 0.1 TAG_WEIGHT = 0.1 RANDOM_WEIGHT = 0.1 RECENT_PENALTY = 0.5 RECENT_COUNT = 10 QUEUE_SIZE = 3 type UserScoredSong = tuple[Song, int] @dataclass class Rank: user_score: float tag: float artist: float random: float recent_penalty: float def total(self) -> float: return self.user_score + self.tag + self.artist + self.random - self.recent_penalty @dataclass class Room: id: int coord: tuple[float, float] name: str pin: int | None tags: set[str] range_size: int # in meters ?? songs: dict[str, UserScoredSong] # all songs + user score (the playlist) history: list[Song] # all songs previously played ## playing queue info playing: list[Song] playing_idx: int def renew_queue(self): for song in self.playing: self.history.append(song) self.playing.clear() self.playing_idx = 0 rankings: dict[str, float] = {} for id, (song, user_score) in self.songs.items(): rankings[id] = self.rank_song(song, user_score).total() # sort dict items by their values and pick the highest 3 top_items = sorted(rankings.items(), key=lambda item: item[1], reverse=True)[:QUEUE_SIZE] # [:3] self.playing = list(map(lambda x: self.songs[x[0]][0], top_items)) # remove the ranking and take only the songs def rank_song_from_id(self, id: str) -> Rank: scored = self.songs[id] return self.rank_song(scored[0], scored[1]) def rank_song(self, song: Song, user_score: int) -> Rank: song_items = self.songs.items() lowest_score = min(song_items, key=lambda item: item[1][1])[1][1] highest_score = max(song_items, key=lambda item: item[1][1])[1][1] score_rank = translate(user_score, lowest_score, highest_score, 0.0, USER_SCORE_WEIGHT) recent_songs = self.history[-RECENT_COUNT:] tag_counts = {} artist_counts = {} for recent_song in recent_songs: for tag in recent_song.tags: if tag not in tag_counts: tag_counts[tag] = 0 tag_counts[tag] += 1 if recent_song.artist not in artist_counts: artist_counts[recent_song.artist] = 0 artist_counts[recent_song.artist] += 1 tag_total = 0 for tag in song.tags: if tag in tag_counts: tag_total += tag_counts[tag] artist_total = artist_counts[song.artist] if song.artist in artist_counts else 0 tag_value = min(RECENT_COUNT, len(self.history)) - tag_total artist_value = min(RECENT_COUNT, len(self.history)) - artist_total tag_rank = translate(tag_value, 0, min(RECENT_COUNT, len(self.history)), 0, TAG_WEIGHT) artist_rank = translate(artist_value, 0, min(RECENT_COUNT, len(self.history)), 0, ARTIST_WEIGHT) random_rank = translate(random.random(), 0, 1, 0.0, RANDOM_WEIGHT) recent_penalty = RECENT_PENALTY if song in recent_songs else 0 return Rank(score_rank, tag_rank, artist_rank, random_rank, recent_penalty) def translate(value: float, in_min: float, in_max: float, out_min: float, out_max: float): if in_max == in_min: return out_min return (value - in_min) / (in_max - in_min) * (out_max - out_min) + out_min def test_algo(): songs = [ Song("paulham", "Io e i miei banchi", "Paul Ham", ["pop"], "", ""), Song("cisco", "CiscoPT", "Cantarex", ["rap"], "", ""), Song("vpn", "VPN", "Cantarex", ["rap"], "", ""), Song("gang", "Gang Gang Gang", "Cantarex", ["rap"], "", ""), Song("bertha1", "Rindondantha", "Berthanetti", ["rock"], "", ""), Song("bertha2", "Ragatthi", "Berthanetti", ["rock"], "", ""), Song("bertha3", "Tranthathione", "Berthanetti", ["rock"], "", ""), Song("cexx", "Spritz", "Cex", ["kpop"], "", ""), ] room = Room( 123, (0.0, 0.0), "test", None, set(["rock", "rap"]), 100, { "paulham": (songs[0], 7), "cisco": (songs[1], 5), "vpn": (songs[2], 11), "gang": (songs[3], 10), "bertha1": (songs[4], 4), "bertha2": (songs[5], 5), "bertha3": (songs[6], -4), "cexx": (songs[7], 12), }, [], [songs[2], songs[0], songs[1]], 1, ) room.renew_queue() print(room.playing)