Esempio n. 1
0
        /// <summary>
        /// Creates a new <see cref="AutoAnimeGroupCalculator"/> using relationships stored in the database.
        /// </summary>
        /// <param name="session">The NHibernate session.</param>
        /// <returns>The created <see cref="AutoAnimeGroupCalculator"/>.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="session"/> is <c>null</c>.</exception>
        public static AutoAnimeGroupCalculator CreateFromServerSettings(ISessionWrapper session)
        {
            string                     exclusionsSetting          = ServerSettings.Instance.AutoGroupSeriesRelationExclusions;
            AutoGroupExclude           exclusions                 = AutoGroupExclude.None;
            AnimeRelationType          relationsToFuzzyTitleTest  = AnimeRelationType.None;
            MainAnimeSelectionStrategy mainAnimeSelectionStrategy = ServerSettings.Instance.AutoGroupSeriesUseScoreAlgorithm
                ? MainAnimeSelectionStrategy.Weighted
                : MainAnimeSelectionStrategy.MinAirDate;

            if (!string.IsNullOrEmpty(exclusionsSetting))
            {
                var exclusionTokens = exclusionsSetting
                                      .Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)
                                      .Select(s => s.Trim())
                                      .Where(s => s.Length > 0)
                                      .ToList();

                exclusions = exclusionTokens
                             .Select(s =>
                {
                    s = s.Replace(" ", string.Empty);
                    Enum.TryParse(s, true, out AutoGroupExclude exclude);

                    return(exclude);
                })
                             .Aggregate(AutoGroupExclude.None, (exclude, allExcludes) => allExcludes | exclude);

                if (exclusionTokens.Contains("AllowDissimilarTitleExclusion", StringComparer.OrdinalIgnoreCase))
                {
                    relationsToFuzzyTitleTest = AnimeRelationType.SecondaryRelations;
                }
            }

            return(Create(session, exclusions, relationsToFuzzyTitleTest, mainAnimeSelectionStrategy));
        }
        /// <summary>
        /// Initializes a new <see cref="AutoAnimeGroupCalculator"/> instance.
        /// </summary>
        /// <param name="relationMap">A <see cref="ILookup{TKey,TElement}"/> that maps anime IDs to their relations.</param>
        /// <param name="exclusions">The relation/anime types to ignore when building relation graphs.</param>
        /// <exception cref="ArgumentNullException"><paramref name="relationMap"/> is <c>null</c>.</exception>
        public AutoAnimeGroupCalculator(ILookup <int, AnimeRelation> relationMap,
                                        AutoGroupExclude exclusions = AutoGroupExclude.SameSetting | AutoGroupExclude.Character,
                                        AnimeRelationType relationsToFuzzyTitleTest           = AnimeRelationType.SecondaryRelations,
                                        MainAnimeSelectionStrategy mainAnimeSelectionStrategy = MainAnimeSelectionStrategy.MinAirDate)
        {
            if (relationMap == null)
            {
                throw new ArgumentNullException(nameof(relationMap));
            }

            _relationMap = relationMap;
            _exclusions  = exclusions;
            _relationsToFuzzyTitleTest = relationsToFuzzyTitleTest;

            switch (mainAnimeSelectionStrategy)
            {
            case MainAnimeSelectionStrategy.MinAirDate:
                _mainAnimeSelector = FindSuitableAnimeByMinAirDate;
                break;

            case MainAnimeSelectionStrategy.Weighted:
                _mainAnimeSelector = FindSuitableAnimeByWeighting;
                break;
            }
        }
        /// <summary>
        /// Creates a new <see cref="AutoAnimeGroupCalculator"/> using relationships stored in the database.
        /// </summary>
        /// <param name="session">The NHibernate session.</param>
        /// <returns>The created <see cref="AutoAnimeGroupCalculator"/>.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="session"/> is <c>null</c>.</exception>
        public static AutoAnimeGroupCalculator Create(ISessionWrapper session)
        {
            AutoGroupExclude exclusions        = AutoGroupExclude.None;
            string           exclusionsSetting = ServerSettings.AutoGroupSeriesRelationExclusions;

            if (!String.IsNullOrEmpty(exclusionsSetting))
            {
                exclusions = exclusionsSetting.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)
                             .Select(s =>
                {
                    AutoGroupExclude exclude;

                    s = s.Replace(" ", String.Empty);
                    Enum.TryParse(s, true, out exclude);

                    return(exclude);
                })
                             .Aggregate(AutoGroupExclude.None, (exclude, allExcludes) => allExcludes | exclude);
            }

            return(Create(session, exclusions));
        }
