/// <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 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)); }
public RelationEdge(AnimeRelationType relationType, int fromAnimeId, int toAnimeId) { // Normalize the relationship so that the directions are consistent (e.g. Prequel/FullStory/ParentStory will // always be on the FROM size, and Sequel/Summary/SideStory will always be on the TO side). // Doing this means that when added to a HashSet, etc. we'll filter out duplicate/reversed relations switch (relationType) { case AnimeRelationType.Prequel: AnimeId1 = fromAnimeId; RelationType1 = AnimeRelationType.Prequel; AnimeId2 = toAnimeId; RelationType2 = AnimeRelationType.Sequel; break; case AnimeRelationType.Sequel: AnimeId1 = toAnimeId; RelationType1 = AnimeRelationType.Prequel; AnimeId2 = fromAnimeId; RelationType2 = AnimeRelationType.Sequel; break; case AnimeRelationType.FullStory: AnimeId1 = fromAnimeId; RelationType1 = AnimeRelationType.FullStory; AnimeId2 = toAnimeId; RelationType2 = AnimeRelationType.Summary; break; case AnimeRelationType.Summary: AnimeId1 = toAnimeId; RelationType1 = AnimeRelationType.FullStory; AnimeId2 = fromAnimeId; RelationType2 = AnimeRelationType.Summary; break; case AnimeRelationType.ParentStory: AnimeId1 = fromAnimeId; RelationType1 = AnimeRelationType.ParentStory; AnimeId2 = toAnimeId; RelationType2 = AnimeRelationType.SideStory; break; case AnimeRelationType.SideStory: AnimeId1 = toAnimeId; RelationType1 = AnimeRelationType.ParentStory; AnimeId2 = fromAnimeId; RelationType2 = AnimeRelationType.SideStory; break; default: AnimeId1 = Math.Min(fromAnimeId, toAnimeId); RelationType1 = relationType; AnimeId2 = Math.Max(fromAnimeId, toAnimeId); RelationType2 = relationType; break; } }
/// <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; } }
/// <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)); }
/// <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)); }
/// <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); } } }