/// <summary>Calculate the minimum number of blocks between two character ids in given collection</summary> public MinimumProximity CalculateMinimumProximity(ISet <string> characterIdsToCalculate) { if (!characterIdsToCalculate.Any()) { return(new MinimumProximity(Int32.MaxValue, null, null, null, null)); } RelatedCharactersData relChar = RelatedCharactersData.Singleton; bool foundFirst = false; int currentBlockCount = 0; int minProximity = Int32.MaxValue; string prevCharacterId = null; ISet <string> prevMatchingCharacterIds = null; BookScript firstBook = null; Block firstBlock = null; BookScript secondBook = null; Block secondBlock = null; BookScript prevBook = null; Block prevBlock = null; bool breakOutOfBothLoops = false; bool calculateAnyRelatedCharacters = characterIdsToCalculate.Any(c => relChar.HasMatchingCharacterIdsOfADifferentAge(c)); foreach (var book in m_booksToConsider) { if (breakOutOfBothLoops) { break; } var treatAsSameCharacter = m_considerSameExtrabiblicalCharacter[book]; currentBlockCount += kDefaultMinimumProximity + 20; // 20 is a pretty arbitrary "magic number" foreach (var block in book.Blocks) { var characterId = block.CharacterIdInScript; // The original logic here was NOT split out for the single character vs. multiple character scenarios. // This made the code much more readable, but the performance was atrocious since we were creating // extra hashsets and doing extra intersects. Please consider the performance implications of any // changes to this code. (I'm sure it could be optimized further, too...) ISet <string> matchingCharacterIds = null; if (calculateAnyRelatedCharacters && relChar.TryGetMatchingCharacterIdsOfADifferentAge(characterId, out matchingCharacterIds)) { if (matchingCharacterIds.Count == 1) { matchingCharacterIds = null; } } else { foreach (var kvp in treatAsSameCharacter) { if (kvp.Value == null) { continue; } if (kvp.Value.Contains((characterId))) { matchingCharacterIds = kvp.Value; break; } } } if (matchingCharacterIds == null) { if ((prevMatchingCharacterIds == null && prevCharacterId == characterId) || (prevMatchingCharacterIds != null && prevMatchingCharacterIds.Contains(characterId))) { currentBlockCount = 0; prevBook = book; prevBlock = block; } else if (characterIdsToCalculate.Contains(characterId) && (!CharacterVerseData.IsCharacterOfType(characterId, CharacterVerseData.StandardCharacter.Narrator) || prevCharacterId == null || !CharacterVerseData.IsCharacterOfType(prevCharacterId, CharacterVerseData.StandardCharacter.Narrator))) { if (ProcessDifferentCharacter(book, block, characterId, matchingCharacterIds, ref foundFirst, ref currentBlockCount, ref minProximity, ref firstBook, ref prevBook, ref firstBlock, ref prevBlock, ref secondBook, ref secondBlock, ref breakOutOfBothLoops, ref prevCharacterId, ref prevMatchingCharacterIds)) { break; } } else { currentBlockCount++; } } else if (prevMatchingCharacterIds != null && matchingCharacterIds.Intersect(prevMatchingCharacterIds).Any()) { currentBlockCount = 0; prevBook = book; prevBlock = block; } else if (characterIdsToCalculate.Intersect(matchingCharacterIds).Any()) { if (ProcessDifferentCharacter(book, block, characterId, matchingCharacterIds, ref foundFirst, ref currentBlockCount, ref minProximity, ref firstBook, ref prevBook, ref firstBlock, ref prevBlock, ref secondBook, ref secondBlock, ref breakOutOfBothLoops, ref prevCharacterId, ref prevMatchingCharacterIds)) { break; } } else { currentBlockCount++; } } } return(new MinimumProximity(minProximity, firstBook, secondBook, firstBlock, secondBlock)); }
/// <summary>Calculate the minimum number of blocks between two character ids in given collection</summary> public MinimumProximity CalculateMinimumProximity(ISet <string> characterIdsToCalculate) { if (!characterIdsToCalculate.Any()) { return(new MinimumProximity(Int32.MaxValue, null, null, null, null)); } RelatedCharactersData relChar = RelatedCharactersData.Singleton; bool foundFirst = false; int currentBlockCount = 0; int minBlockProximity = Int32.MaxValue; string prevCharacterId = null; ISet <string> prevMatchingCharacterIds = null; BookScript firstBook = null; Block firstBlock = null; BookScript secondBook = null; Block secondBlock = null; BookScript prevBook = null; Block prevBlock = null; bool breakOutOfBothLoops = false; bool calculateAnyRelatedCharacters = characterIdsToCalculate.Any(c => relChar.HasMatchingCharacterIdsOfADifferentAge(c)); foreach (var book in m_booksToConsider) { if (breakOutOfBothLoops) { break; } var countVersesRatherThanBlocks = !m_referenceText.HasContentForBook(book.BookId); var treatAsSameCharacter = m_considerSameExtrabiblicalCharacter[book]; if (m_narrationByAuthor) { var author = BiblicalAuthors.GetAuthorOfBook(book.BookId); if (author.CombineAuthorAndNarrator && !treatAsSameCharacter.Any(set => set.Contains(author.Name))) { if (!m_strictlyAdhereToNarratorPreferences || (characterIdsToCalculate.Contains(book.NarratorCharacterId) && characterIdsToCalculate.Contains(author.Name))) { HashSet <string> charactersToTreatAsOneWithNarrator = treatAsSameCharacter.FirstOrDefault(set => set.Contains(book.NarratorCharacterId)); if (charactersToTreatAsOneWithNarrator == null) { charactersToTreatAsOneWithNarrator = new HashSet <string>(); treatAsSameCharacter.Add(charactersToTreatAsOneWithNarrator); charactersToTreatAsOneWithNarrator.Add(book.NarratorCharacterId); charactersToTreatAsOneWithNarrator.Add(author.Name); } else { charactersToTreatAsOneWithNarrator.Add(author.Name); } } } } // We don't want to treat book ends as being directly adjacent but not infinitely distant, either. currentBlockCount += kDefaultMinimumBlocks * 5 / 3; // The amount of padding is somewhat arbitrary. foreach (var block in book.Blocks) { var characterId = block.CharacterIdInScript; // The original logic here was NOT split out for the single character vs. multiple character scenarios. // This made the code much more readable, but the performance was atrocious since we were creating // extra hashsets and doing extra intersects. Please consider the performance implications of any // changes to this code. (I'm sure it could be optimized further, too...) ISet <string> matchingCharacterIds = null; if (calculateAnyRelatedCharacters && relChar.TryGetMatchingCharacterIdsOfADifferentAge(characterId, out matchingCharacterIds)) { if (matchingCharacterIds.Count == 1) { matchingCharacterIds = null; } } else { foreach (var set in treatAsSameCharacter) { if (set.Contains(characterId)) { matchingCharacterIds = set; break; } } } if (matchingCharacterIds == null) { if ((prevMatchingCharacterIds == null && prevCharacterId == characterId) || (prevMatchingCharacterIds != null && prevMatchingCharacterIds.Contains(characterId))) { currentBlockCount = 0; prevBook = book; prevBlock = block; } else if (characterIdsToCalculate.Contains(characterId) && (!CharacterVerseData.IsCharacterOfType(characterId, CharacterVerseData.StandardCharacter.Narrator) || prevCharacterId == null || !CharacterVerseData.IsCharacterOfType(prevCharacterId, CharacterVerseData.StandardCharacter.Narrator))) { if (ProcessDifferentCharacter(book, block, characterId, matchingCharacterIds, ref foundFirst, ref currentBlockCount, ref minBlockProximity, ref firstBook, ref prevBook, ref firstBlock, ref prevBlock, ref secondBook, ref secondBlock, ref breakOutOfBothLoops, ref prevCharacterId, ref prevMatchingCharacterIds)) { break; } } else { IncrementCount(countVersesRatherThanBlocks, block, ref currentBlockCount); } } else if (prevMatchingCharacterIds != null && matchingCharacterIds.Intersect(prevMatchingCharacterIds).Any()) { currentBlockCount = 0; prevBook = book; prevBlock = block; prevMatchingCharacterIds = matchingCharacterIds; } else if (characterIdsToCalculate.Intersect(matchingCharacterIds).Any()) { if (ProcessDifferentCharacter(book, block, characterId, matchingCharacterIds, ref foundFirst, ref currentBlockCount, ref minBlockProximity, ref firstBook, ref prevBook, ref firstBlock, ref prevBlock, ref secondBook, ref secondBlock, ref breakOutOfBothLoops, ref prevCharacterId, ref prevMatchingCharacterIds)) { break; } } else { IncrementCount(countVersesRatherThanBlocks, block, ref currentBlockCount); } } } return(new MinimumProximity(minBlockProximity, firstBook, secondBook, firstBlock, secondBlock)); }
/// <summary> /// Calculate the minimum number of blocks between two character ids in given collection /// </summary> public MinimumProximity CalculateMinimumProximity(ISet <string> characterIdsToCalculate, bool handleEachBookSeparately = true) { RelatedCharactersData relChar = RelatedCharactersData.Singleton; bool foundFirst = false; int currentBlockCount = 0; int minProximity = Int32.MaxValue; ISet <string> prevMatchingCharacterIds = new HashSet <string>(); BookScript firstBook = null; Block firstBlock = null; BookScript secondBook = null; Block secondBlock = null; BookScript prevBook = null; Block prevBlock = null; bool breakOutOfBothLoops = false; ISet <string> characterIdsWithAgeVariations = relChar.GetCharacterIdsForType(CharacterRelationshipType.SameCharacterWithMultipleAges); foreach (var book in m_project.IncludedBooks) { if (breakOutOfBothLoops) { break; } if (handleEachBookSeparately) { currentBlockCount += 50; } foreach (var block in book.Blocks) { var characterId = block.CharacterIdInScript; ISet <string> matchingCharacterIds; if (characterIdsWithAgeVariations.Contains(characterId)) { matchingCharacterIds = relChar.GetMatchingCharacterIds(characterId, CharacterRelationshipType.SameCharacterWithMultipleAges); } else { matchingCharacterIds = new HashSet <string> { characterId } }; if (matchingCharacterIds.Intersect(prevMatchingCharacterIds).Any()) { currentBlockCount = 0; prevBook = book; prevBlock = block; } else if (characterIdsToCalculate.Intersect(matchingCharacterIds).Any()) { if (foundFirst) { if (currentBlockCount < minProximity) { minProximity = currentBlockCount; firstBook = prevBook; firstBlock = prevBlock; secondBook = book; secondBlock = block; if (minProximity == 0) { breakOutOfBothLoops = true; break; } } } else { firstBook = book; firstBlock = block; secondBook = book; secondBlock = block; } foundFirst = true; currentBlockCount = 0; prevMatchingCharacterIds = matchingCharacterIds; prevBook = book; prevBlock = block; } else { currentBlockCount++; } } } return(new MinimumProximity { NumberOfBlocks = minProximity, FirstBook = firstBook, SecondBook = secondBook, FirstBlock = firstBlock, SecondBlock = secondBlock }); } }