Esempio n. 4
0
        /// <summary>
        /// Initializes a new <see cref="AutoAnimeGroupCalculator"/> instance.
        /// </summary>
        /// <param name="relationMap">A <see cref="ILookup{TKey,TElement}"/> that maps anime IDs to their relations.</param>
        /// <param name="exclusions">The relation/anime types to ignore when building relation graphs.</param>
        /// <param name="relationsToFuzzyTitleTest">The relationships for which we'll perform title similarity checks for
        /// (If the titles aren't similar enough then the anime will end up in different groups).</param>
        /// <param name="mainAnimeSelectionStrategy">The strategy to use for selecting the "main" anime that will be used
        /// for representing the group.</param>
        /// <exception cref="ArgumentNullException"><paramref name="relationMap"/> is <c>null</c>.</exception>
        public AutoAnimeGroupCalculator(Dictionary <int, List <AnimeRelation> > relationMap, AutoGroupExclude exclusions,
                                        AnimeRelationType relationsToFuzzyTitleTest, MainAnimeSelectionStrategy mainAnimeSelectionStrategy)
        {
            if (relationMap == null)
            {
                throw new ArgumentNullException(nameof(relationMap));
            }

            _relationMap = relationMap;
            _exclusions  = exclusions;
            _relationsToFuzzyTitleTest = relationsToFuzzyTitleTest;

            switch (mainAnimeSelectionStrategy)
            {
            case MainAnimeSelectionStrategy.MinAirDate:
                _mainAnimeSelector = FindSuitableAnimeByMinAirDate;
                break;

            case MainAnimeSelectionStrategy.Weighted:
                _mainAnimeSelector = FindSuitableAnimeByWeighting;
                break;
            }
        }
