Exemplo n.º 1
0
        /// <summary>
        /// Builds a dictionary containing Song ID keys and weight values.
        /// The weight values are determined by song popularity, song like, artist like, album like, and tag like.
        /// </summary>
        /// <param name="songsWithRanking">Songs grouped by community rank standing.</param>
        /// <returns></returns>
        /// <remarks>
        /// Based off of the song weights algorithm described here:
        /// http://stackoverflow.com/questions/3345788/algorithm-for-picking-thumbed-up-items/3345838#3345838
        ///
        /// The song weights algorithm is loosely based on the more general Multiplicative Weight Update Algorithm (MWUA), described here:
        /// https://jeremykun.com/2017/02/27/the-reasonable-effectiveness-of-the-multiplicative-weights-update-algorithm/
        /// </remarks>
        internal Dictionary <string, SongWeight> BuildSongWeightsTable(IList <Songs_RankStandings.Result> songsWithRanking)
        {
            // Generate a table containing all the songs. The table will be a dictionary of SongID keys and weight values.
            // Each song takes up N weight, where N starts out as 1, but can grow or shrink depending on whether that song/artist/album/tags is liked or disliked.
            var totalSongCount = songsWithRanking.Sum(s => s.SongIds.Count);
            var songWeights    = new Dictionary <string, SongWeight>(totalSongCount);

            foreach (var ranking in songsWithRanking)
            {
                var rankingMultipler = GetWeightMultiplier(ranking.Standing);
                var songIdsAndDates  = ranking.SongIds.Zip(ranking.SongUploadDates);
                foreach (var(songId, date) in songIdsAndDates)
                {
                    // Give it a weight based on its community rank.
                    // Multiply that weight by the song's age (newer songs are played more often.)
                    var ageMultiplier    = GetAgeMultiplier(date);
                    var rankAndAgeWeight = SongWeight.Default()
                                           .WithCommunityRankMultiplier(rankingMultipler)
                                           .WithAgeMultiplier(ageMultiplier);

                    songWeights[songId] = rankAndAgeWeight;
                }
            }

            // Now we've generated the table with all songs, each weighted according to their community ranking.
            // Next, adjust the weight based on whether we like this song or not.
            foreach (var likedSong in Songs)
            {
                if (songWeights.TryGetValue(likedSong.SongId, out var existingWeight))
                {
                    var songMultiplier = GetSongLikeDislikeMultiplier(likedSong);
                    songWeights[likedSong.SongId] = existingWeight.WithSongMultiplier(songMultiplier);
                }
            }

            // Next, adjust the weight based on whether we like this artist or not.
            var artistPrefs = Artists.GroupBy(a => a.Name);

            foreach (var artist in artistPrefs)
            {
                var artistMultiplier = GetArtistMultiplier(artist);
                if (artistMultiplier != 1.0)
                {
                    foreach (var pref in artist)
                    {
                        if (songWeights.TryGetValue(pref.SongId, out var existingWeight))
                        {
                            songWeights[pref.SongId] = existingWeight.WithArtistMultiplier(artistMultiplier);
                        }
                    }
                }
            }

            // Next, adjust the weight based on whether we like this album or not.
            var albumPrefs = Albums.GroupBy(a => a.Name);

            foreach (var album in albumPrefs)
            {
                var albumMultiplier = GetAlbumMultiplier(album);
                if (albumMultiplier != 1.0)
                {
                    foreach (var pref in album)
                    {
                        if (songWeights.TryGetValue(pref.SongId, out var existingWeight))
                        {
                            songWeights[pref.SongId] = existingWeight.WithAlbumMultiplier(albumMultiplier);
                        }
                    }
                }
            }

            // Adjust the weight based on whether we like the tags of the song or not.
            var tagLikeDislikeDifferences = CreateTagLikeDislikeDifferences(Tags);
            var songTags = Tags.GroupBy(t => t.SongId);
            var songsWithTagMultipliers = songTags.Select(tag => (SongId: tag.Key, TagsMultiplier: GetCumulativeTagMultiplier(tag, tagLikeDislikeDifferences)));

            foreach (var(SongId, TagsMultiplier) in songsWithTagMultipliers)
            {
                if (songWeights.TryGetValue(SongId, out var existingWeight))
                {
                    songWeights[SongId] = existingWeight.WithTagMultiplier(TagsMultiplier);
                }
            }

            return(songWeights);
        }