Esempio n. 5
0
        /// <summary>
        /// Creates a new <see cref="AutoAnimeGroupCalculator"/> using relationships stored in the database.
        /// </summary>
        /// <param name="session">The NHibernate session.</param>
        /// <param name="exclusions">The relation/anime types to ignore when building relation graphs.</param>
        /// <param name="relationsToFuzzyTitleTest">The relationships for which we'll perform title similarity checks for
        /// (If the titles aren't similar enough then the anime will end up in different groups).</param>
        /// <param name="mainAnimeSelectionStrategy">The strategy to use for selecting the "main" anime that will be used
        /// for representing the group.</param>
        /// <returns>The created <see cref="AutoAnimeGroupCalculator"/>.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="session"/> is <c>null</c>.</exception>
        public static AutoAnimeGroupCalculator Create(ISessionWrapper session,
                                                      AutoGroupExclude exclusions = AutoGroupExclude.SameSetting | AutoGroupExclude.Character,
                                                      AnimeRelationType relationsToFuzzyTitleTest           = AnimeRelationType.SecondaryRelations,
                                                      MainAnimeSelectionStrategy mainAnimeSelectionStrategy = MainAnimeSelectionStrategy.MinAirDate)
        {
            if (session == null)
            {
                throw new ArgumentNullException(nameof(session));
            }

            var relationshipMap = session.CreateSQLQuery(@"
                SELECT    fromAnime.AnimeID AS fromAnimeId
                        , toAnime.AnimeID AS toAnimeId
                        , fromAnime.AnimeType AS fromAnimeType
                        , toAnime.AnimeType AS toAnimeType
                        , fromAnime.MainTitle AS fromMainTitle
                        , toAnime.MainTitle AS toMainTitle
                        , fromAnime.AirDate AS fromAirDate
                        , toAnime.AirDate AS toAirDate
                        , rel.RelationType AS relationType
                    FROM AniDB_Anime_Relation rel
                        INNER JOIN AniDB_Anime fromAnime
                            ON fromAnime.AnimeID = rel.AnimeID
                        INNER JOIN AniDB_Anime toAnime
                            ON toAnime.AnimeID = rel.RelatedAnimeID")
                                  .AddScalar("fromAnimeId", NHibernateUtil.Int32)
                                  .AddScalar("toAnimeId", NHibernateUtil.Int32)
                                  .AddScalar("fromAnimeType", NHibernateUtil.Int32)
                                  .AddScalar("toAnimeType", NHibernateUtil.Int32)
                                  .AddScalar("fromMainTitle", NHibernateUtil.String)
                                  .AddScalar("toMainTitle", NHibernateUtil.String)
                                  .AddScalar("fromAirDate", NHibernateUtil.DateTime)
                                  .AddScalar("toAirDate", NHibernateUtil.DateTime)
                                  .AddScalar("relationType", NHibernateUtil.String)
                                  .List <object[]>()
                                  .Select(r =>
            {
                var relation = new AnimeRelation
                {
                    FromId        = (int)r[0],
                    ToId          = (int)r[1],
                    FromType      = (AnimeType)r[2],
                    ToType        = (AnimeType)r[3],
                    FromMainTitle = (string)r[4],
                    ToMainTitle   = (string)r[5],
                    FromAirDate   = (DateTime?)r[6],
                    ToAirDate     = (DateTime?)r[7]
                };

                switch (((string)r[8]).ToLowerInvariant())
                {
                case "full story":
                    relation.RelationType = AnimeRelationType.FullStory;
                    break;

                case "summary":
                    relation.RelationType = AnimeRelationType.Summary;
                    break;

                case "parent story":
                    relation.RelationType = AnimeRelationType.ParentStory;
                    break;

                case "side story":
                    relation.RelationType = AnimeRelationType.SideStory;
                    break;

                case "prequel":
                    relation.RelationType = AnimeRelationType.Prequel;
                    break;

                case "sequel":
                    relation.RelationType = AnimeRelationType.Sequel;
                    break;

                case "alternative setting":
                    relation.RelationType = AnimeRelationType.AlternateSetting;
                    break;

                case "alternative version":
                    relation.RelationType = AnimeRelationType.AlternateVersion;
                    break;

                case "same setting":
                    relation.RelationType = AnimeRelationType.SameSetting;
                    break;

                case "character":
                    relation.RelationType = AnimeRelationType.Character;
                    break;

                default:
                    relation.RelationType = AnimeRelationType.Other;
                    break;
                }

                return(relation);
            })
                                  .ToLookup(k => k.FromId);

            return(new AutoAnimeGroupCalculator(relationshipMap, exclusions, relationsToFuzzyTitleTest,
                                                mainAnimeSelectionStrategy));
        }
Esempio n. 6
0
        /// <summary>
        /// Creates a new <see cref="AutoAnimeGroupCalculator"/> using relationships stored in the database.
        /// </summary>
        /// <param name="exclusions">The relation/anime types to ignore when building relation graphs.</param>
        /// <param name="relationsToFuzzyTitleTest">The relationships for which we'll perform title similarity checks for
        /// (If the titles aren't similar enough then the anime will end up in different groups).</param>
        /// <param name="mainAnimeSelectionStrategy">The strategy to use for selecting the "main" anime that will be used
        /// for representing the group.</param>
        /// <returns>The created <see cref="AutoAnimeGroupCalculator"/>.</returns>
        public static AutoAnimeGroupCalculator Create(AutoGroupExclude exclusions = AutoGroupExclude.SameSetting | AutoGroupExclude.Character, AnimeRelationType relationsToFuzzyTitleTest = AnimeRelationType.SecondaryRelations, MainAnimeSelectionStrategy mainAnimeSelectionStrategy = MainAnimeSelectionStrategy.MinAirDate)
        {
            Dictionary <int, (int type, string title, DateTime?airdate)> animes = Repo.Instance.AniDB_Anime.GetRelationInfo();
            Dictionary <int, List <AnimeRelation> > relations = new Dictionary <int, List <AnimeRelation> >();

            foreach (AniDB_Anime_Relation rel in Repo.Instance.AniDB_Anime_Relation.GetAll())
            {
                (int type, string title, DateTime? airdate)from = animes.ContainsKey(rel.AnimeID) ? animes[rel.AnimeID] : (0, null, null);
                (int type, string title, DateTime? airdate)to   = animes.ContainsKey(rel.RelatedAnimeID) ? animes[rel.RelatedAnimeID] : (0, null, null);
                if (from.title != null && to.title != null)
                {
                    var relation = new AnimeRelation
                    {
                        FromId        = rel.AnimeID,
                        ToId          = rel.RelatedAnimeID,
                        FromType      = (AnimeType)from.type,
                        ToType        = (AnimeType)to.type,
                        FromMainTitle = from.title,
                        ToMainTitle   = to.title,
                        FromAirDate   = from.airdate,
                        ToAirDate     = to.airdate
                    };

                    switch ((rel.RelationType).ToLowerInvariant())
                    {
                    case "full story":
                        relation.RelationType = AnimeRelationType.FullStory;
                        break;

                    case "summary":
                        relation.RelationType = AnimeRelationType.Summary;
                        break;

                    case "parent story":
                        relation.RelationType = AnimeRelationType.ParentStory;
                        break;

                    case "side story":
                        relation.RelationType = AnimeRelationType.SideStory;
                        break;

                    case "prequel":
                        relation.RelationType = AnimeRelationType.Prequel;
                        break;

                    case "sequel":
                        relation.RelationType = AnimeRelationType.Sequel;
                        break;

                    case "alternative setting":
                        relation.RelationType = AnimeRelationType.AlternateSetting;
                        break;

                    case "alternative version":
                        relation.RelationType = AnimeRelationType.AlternateVersion;
                        break;

                    case "same setting":
                        relation.RelationType = AnimeRelationType.SameSetting;
                        break;

                    case "character":
                        relation.RelationType = AnimeRelationType.Character;
                        break;

                    default:
                        relation.RelationType = AnimeRelationType.Other;
                        break;
                    }
                    if (!relations.ContainsKey(rel.AnimeID))
                    {
                        relations.Add(rel.AnimeID, new List <AnimeRelation>());
                    }
                    relations[rel.AnimeID].Add(relation);
                }
            }
            return(new AutoAnimeGroupCalculator(relations, exclusions, relationsToFuzzyTitleTest, mainAnimeSelectionStrategy));
        }
Esempio n. 7
0
        public static void Main(string[] args)
        {
            using (IDbConnection con = OpenDatabaseConnection())
            {
                var relations         = GetRelations(con).ToList();
                var knownAnime        = GetAnime(con).ToDictionary(a => a.AnimeId);
                var relationsPerAnime = relations.Select(r => new { From = r.AnimeId, To = r.RelatedAnimeId })
                                        .Union(relations.Select(r => new { From = r.RelatedAnimeId, To = r.AnimeId }))
                                        .ToLookup(r => r.From, r => r.To);
                var animeGroupCalculator    = AutoAnimeGroupCalculator.CreateFromServerSettings(con);
                AutoGroupExclude relExclude = ServerSettings.GetAutoGroupSeriesRelationExclusions();

                List <HashSet <int> > subGraphs = BuildSubGraphs(relationsPerAnime);

                WriteLine("digraph Shoko_Anime_Relations {");
                WriteLine("\tnode [fontsize=11, fillcolor=powderblue, style=filled];");
                WriteLine("\tgraph [nodesep=1.5, ranksep=6, overlap=false, splines=spline, style=dashed, smoothing=power_dist];");

                int sgCount = 0;

                // Render each subgraph
                foreach (var subgraph in subGraphs)
                {
                    Write("\tsubgraph cluster_");
                    Write(sgCount);
                    WriteLine(" {");

                    foreach (int animeId in subgraph)
                    {
                        Write("\t\t\"");
                        Write(animeId);
                        Write("\" [fillcolor=");

                        if (knownAnime.TryGetValue(animeId, out var anime))
                        {
                            switch (anime.AnimeType)
                            {
                            case AnimeType.Movie:
                                Write("palegreen");
                                break;

                            case AnimeType.OVA:
                                Write("tan");
                                break;

                            case AnimeType.TVSeries:
                                Write("lightblue");
                                break;

                            case AnimeType.TVSpecial:
                                Write("cornsilk");
                                break;

                            case AnimeType.Other:
                                Write("lightpink");
                                break;

                            case AnimeType.Web:
                                Write("pink");
                                break;
                            }

                            if (animeGroupCalculator.GetGroupAnimeId(animeId) == animeId)
                            {
                                Write(", penwidth=2, shape=doubleoctagon");
                            }

                            string title = anime.MainTitle;

                            if (title.Length > MaxTitleLength)
                            {
                                title = title.Substring(0, MaxTitleLength - 3) + "...";
                            }

                            title = WordWrap(title, WordWrapLength);

                            Write(", label=<");
                            Write(anime.AnimeId);

                            if (anime.AirDate != null)
                            {
                                Write("  <i>(");
                                Write(anime.AirDate.Value.ToString("MMM yyyy"));
                                Write(")</i>");
                            }

                            Write("<br/><b>");
                            Write(HttpUtility.HtmlEncode(title).Replace("\n", "<br/>"));
                            Write("</b>>");
                        }
                        else // Write colour for missing series
                        {
                            Write("tomato, shape=box");
                        }

                        Write(", target=_blank, href=\"https://anidb.net/a");
                        Write(animeId);
                        WriteLine("\"];");
                    }

                    WriteLine("\t}");
                    sgCount++;
                }

                // Render all edges
                WriteLine();
                WriteLine("\t#Edges");

                foreach (var edge in relations)
                {
                    Write("\t\"");
                    Write(edge.AnimeId);
                    Write("\" -> \"");
                    Write(edge.RelatedAnimeId);
                    Write("\" [label=\"");
                    Write(edge.RelationType);
                    Write("\"");

                    // If the relation is meant to be exlcuded when calculating groups, then we'll colour it differently
                    if (Enum.TryParse(edge.RelationType.Replace(" ", string.Empty), true, out AutoGroupExclude relType) &&
                        (relType & relExclude) > 0)
                    {
                        Write(", color=grey, style=dashed");
                    }

                    WriteLine("];");
                }

                WriteLine("}");
            }
        }
Esempio n. 8
0
        /// <summary>
        /// Creates a new <see cref="AutoAnimeGroupCalculator"/> using relationships stored in the database.
        /// </summary>
        /// <param name="con">The open database connection.</param>
        /// <param name="exclusions">The relation/anime types to ignore when building relation graphs.</param>
        /// <param name="relationsToFuzzyTitleTest">The relationships for which we'll perform title similarity checks for
        /// (If the titles aren't similar enough then the anime will end up in different groups).</param>
        /// <param name="mainAnimeSelectionStrategy">The strategy to use for selecting the "main" anime that will be used
        /// for representing the group.</param>
        /// <returns>The created <see cref="AutoAnimeGroupCalculator"/>.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="con"/> is <c>null</c>.</exception>
        public static AutoAnimeGroupCalculator Create(IDbConnection con, AutoGroupExclude exclusions = AutoGroupExclude.SameSetting | AutoGroupExclude.Character,
                                                      AnimeRelationType relationsToFuzzyTitleTest    = AnimeRelationType.SecondaryRelations, MainAnimeSelectionStrategy mainAnimeSelectionStrategy = MainAnimeSelectionStrategy.MinAirDate)
        {
            if (con == null)
            {
                throw new ArgumentNullException(nameof(con));
            }

            using (IDbCommand cmd = con.CreateCommand())
            {
                cmd.CommandText = @"
               SELECT  fromAnime.AnimeID AS fromAnimeId
                     , toAnime.AnimeID AS toAnimeId
                     , fromAnime.AnimeType AS fromAnimeType
                     , toAnime.AnimeType AS toAnimeType
                     , fromAnime.MainTitle AS fromMainTitle
                     , toAnime.MainTitle AS toMainTitle
                     , fromAnime.AirDate AS fromAirDate
                     , toAnime.AirDate AS toAirDate
                     , rel.RelationType AS relationType
                  FROM AniDB_Anime_Relation rel
                     INNER JOIN AniDB_Anime fromAnime
                        ON fromAnime.AnimeID = rel.AnimeID
                     INNER JOIN AniDB_Anime toAnime
                        ON toAnime.AnimeID = rel.RelatedAnimeID";

                using (IDataReader reader = cmd.ExecuteReader())
                {
                    var relationshipMap = ReadAnimeRelations(reader).ToLookup(k => k.FromId);

                    return(new AutoAnimeGroupCalculator(relationshipMap, exclusions, relationsToFuzzyTitleTest,
                                                        mainAnimeSelectionStrategy));
                }
            }

            IEnumerable <AnimeRelation> ReadAnimeRelations(IDataReader reader)
            {
                while (reader.Read())
                {
                    var relation = new AnimeRelation
                    {
                        FromId        = reader.GetInt32(0),
                        ToId          = reader.GetInt32(1),
                        FromType      = (AnimeType)reader.GetInt32(2),
                        ToType        = (AnimeType)reader.GetInt32(3),
                        FromMainTitle = reader.GetString(4),
                        ToMainTitle   = reader.GetString(5),
                        FromAirDate   = reader.IsDBNull(6) ? null : (DateTime?)reader.GetDateTime(6),
                        ToAirDate     = reader.IsDBNull(7) ? null : (DateTime?)reader.GetDateTime(7)
                    };

                    switch ((reader.GetString(8)).ToLowerInvariant())
                    {
                    case "full story":
                        relation.RelationType = AnimeRelationType.FullStory;
                        break;

                    case "summary":
                        relation.RelationType = AnimeRelationType.Summary;
                        break;

                    case "parent story":
                        relation.RelationType = AnimeRelationType.ParentStory;
                        break;

                    case "side story":
                        relation.RelationType = AnimeRelationType.SideStory;
                        break;

                    case "prequel":
                        relation.RelationType = AnimeRelationType.Prequel;
                        break;

                    case "sequel":
                        relation.RelationType = AnimeRelationType.Sequel;
                        break;

                    case "alternative setting":
                        relation.RelationType = AnimeRelationType.AlternateSetting;
                        break;

                    case "alternative version":
                        relation.RelationType = AnimeRelationType.AlternateVersion;
                        break;

                    case "same setting":
                        relation.RelationType = AnimeRelationType.SameSetting;
                        break;

                    case "character":
                        relation.RelationType = AnimeRelationType.Character;
                        break;

                    default:
                        relation.RelationType = AnimeRelationType.Other;
                        break;
                    }

                    yield return(relation);
                }
            }
        }