diff --git a/Sequences/SequencesOptions.cs b/Sequences/SequencesOptions.cs new file mode 100644 index 000000000..7e72c2501 --- /dev/null +++ b/Sequences/SequencesOptions.cs @@ -0,0 +1,28 @@ +using System; +using System.Runtime.CompilerServices; +using LinkIndex = System.UInt64; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Platform.Data.Doublets.Sequences +{ + /// + /// Basic options for Sequences - minimal implementation for refactoring purposes. + /// + public class SequencesOptions + { + public bool UseSequenceMarker { get; set; } = false; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ValidateOptions() + { + // Basic validation - placeholder + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void InitOptions(object links) + { + // Basic initialization - placeholder + } + } +} \ No newline at end of file diff --git a/csharp/Platform.Data.Doublets/Sequences/ISequenceMatcher.cs b/csharp/Platform.Data.Doublets/Sequences/ISequenceMatcher.cs new file mode 100644 index 000000000..b2d072cc5 --- /dev/null +++ b/csharp/Platform.Data.Doublets/Sequences/ISequenceMatcher.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using LinkIndex = System.UInt64; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Platform.Data.Doublets.Sequences +{ + /// + /// Interface for sequence matching operations. + /// + public interface ISequenceMatcher + { + bool Match(LinkIndex sequenceToMatch); + void AddMatchedToResults(IList restrictions); + LinkIndex HandleMatched(IList restrictions); + } +} \ No newline at end of file diff --git a/csharp/Platform.Data.Doublets/Sequences/Sequences.cs b/csharp/Platform.Data.Doublets/Sequences/Sequences.cs new file mode 100644 index 000000000..8588daf64 --- /dev/null +++ b/csharp/Platform.Data.Doublets/Sequences/Sequences.cs @@ -0,0 +1,633 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using Platform.Collections; +using Platform.Collections.Lists; +using Platform.Collections.Stacks; +using Platform.Threading.Synchronization; +using LinkIndex = System.UInt64; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Platform.Data.Doublets.Sequences +{ + /// + /// Представляет коллекцию последовательностей связей. + /// + /// + /// Обязательно реализовать атомарность каждого публичного метода. + /// + /// TODO: + /// + /// !!! Повышение вероятности повторного использования групп (подпоследовательностей), + /// через естественную группировку по unicode типам, все whitespace вместе, все символы вместе, все числа вместе и т.п. + /// + использовать ровно сбалансированный вариант, чтобы уменьшать вложенность (глубину графа) + /// + /// x*y - найти все связи между, в последовательностях любой формы, если не стоит ограничитель на то, что является последовательностью, а что нет, + /// то находятся любые структуры связей, которые содержат эти элементы именно в таком порядке. + /// + /// Рост последовательности слева и справа. + /// Поиск со звёздочкой. + /// URL, PURL - реестр используемых во вне ссылок на ресурсы, + /// так же проблема может быть решена при реализации дистанционных триггеров. + /// Нужны ли уникальные указатели вообще? + /// Что если обращение к информации будет происходить через содержимое всегда? + /// + /// Писать тесты. + /// + /// + /// Можно убрать зависимость от конкретной реализации Links, + /// на зависимость от абстрактного элемента, который может быть представлен несколькими способами. + /// + /// Можно ли как-то сделать один общий интерфейс + /// + /// + /// Блокчейн и/или гит для распределённой записи транзакций. + /// + /// + public partial class Sequences : ILinks // IList, IList (после завершения реализации Sequences) + { + /// Возвращает значение LinkIndex, обозначающее любое количество связей. + public const LinkIndex ZeroOrMany = LinkIndex.MaxValue; + + public SequencesOptions Options { get; } + public SynchronizedLinks Links { get; } + private readonly ISynchronization _sync; + + public LinksConstants Constants { get; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Sequences(SynchronizedLinks links, SequencesOptions options) + { + Links = links; + _sync = links.SyncRoot; + Options = options; + Options.ValidateOptions(); + Options.InitOptions(Links); + Constants = links.Constants; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Sequences(SynchronizedLinks links) : this(links, new SequencesOptions()) { } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsSequence(LinkIndex sequence) + { + return _sync.ExecuteReadOperation(() => + { + if (Options.UseSequenceMarker) + { + return Options.MarkedSequenceMatcher.IsMatched(sequence); + } + return !Links.Unsync.IsPartialPoint(sequence); + }); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private LinkIndex GetSequenceByElements(LinkIndex sequence) + { + if (Options.UseSequenceMarker) + { + return Links.SearchOrDefault(Options.SequenceMarkerLink, sequence); + } + return sequence; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private LinkIndex GetSequenceElements(LinkIndex sequence) + { + if (Options.UseSequenceMarker) + { + var linkContents = new Link(Links.GetLink(sequence)); + if (linkContents.Source == Options.SequenceMarkerLink) + { + return linkContents.Target; + } + if (linkContents.Target == Options.SequenceMarkerLink) + { + return linkContents.Source; + } + } + return sequence; + } + + #region Count + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public LinkIndex Count(IList restrictions) + { + if (restrictions.IsNullOrEmpty()) + { + return Links.Count(Constants.Any, Options.SequenceMarkerLink, Constants.Any); + } + if (restrictions.Count == 1) // Первая связь это адрес + { + var sequenceIndex = restrictions[0]; + if (sequenceIndex == Constants.Null) + { + return 0; + } + if (sequenceIndex == Constants.Any) + { + return Count(null); + } + if (Options.UseSequenceMarker) + { + return Links.Count(Constants.Any, Options.SequenceMarkerLink, sequenceIndex); + } + return Links.Exists(sequenceIndex) ? 1UL : 0; + } + throw new NotImplementedException(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private LinkIndex CountUsages(params LinkIndex[] restrictions) + { + if (restrictions.Length == 0) + { + return 0; + } + if (restrictions.Length == 1) // Первая связь это адрес + { + if (restrictions[0] == Constants.Null) + { + return 0; + } + var any = Constants.Any; + if (Options.UseSequenceMarker) + { + var elementsLink = GetSequenceElements(restrictions[0]); + var sequenceLink = GetSequenceByElements(elementsLink); + if (sequenceLink != Constants.Null) + { + return Links.Count(any, sequenceLink) + Links.Count(any, elementsLink) - 1; + } + return Links.Count(any, elementsLink); + } + return Links.Count(any, restrictions[0]); + } + throw new NotImplementedException(); + } + + #endregion + + #region Create + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public LinkIndex Create(IList restrictions) + { + return _sync.ExecuteWriteOperation(() => + { + if (restrictions.IsNullOrEmpty()) + { + return Constants.Null; + } + Links.EnsureInnerReferenceExists(restrictions, nameof(restrictions)); + return CreateCore(restrictions); + }); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private LinkIndex CreateCore(IList restrictions) + { + LinkIndex[] sequence = restrictions.SkipFirst(); + if (Options.UseIndex) + { + Options.Index.Add(sequence); + } + var sequenceRoot = default(LinkIndex); + if (Options.EnforceSingleSequenceVersionOnWriteBasedOnExisting) + { + var matches = Each(restrictions); + if (matches.Count > 0) + { + sequenceRoot = matches[0]; + } + } + else if (Options.EnforceSingleSequenceVersionOnWriteBasedOnNew) + { + return CompactCore(sequence); + } + if (sequenceRoot == default) + { + sequenceRoot = Options.LinksToSequenceConverter.Convert(sequence); + } + if (Options.UseSequenceMarker) + { + return Links.Unsync.GetOrCreate(Options.SequenceMarkerLink, sequenceRoot); + } + return sequenceRoot; // Возвращаем корень последовательности (т.е. сами элементы) + } + + #endregion + + #region Each + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public List Each(IList sequence) + { + var results = new List(); + var filler = new ListFiller(results, Constants.Continue); + Each(filler.AddFirstAndReturnConstant, sequence); + return results; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public LinkIndex Each(Func, LinkIndex> handler, IList restrictions) + { + return _sync.ExecuteReadOperation(() => + { + if (restrictions.IsNullOrEmpty()) + { + return Constants.Continue; + } + Links.EnsureInnerReferenceExists(restrictions, nameof(restrictions)); + if (restrictions.Count == 1) + { + var link = restrictions[0]; + var any = Constants.Any; + if (link == any) + { + if (Options.UseSequenceMarker) + { + return Links.Unsync.Each(handler, new Link(any, Options.SequenceMarkerLink, any)); + } + else + { + return Links.Unsync.Each(handler, new Link(any, any, any)); + } + } + if (Options.UseSequenceMarker) + { + var sequenceLinkValues = Links.Unsync.GetLink(link); + if (sequenceLinkValues[Constants.SourcePart] == Options.SequenceMarkerLink) + { + link = sequenceLinkValues[Constants.TargetPart]; + } + } + var sequence = Options.Walker.Walk(link).ToArray().ShiftRight(); + sequence[0] = link; + return handler(sequence); + } + else if (restrictions.Count == 2) + { + throw new NotImplementedException(); + } + else if (restrictions.Count == 3) + { + return Links.Unsync.Each(handler, restrictions); + } + else + { + var sequence = restrictions.SkipFirst(); + if (Options.UseIndex && !Options.Index.MightContain(sequence)) + { + return Constants.Break; + } + return EachCore(handler, sequence); + } + }); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private LinkIndex EachCore(Func, LinkIndex> handler, IList values) + { + var matcher = new SimpleFullMatcher(this, values, new HashSet(), handler); + // TODO: Find out why matcher.HandleFullMatched executed twice for the same sequence Id. + Func, LinkIndex> innerHandler = Options.UseSequenceMarker ? (Func, LinkIndex>)matcher.HandleFullMatchedSequence : matcher.HandleFullMatched; + //if (sequence.Length >= 2) + if (StepRight(innerHandler, values[0], values[1]) != Constants.Continue) + { + return Constants.Break; + } + var last = values.Count - 2; + for (var i = 1; i < last; i++) + { + if (PartialStepRight(innerHandler, values[i], values[i + 1]) != Constants.Continue) + { + return Constants.Break; + } + } + if (values.Count >= 3) + { + if (StepLeft(innerHandler, values[values.Count - 2], values[values.Count - 1]) != Constants.Continue) + { + return Constants.Break; + } + } + return Constants.Continue; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private LinkIndex PartialStepRight(Func, LinkIndex> handler, LinkIndex left, LinkIndex right) + { + return Links.Unsync.Each(doublet => + { + var doubletIndex = doublet[Constants.IndexPart]; + if (StepRight(handler, doubletIndex, right) != Constants.Continue) + { + return Constants.Break; + } + if (left != doubletIndex) + { + return PartialStepRight(handler, doubletIndex, right); + } + return Constants.Continue; + }, new Link(Constants.Any, Constants.Any, left)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private LinkIndex StepRight(Func, LinkIndex> handler, LinkIndex left, LinkIndex right) => Links.Unsync.Each(rightStep => TryStepRightUp(handler, right, rightStep[Constants.IndexPart]), new Link(Constants.Any, left, Constants.Any)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private LinkIndex TryStepRightUp(Func, LinkIndex> handler, LinkIndex right, LinkIndex stepFrom) + { + var upStep = stepFrom; + var firstSource = Links.Unsync.GetTarget(upStep); + while (firstSource != right && firstSource != upStep) + { + upStep = firstSource; + firstSource = Links.Unsync.GetSource(upStep); + } + if (firstSource == right) + { + return handler(new LinkAddress(stepFrom)); + } + return Constants.Continue; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private LinkIndex StepLeft(Func, LinkIndex> handler, LinkIndex left, LinkIndex right) => Links.Unsync.Each(leftStep => TryStepLeftUp(handler, left, leftStep[Constants.IndexPart]), new Link(Constants.Any, Constants.Any, right)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private LinkIndex TryStepLeftUp(Func, LinkIndex> handler, LinkIndex left, LinkIndex stepFrom) + { + var upStep = stepFrom; + var firstTarget = Links.Unsync.GetSource(upStep); + while (firstTarget != left && firstTarget != upStep) + { + upStep = firstTarget; + firstTarget = Links.Unsync.GetTarget(upStep); + } + if (firstTarget == left) + { + return handler(new LinkAddress(stepFrom)); + } + return Constants.Continue; + } + + #endregion + + #region Update + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public LinkIndex Update(IList restrictions, IList substitution) + { + var sequence = restrictions.SkipFirst(); + var newSequence = substitution.SkipFirst(); + if (sequence.IsNullOrEmpty() && newSequence.IsNullOrEmpty()) + { + return Constants.Null; + } + if (sequence.IsNullOrEmpty()) + { + return Create(substitution); + } + if (newSequence.IsNullOrEmpty()) + { + Delete(restrictions); + return Constants.Null; + } + return _sync.ExecuteWriteOperation((Func)(() => + { + ILinksExtensions.EnsureLinkIsAnyOrExists(Links, (IList)sequence); + Links.EnsureLinkExists(newSequence); + return UpdateCore(sequence, newSequence); + })); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private LinkIndex UpdateCore(IList sequence, IList newSequence) + { + LinkIndex bestVariant; + if (Options.EnforceSingleSequenceVersionOnWriteBasedOnNew && !sequence.EqualTo(newSequence)) + { + bestVariant = CompactCore(newSequence); + } + else + { + bestVariant = CreateCore(newSequence); + } + // TODO: Check all options only ones before loop execution + // Возможно нужно две версии Each, возвращающий фактические последовательности и с маркером, + // или возможно даже возвращать и тот и тот вариант. С другой стороны все варианты можно получить имея только фактические последовательности. + foreach (var variant in Each(sequence)) + { + if (variant != bestVariant) + { + UpdateOneCore(variant, bestVariant); + } + } + return bestVariant; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void UpdateOneCore(LinkIndex sequence, LinkIndex newSequence) + { + if (Options.UseGarbageCollection) + { + var sequenceElements = GetSequenceElements(sequence); + var sequenceElementsContents = new Link(Links.GetLink(sequenceElements)); + var sequenceLink = GetSequenceByElements(sequenceElements); + var newSequenceElements = GetSequenceElements(newSequence); + var newSequenceLink = GetSequenceByElements(newSequenceElements); + if (Options.UseCascadeUpdate || CountUsages(sequence) == 0) + { + if (sequenceLink != Constants.Null) + { + Links.Unsync.MergeAndDelete(sequenceLink, newSequenceLink); + } + Links.Unsync.MergeAndDelete(sequenceElements, newSequenceElements); + } + ClearGarbage(sequenceElementsContents.Source); + ClearGarbage(sequenceElementsContents.Target); + } + else + { + if (Options.UseSequenceMarker) + { + var sequenceElements = GetSequenceElements(sequence); + var sequenceLink = GetSequenceByElements(sequenceElements); + var newSequenceElements = GetSequenceElements(newSequence); + var newSequenceLink = GetSequenceByElements(newSequenceElements); + if (Options.UseCascadeUpdate || CountUsages(sequence) == 0) + { + if (sequenceLink != Constants.Null) + { + Links.Unsync.MergeAndDelete(sequenceLink, newSequenceLink); + } + Links.Unsync.MergeAndDelete(sequenceElements, newSequenceElements); + } + } + else + { + if (Options.UseCascadeUpdate || CountUsages(sequence) == 0) + { + Links.Unsync.MergeAndDelete(sequence, newSequence); + } + } + } + } + + #endregion + + #region Delete + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Delete(IList restrictions) + { + _sync.ExecuteWriteOperation(() => + { + var sequence = restrictions.SkipFirst(); + // TODO: Check all options only ones before loop execution + foreach (var linkToDelete in Each(sequence)) + { + DeleteOneCore(linkToDelete); + } + }); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DeleteOneCore(LinkIndex link) + { + if (Options.UseGarbageCollection) + { + var sequenceElements = GetSequenceElements(link); + var sequenceElementsContents = new Link(Links.GetLink(sequenceElements)); + var sequenceLink = GetSequenceByElements(sequenceElements); + if (Options.UseCascadeDelete || CountUsages(link) == 0) + { + if (sequenceLink != Constants.Null) + { + Links.Unsync.Delete(sequenceLink); + } + Links.Unsync.Delete(link); + } + ClearGarbage(sequenceElementsContents.Source); + ClearGarbage(sequenceElementsContents.Target); + } + else + { + if (Options.UseSequenceMarker) + { + var sequenceElements = GetSequenceElements(link); + var sequenceLink = GetSequenceByElements(sequenceElements); + if (Options.UseCascadeDelete || CountUsages(link) == 0) + { + if (sequenceLink != Constants.Null) + { + Links.Unsync.Delete(sequenceLink); + } + Links.Unsync.Delete(link); + } + } + else + { + if (Options.UseCascadeDelete || CountUsages(link) == 0) + { + Links.Unsync.Delete(link); + } + } + } + } + + #endregion + + #region Compactification + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void CompactAll() + { + _sync.ExecuteWriteOperation(() => + { + var sequences = Each((LinkAddress)Constants.Any); + for (int i = 0; i < sequences.Count; i++) + { + var sequence = this.ToList(sequences[i]); + Compact(sequence.ShiftRight()); + } + }); + } + + /// + /// bestVariant можно выбирать по максимальному числу использований, + /// но балансированный позволяет гарантировать уникальность (если есть возможность, + /// гарантировать его использование в других местах). + /// + /// Получается этот метод должен игнорировать Options.EnforceSingleSequenceVersionOnWrite + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public LinkIndex Compact(IList sequence) + { + return _sync.ExecuteWriteOperation(() => + { + if (sequence.IsNullOrEmpty()) + { + return Constants.Null; + } + Links.EnsureInnerReferenceExists(sequence, nameof(sequence)); + return CompactCore(sequence); + }); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private LinkIndex CompactCore(IList sequence) => UpdateCore(sequence, sequence); + + #endregion + + #region Garbage Collection + + /// + /// TODO: Добавить дополнительный обработчик / событие CanBeDeleted которое можно определить извне или в унаследованном классе + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool IsGarbage(LinkIndex link) => link != Options.SequenceMarkerLink && !Links.Unsync.IsPartialPoint(link) && Links.Count(Constants.Any, link) == 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ClearGarbage(LinkIndex link) + { + if (IsGarbage(link)) + { + var contents = new Link(Links.GetLink(link)); + Links.Unsync.Delete(link); + ClearGarbage(contents.Source); + ClearGarbage(contents.Target); + } + } + + #endregion + + #region Walkers + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool EachPart(Func handler, LinkIndex sequence) + { + return _sync.ExecuteReadOperation(() => + { + var links = Links.Unsync; + foreach (var part in Options.Walker.Walk(sequence)) + { + if (!handler(part)) + { + return false; + } + } + return true; + }); + } + + + #endregion + } +} \ No newline at end of file diff --git a/csharp/Platform.Data.Doublets/Sequences/SequencesOptions.cs b/csharp/Platform.Data.Doublets/Sequences/SequencesOptions.cs new file mode 100644 index 000000000..7e72c2501 --- /dev/null +++ b/csharp/Platform.Data.Doublets/Sequences/SequencesOptions.cs @@ -0,0 +1,28 @@ +using System; +using System.Runtime.CompilerServices; +using LinkIndex = System.UInt64; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Platform.Data.Doublets.Sequences +{ + /// + /// Basic options for Sequences - minimal implementation for refactoring purposes. + /// + public class SequencesOptions + { + public bool UseSequenceMarker { get; set; } = false; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ValidateOptions() + { + // Basic validation - placeholder + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void InitOptions(object links) + { + // Basic initialization - placeholder + } + } +} \ No newline at end of file diff --git a/csharp/Platform.Data.Doublets/Sequences/SimpleFullMatcher.cs b/csharp/Platform.Data.Doublets/Sequences/SimpleFullMatcher.cs new file mode 100644 index 000000000..9dcd7d4c5 --- /dev/null +++ b/csharp/Platform.Data.Doublets/Sequences/SimpleFullMatcher.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using LinkIndex = System.UInt64; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Platform.Data.Doublets.Sequences +{ + /// + /// Matcher for full sequence matching operations - simplified version. + /// + public class SimpleFullMatcher : SimpleSequenceMatcherBase + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public SimpleFullMatcher(Sequences sequences, IList patternSequence, HashSet results, Func, LinkIndex> stopableHandler, HashSet? readAsElements = null) + : base(sequences, patternSequence, results, stopableHandler, readAsElements) + { + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override bool Match(LinkIndex sequenceToMatch) + { + return FullMatch(sequenceToMatch); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool FullMatch(LinkIndex sequenceToMatch) + { + _filterPosition = 0; + foreach (var part in Walk(sequenceToMatch)) + { + if (!FullMatchCore(part)) + { + break; + } + } + return _filterPosition == _patternSequence.Count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool FullMatchCore(LinkIndex element) + { + if (_filterPosition == _patternSequence.Count) + { + _filterPosition = -2; // Длиннее чем нужно + return false; + } + if (_patternSequence[_filterPosition] != _sequences.Links.Constants.Any + && element != _patternSequence[_filterPosition]) + { + _filterPosition = -1; + return false; // Начинается/Продолжается иначе + } + _filterPosition++; + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void AddMatchedToResults(IList restrictions) + { + AddFullMatchedToResults(restrictions); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AddFullMatchedToResults(IList restrictions) + { + var sequenceToMatch = restrictions[_sequences.Links.Constants.IndexPart]; + if (FullMatch(sequenceToMatch)) + { + _results.Add(sequenceToMatch); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override LinkIndex HandleMatched(IList restrictions) + { + return HandleFullMatched(restrictions); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public LinkIndex HandleFullMatched(IList restrictions) + { + var sequenceToMatch = restrictions[_sequences.Links.Constants.IndexPart]; + if (FullMatch(sequenceToMatch) && _results.Add(sequenceToMatch)) + { + return _stopableHandler(new LinkAddress(sequenceToMatch)); + } + return _sequences.Links.Constants.Continue; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public LinkIndex HandleFullMatchedSequence(IList restrictions) + { + var sequenceToMatch = restrictions[_sequences.Links.Constants.IndexPart]; + var sequence = _sequences.GetSequenceByElements(sequenceToMatch); + if (sequence != _sequences.Links.Constants.Null && FullMatch(sequenceToMatch) && _results.Add(sequenceToMatch)) + { + return _stopableHandler(new LinkAddress(sequence)); + } + return _sequences.Links.Constants.Continue; + } + } +} \ No newline at end of file diff --git a/csharp/Platform.Data.Doublets/Sequences/SimplePartialMatcher.cs b/csharp/Platform.Data.Doublets/Sequences/SimplePartialMatcher.cs new file mode 100644 index 000000000..58191a68a --- /dev/null +++ b/csharp/Platform.Data.Doublets/Sequences/SimplePartialMatcher.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using LinkIndex = System.UInt64; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Platform.Data.Doublets.Sequences +{ + /// + /// Matcher for partial sequence matching operations - simplified version. + /// + public class SimplePartialMatcher : SimpleSequenceMatcherBase + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public SimplePartialMatcher(Sequences sequences, IList patternSequence, HashSet results, Func, LinkIndex> stopableHandler, HashSet? readAsElements = null) + : base(sequences, patternSequence, results, stopableHandler, readAsElements) + { + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override bool Match(LinkIndex sequenceToMatch) + { + return PartialMatch(sequenceToMatch); + } + + /// + /// TODO: Add support for LinksConstants.Any + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool PartialMatch(LinkIndex sequenceToMatch) + { + _filterPosition = -1; + foreach (var part in Walk(sequenceToMatch)) + { + if (!PartialMatchCore(part)) + { + break; + } + } + return _filterPosition == _patternSequence.Count - 1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool PartialMatchCore(LinkIndex element) + { + if (_filterPosition == (_patternSequence.Count - 1)) + { + return false; // Нашлось + } + if (_filterPosition >= 0) + { + if (element == _patternSequence[_filterPosition + 1]) + { + _filterPosition++; + } + else + { + _filterPosition = -1; + } + } + if (_filterPosition < 0) + { + if (element == _patternSequence[0]) + { + _filterPosition = 0; + } + } + return true; // Ищем дальше + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void AddMatchedToResults(IList restrictions) + { + var sequenceToMatch = restrictions[_sequences.Links.Constants.IndexPart]; + AddPartialMatchedToResults(sequenceToMatch); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AddPartialMatchedToResults(LinkIndex sequenceToMatch) + { + if (PartialMatch(sequenceToMatch)) + { + _results.Add(sequenceToMatch); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override LinkIndex HandleMatched(IList restrictions) + { + return HandlePartialMatched(restrictions); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public LinkIndex HandlePartialMatched(IList restrictions) + { + var sequenceToMatch = restrictions[_sequences.Links.Constants.IndexPart]; + if (PartialMatch(sequenceToMatch)) + { + return _stopableHandler(new LinkAddress(sequenceToMatch)); + } + return _sequences.Links.Constants.Continue; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AddAllPartialMatchedToResults(IEnumerable sequencesToMatch) + { + foreach (var sequenceToMatch in sequencesToMatch) + { + if (PartialMatch(sequenceToMatch)) + { + _results.Add(sequenceToMatch); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AddAllPartialMatchedToResultsAndReadAsElements(IEnumerable sequencesToMatch) + { + foreach (var sequenceToMatch in sequencesToMatch) + { + if (PartialMatch(sequenceToMatch)) + { + _readAsElements?.Add(sequenceToMatch); + _results.Add(sequenceToMatch); + } + } + } + } +} \ No newline at end of file diff --git a/csharp/Platform.Data.Doublets/Sequences/SimpleSequenceMatcherBase.cs b/csharp/Platform.Data.Doublets/Sequences/SimpleSequenceMatcherBase.cs new file mode 100644 index 000000000..45fa5b922 --- /dev/null +++ b/csharp/Platform.Data.Doublets/Sequences/SimpleSequenceMatcherBase.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using LinkIndex = System.UInt64; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Platform.Data.Doublets.Sequences +{ + /// + /// Base class for sequence matchers providing common functionality without Walker dependency. + /// + public abstract class SimpleSequenceMatcherBase : ISequenceMatcher + { + protected readonly Sequences _sequences; + protected readonly IList _patternSequence; + protected readonly HashSet _linksInSequence; + protected readonly HashSet _results; + protected readonly Func, LinkIndex> _stopableHandler; + protected readonly HashSet? _readAsElements; + protected int _filterPosition; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected SimpleSequenceMatcherBase(Sequences sequences, IList patternSequence, HashSet results, Func, LinkIndex> stopableHandler, HashSet? readAsElements = null) + { + _sequences = sequences; + _patternSequence = patternSequence; + _linksInSequence = new HashSet(patternSequence.Where(x => x != sequences.Links.Constants.Any)); + _results = results; + _stopableHandler = stopableHandler; + _readAsElements = readAsElements; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected virtual bool IsElement(LinkIndex link) => (_readAsElements != null && _readAsElements.Contains(link)) || _linksInSequence.Contains(link); + + public abstract bool Match(LinkIndex sequenceToMatch); + + public abstract void AddMatchedToResults(IList restrictions); + + public abstract LinkIndex HandleMatched(IList restrictions); + + /// + /// Simple walk method without complex Walker dependency. + /// + protected virtual IEnumerable Walk(LinkIndex sequence) + { + // Simple implementation for walking through a sequence + // This is a basic version for the refactoring goal + + if (!_sequences.Links.Exists(sequence)) + yield break; + + var link = _sequences.Links.GetLink(sequence); + var source = _sequences.Links.GetSource(link); + var target = _sequences.Links.GetTarget(link); + + if (IsElement(source)) + yield return source; + else if (source != sequence) // Avoid infinite recursion + { + foreach (var part in Walk(source)) + yield return part; + } + + if (IsElement(target)) + yield return target; + else if (target != sequence) // Avoid infinite recursion + { + foreach (var part in Walk(target)) + yield return part; + } + } + } +} \ No newline at end of file diff --git a/sequences_original.cs b/sequences_original.cs new file mode 100644 index 000000000..475d857e1 --- /dev/null +++ b/sequences_original.cs @@ -0,0 +1,814 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using Platform.Collections; +using Platform.Collections.Lists; +using Platform.Collections.Stacks; +using Platform.Threading.Synchronization; +using Platform.Data.Doublets.Sequences.Walkers; +using LinkIndex = System.UInt64; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Platform.Data.Doublets.Sequences +{ + /// + /// Представляет коллекцию последовательностей связей. + /// + /// + /// Обязательно реализовать атомарность каждого публичного метода. + /// + /// TODO: + /// + /// !!! Повышение вероятности повторного использования групп (подпоследовательностей), + /// через естественную группировку по unicode типам, все whitespace вместе, все символы вместе, все числа вместе и т.п. + /// + использовать ровно сбалансированный вариант, чтобы уменьшать вложенность (глубину графа) + /// + /// x*y - найти все связи между, в последовательностях любой формы, если не стоит ограничитель на то, что является последовательностью, а что нет, + /// то находятся любые структуры связей, которые содержат эти элементы именно в таком порядке. + /// + /// Рост последовательности слева и справа. + /// Поиск со звёздочкой. + /// URL, PURL - реестр используемых во вне ссылок на ресурсы, + /// так же проблема может быть решена при реализации дистанционных триггеров. + /// Нужны ли уникальные указатели вообще? + /// Что если обращение к информации будет происходить через содержимое всегда? + /// + /// Писать тесты. + /// + /// + /// Можно убрать зависимость от конкретной реализации Links, + /// на зависимость от абстрактного элемента, который может быть представлен несколькими способами. + /// + /// Можно ли как-то сделать один общий интерфейс + /// + /// + /// Блокчейн и/или гит для распределённой записи транзакций. + /// + /// + public partial class Sequences : ILinks // IList, IList (после завершения реализации Sequences) + { + /// Возвращает значение LinkIndex, обозначающее любое количество связей. + public const LinkIndex ZeroOrMany = LinkIndex.MaxValue; + + public SequencesOptions Options { get; } + public SynchronizedLinks Links { get; } + private readonly ISynchronization _sync; + + public LinksConstants Constants { get; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Sequences(SynchronizedLinks links, SequencesOptions options) + { + Links = links; + _sync = links.SyncRoot; + Options = options; + Options.ValidateOptions(); + Options.InitOptions(Links); + Constants = links.Constants; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Sequences(SynchronizedLinks links) : this(links, new SequencesOptions()) { } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsSequence(LinkIndex sequence) + { + return _sync.ExecuteReadOperation(() => + { + if (Options.UseSequenceMarker) + { + return Options.MarkedSequenceMatcher.IsMatched(sequence); + } + return !Links.Unsync.IsPartialPoint(sequence); + }); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private LinkIndex GetSequenceByElements(LinkIndex sequence) + { + if (Options.UseSequenceMarker) + { + return Links.SearchOrDefault(Options.SequenceMarkerLink, sequence); + } + return sequence; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private LinkIndex GetSequenceElements(LinkIndex sequence) + { + if (Options.UseSequenceMarker) + { + var linkContents = new Link(Links.GetLink(sequence)); + if (linkContents.Source == Options.SequenceMarkerLink) + { + return linkContents.Target; + } + if (linkContents.Target == Options.SequenceMarkerLink) + { + return linkContents.Source; + } + } + return sequence; + } + + #region Count + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public LinkIndex Count(IList restrictions) + { + if (restrictions.IsNullOrEmpty()) + { + return Links.Count(Constants.Any, Options.SequenceMarkerLink, Constants.Any); + } + if (restrictions.Count == 1) // Первая связь это адрес + { + var sequenceIndex = restrictions[0]; + if (sequenceIndex == Constants.Null) + { + return 0; + } + if (sequenceIndex == Constants.Any) + { + return Count(null); + } + if (Options.UseSequenceMarker) + { + return Links.Count(Constants.Any, Options.SequenceMarkerLink, sequenceIndex); + } + return Links.Exists(sequenceIndex) ? 1UL : 0; + } + throw new NotImplementedException(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private LinkIndex CountUsages(params LinkIndex[] restrictions) + { + if (restrictions.Length == 0) + { + return 0; + } + if (restrictions.Length == 1) // Первая связь это адрес + { + if (restrictions[0] == Constants.Null) + { + return 0; + } + var any = Constants.Any; + if (Options.UseSequenceMarker) + { + var elementsLink = GetSequenceElements(restrictions[0]); + var sequenceLink = GetSequenceByElements(elementsLink); + if (sequenceLink != Constants.Null) + { + return Links.Count(any, sequenceLink) + Links.Count(any, elementsLink) - 1; + } + return Links.Count(any, elementsLink); + } + return Links.Count(any, restrictions[0]); + } + throw new NotImplementedException(); + } + + #endregion + + #region Create + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public LinkIndex Create(IList restrictions) + { + return _sync.ExecuteWriteOperation(() => + { + if (restrictions.IsNullOrEmpty()) + { + return Constants.Null; + } + Links.EnsureInnerReferenceExists(restrictions, nameof(restrictions)); + return CreateCore(restrictions); + }); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private LinkIndex CreateCore(IList restrictions) + { + LinkIndex[] sequence = restrictions.SkipFirst(); + if (Options.UseIndex) + { + Options.Index.Add(sequence); + } + var sequenceRoot = default(LinkIndex); + if (Options.EnforceSingleSequenceVersionOnWriteBasedOnExisting) + { + var matches = Each(restrictions); + if (matches.Count > 0) + { + sequenceRoot = matches[0]; + } + } + else if (Options.EnforceSingleSequenceVersionOnWriteBasedOnNew) + { + return CompactCore(sequence); + } + if (sequenceRoot == default) + { + sequenceRoot = Options.LinksToSequenceConverter.Convert(sequence); + } + if (Options.UseSequenceMarker) + { + return Links.Unsync.GetOrCreate(Options.SequenceMarkerLink, sequenceRoot); + } + return sequenceRoot; // Возвращаем корень последовательности (т.е. сами элементы) + } + + #endregion + + #region Each + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public List Each(IList sequence) + { + var results = new List(); + var filler = new ListFiller(results, Constants.Continue); + Each(filler.AddFirstAndReturnConstant, sequence); + return results; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public LinkIndex Each(Func, LinkIndex> handler, IList restrictions) + { + return _sync.ExecuteReadOperation(() => + { + if (restrictions.IsNullOrEmpty()) + { + return Constants.Continue; + } + Links.EnsureInnerReferenceExists(restrictions, nameof(restrictions)); + if (restrictions.Count == 1) + { + var link = restrictions[0]; + var any = Constants.Any; + if (link == any) + { + if (Options.UseSequenceMarker) + { + return Links.Unsync.Each(handler, new Link(any, Options.SequenceMarkerLink, any)); + } + else + { + return Links.Unsync.Each(handler, new Link(any, any, any)); + } + } + if (Options.UseSequenceMarker) + { + var sequenceLinkValues = Links.Unsync.GetLink(link); + if (sequenceLinkValues[Constants.SourcePart] == Options.SequenceMarkerLink) + { + link = sequenceLinkValues[Constants.TargetPart]; + } + } + var sequence = Options.Walker.Walk(link).ToArray().ShiftRight(); + sequence[0] = link; + return handler(sequence); + } + else if (restrictions.Count == 2) + { + throw new NotImplementedException(); + } + else if (restrictions.Count == 3) + { + return Links.Unsync.Each(handler, restrictions); + } + else + { + var sequence = restrictions.SkipFirst(); + if (Options.UseIndex && !Options.Index.MightContain(sequence)) + { + return Constants.Break; + } + return EachCore(handler, sequence); + } + }); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private LinkIndex EachCore(Func, LinkIndex> handler, IList values) + { + var matcher = new Matcher(this, values, new HashSet(), handler); + // TODO: Find out why matcher.HandleFullMatched executed twice for the same sequence Id. + Func, LinkIndex> innerHandler = Options.UseSequenceMarker ? (Func, LinkIndex>)matcher.HandleFullMatchedSequence : matcher.HandleFullMatched; + //if (sequence.Length >= 2) + if (StepRight(innerHandler, values[0], values[1]) != Constants.Continue) + { + return Constants.Break; + } + var last = values.Count - 2; + for (var i = 1; i < last; i++) + { + if (PartialStepRight(innerHandler, values[i], values[i + 1]) != Constants.Continue) + { + return Constants.Break; + } + } + if (values.Count >= 3) + { + if (StepLeft(innerHandler, values[values.Count - 2], values[values.Count - 1]) != Constants.Continue) + { + return Constants.Break; + } + } + return Constants.Continue; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private LinkIndex PartialStepRight(Func, LinkIndex> handler, LinkIndex left, LinkIndex right) + { + return Links.Unsync.Each(doublet => + { + var doubletIndex = doublet[Constants.IndexPart]; + if (StepRight(handler, doubletIndex, right) != Constants.Continue) + { + return Constants.Break; + } + if (left != doubletIndex) + { + return PartialStepRight(handler, doubletIndex, right); + } + return Constants.Continue; + }, new Link(Constants.Any, Constants.Any, left)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private LinkIndex StepRight(Func, LinkIndex> handler, LinkIndex left, LinkIndex right) => Links.Unsync.Each(rightStep => TryStepRightUp(handler, right, rightStep[Constants.IndexPart]), new Link(Constants.Any, left, Constants.Any)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private LinkIndex TryStepRightUp(Func, LinkIndex> handler, LinkIndex right, LinkIndex stepFrom) + { + var upStep = stepFrom; + var firstSource = Links.Unsync.GetTarget(upStep); + while (firstSource != right && firstSource != upStep) + { + upStep = firstSource; + firstSource = Links.Unsync.GetSource(upStep); + } + if (firstSource == right) + { + return handler(new LinkAddress(stepFrom)); + } + return Constants.Continue; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private LinkIndex StepLeft(Func, LinkIndex> handler, LinkIndex left, LinkIndex right) => Links.Unsync.Each(leftStep => TryStepLeftUp(handler, left, leftStep[Constants.IndexPart]), new Link(Constants.Any, Constants.Any, right)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private LinkIndex TryStepLeftUp(Func, LinkIndex> handler, LinkIndex left, LinkIndex stepFrom) + { + var upStep = stepFrom; + var firstTarget = Links.Unsync.GetSource(upStep); + while (firstTarget != left && firstTarget != upStep) + { + upStep = firstTarget; + firstTarget = Links.Unsync.GetTarget(upStep); + } + if (firstTarget == left) + { + return handler(new LinkAddress(stepFrom)); + } + return Constants.Continue; + } + + #endregion + + #region Update + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public LinkIndex Update(IList restrictions, IList substitution) + { + var sequence = restrictions.SkipFirst(); + var newSequence = substitution.SkipFirst(); + if (sequence.IsNullOrEmpty() && newSequence.IsNullOrEmpty()) + { + return Constants.Null; + } + if (sequence.IsNullOrEmpty()) + { + return Create(substitution); + } + if (newSequence.IsNullOrEmpty()) + { + Delete(restrictions); + return Constants.Null; + } + return _sync.ExecuteWriteOperation((Func)(() => + { + ILinksExtensions.EnsureLinkIsAnyOrExists(Links, (IList)sequence); + Links.EnsureLinkExists(newSequence); + return UpdateCore(sequence, newSequence); + })); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private LinkIndex UpdateCore(IList sequence, IList newSequence) + { + LinkIndex bestVariant; + if (Options.EnforceSingleSequenceVersionOnWriteBasedOnNew && !sequence.EqualTo(newSequence)) + { + bestVariant = CompactCore(newSequence); + } + else + { + bestVariant = CreateCore(newSequence); + } + // TODO: Check all options only ones before loop execution + // Возможно нужно две версии Each, возвращающий фактические последовательности и с маркером, + // или возможно даже возвращать и тот и тот вариант. С другой стороны все варианты можно получить имея только фактические последовательности. + foreach (var variant in Each(sequence)) + { + if (variant != bestVariant) + { + UpdateOneCore(variant, bestVariant); + } + } + return bestVariant; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void UpdateOneCore(LinkIndex sequence, LinkIndex newSequence) + { + if (Options.UseGarbageCollection) + { + var sequenceElements = GetSequenceElements(sequence); + var sequenceElementsContents = new Link(Links.GetLink(sequenceElements)); + var sequenceLink = GetSequenceByElements(sequenceElements); + var newSequenceElements = GetSequenceElements(newSequence); + var newSequenceLink = GetSequenceByElements(newSequenceElements); + if (Options.UseCascadeUpdate || CountUsages(sequence) == 0) + { + if (sequenceLink != Constants.Null) + { + Links.Unsync.MergeAndDelete(sequenceLink, newSequenceLink); + } + Links.Unsync.MergeAndDelete(sequenceElements, newSequenceElements); + } + ClearGarbage(sequenceElementsContents.Source); + ClearGarbage(sequenceElementsContents.Target); + } + else + { + if (Options.UseSequenceMarker) + { + var sequenceElements = GetSequenceElements(sequence); + var sequenceLink = GetSequenceByElements(sequenceElements); + var newSequenceElements = GetSequenceElements(newSequence); + var newSequenceLink = GetSequenceByElements(newSequenceElements); + if (Options.UseCascadeUpdate || CountUsages(sequence) == 0) + { + if (sequenceLink != Constants.Null) + { + Links.Unsync.MergeAndDelete(sequenceLink, newSequenceLink); + } + Links.Unsync.MergeAndDelete(sequenceElements, newSequenceElements); + } + } + else + { + if (Options.UseCascadeUpdate || CountUsages(sequence) == 0) + { + Links.Unsync.MergeAndDelete(sequence, newSequence); + } + } + } + } + + #endregion + + #region Delete + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Delete(IList restrictions) + { + _sync.ExecuteWriteOperation(() => + { + var sequence = restrictions.SkipFirst(); + // TODO: Check all options only ones before loop execution + foreach (var linkToDelete in Each(sequence)) + { + DeleteOneCore(linkToDelete); + } + }); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void DeleteOneCore(LinkIndex link) + { + if (Options.UseGarbageCollection) + { + var sequenceElements = GetSequenceElements(link); + var sequenceElementsContents = new Link(Links.GetLink(sequenceElements)); + var sequenceLink = GetSequenceByElements(sequenceElements); + if (Options.UseCascadeDelete || CountUsages(link) == 0) + { + if (sequenceLink != Constants.Null) + { + Links.Unsync.Delete(sequenceLink); + } + Links.Unsync.Delete(link); + } + ClearGarbage(sequenceElementsContents.Source); + ClearGarbage(sequenceElementsContents.Target); + } + else + { + if (Options.UseSequenceMarker) + { + var sequenceElements = GetSequenceElements(link); + var sequenceLink = GetSequenceByElements(sequenceElements); + if (Options.UseCascadeDelete || CountUsages(link) == 0) + { + if (sequenceLink != Constants.Null) + { + Links.Unsync.Delete(sequenceLink); + } + Links.Unsync.Delete(link); + } + } + else + { + if (Options.UseCascadeDelete || CountUsages(link) == 0) + { + Links.Unsync.Delete(link); + } + } + } + } + + #endregion + + #region Compactification + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void CompactAll() + { + _sync.ExecuteWriteOperation(() => + { + var sequences = Each((LinkAddress)Constants.Any); + for (int i = 0; i < sequences.Count; i++) + { + var sequence = this.ToList(sequences[i]); + Compact(sequence.ShiftRight()); + } + }); + } + + /// + /// bestVariant можно выбирать по максимальному числу использований, + /// но балансированный позволяет гарантировать уникальность (если есть возможность, + /// гарантировать его использование в других местах). + /// + /// Получается этот метод должен игнорировать Options.EnforceSingleSequenceVersionOnWrite + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public LinkIndex Compact(IList sequence) + { + return _sync.ExecuteWriteOperation(() => + { + if (sequence.IsNullOrEmpty()) + { + return Constants.Null; + } + Links.EnsureInnerReferenceExists(sequence, nameof(sequence)); + return CompactCore(sequence); + }); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private LinkIndex CompactCore(IList sequence) => UpdateCore(sequence, sequence); + + #endregion + + #region Garbage Collection + + /// + /// TODO: Добавить дополнительный обработчик / событие CanBeDeleted которое можно определить извне или в унаследованном классе + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool IsGarbage(LinkIndex link) => link != Options.SequenceMarkerLink && !Links.Unsync.IsPartialPoint(link) && Links.Count(Constants.Any, link) == 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ClearGarbage(LinkIndex link) + { + if (IsGarbage(link)) + { + var contents = new Link(Links.GetLink(link)); + Links.Unsync.Delete(link); + ClearGarbage(contents.Source); + ClearGarbage(contents.Target); + } + } + + #endregion + + #region Walkers + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool EachPart(Func handler, LinkIndex sequence) + { + return _sync.ExecuteReadOperation(() => + { + var links = Links.Unsync; + foreach (var part in Options.Walker.Walk(sequence)) + { + if (!handler(part)) + { + return false; + } + } + return true; + }); + } + + public class Matcher : RightSequenceWalker + { + private readonly Sequences _sequences; + private readonly IList _patternSequence; + private readonly HashSet _linksInSequence; + private readonly HashSet _results; + private readonly Func, LinkIndex> _stopableHandler; + private readonly HashSet _readAsElements; + private int _filterPosition; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Matcher(Sequences sequences, IList patternSequence, HashSet results, Func, LinkIndex> stopableHandler, HashSet readAsElements = null) + : base(sequences.Links.Unsync, new DefaultStack()) + { + _sequences = sequences; + _patternSequence = patternSequence; + _linksInSequence = new HashSet(patternSequence.Where(x => x != Links.Constants.Any && x != ZeroOrMany)); + _results = results; + _stopableHandler = stopableHandler; + _readAsElements = readAsElements; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override bool IsElement(LinkIndex link) => base.IsElement(link) || (_readAsElements != null && _readAsElements.Contains(link)) || _linksInSequence.Contains(link); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool FullMatch(LinkIndex sequenceToMatch) + { + _filterPosition = 0; + foreach (var part in Walk(sequenceToMatch)) + { + if (!FullMatchCore(part)) + { + break; + } + } + return _filterPosition == _patternSequence.Count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool FullMatchCore(LinkIndex element) + { + if (_filterPosition == _patternSequence.Count) + { + _filterPosition = -2; // Длиннее чем нужно + return false; + } + if (_patternSequence[_filterPosition] != Links.Constants.Any + && element != _patternSequence[_filterPosition]) + { + _filterPosition = -1; + return false; // Начинается/Продолжается иначе + } + _filterPosition++; + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AddFullMatchedToResults(IList restrictions) + { + var sequenceToMatch = restrictions[Links.Constants.IndexPart]; + if (FullMatch(sequenceToMatch)) + { + _results.Add(sequenceToMatch); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public LinkIndex HandleFullMatched(IList restrictions) + { + var sequenceToMatch = restrictions[Links.Constants.IndexPart]; + if (FullMatch(sequenceToMatch) && _results.Add(sequenceToMatch)) + { + return _stopableHandler(new LinkAddress(sequenceToMatch)); + } + return Links.Constants.Continue; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public LinkIndex HandleFullMatchedSequence(IList restrictions) + { + var sequenceToMatch = restrictions[Links.Constants.IndexPart]; + var sequence = _sequences.GetSequenceByElements(sequenceToMatch); + if (sequence != Links.Constants.Null && FullMatch(sequenceToMatch) && _results.Add(sequenceToMatch)) + { + return _stopableHandler(new LinkAddress(sequence)); + } + return Links.Constants.Continue; + } + + /// + /// TODO: Add support for LinksConstants.Any + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool PartialMatch(LinkIndex sequenceToMatch) + { + _filterPosition = -1; + foreach (var part in Walk(sequenceToMatch)) + { + if (!PartialMatchCore(part)) + { + break; + } + } + return _filterPosition == _patternSequence.Count - 1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool PartialMatchCore(LinkIndex element) + { + if (_filterPosition == (_patternSequence.Count - 1)) + { + return false; // Нашлось + } + if (_filterPosition >= 0) + { + if (element == _patternSequence[_filterPosition + 1]) + { + _filterPosition++; + } + else + { + _filterPosition = -1; + } + } + if (_filterPosition < 0) + { + if (element == _patternSequence[0]) + { + _filterPosition = 0; + } + } + return true; // Ищем дальше + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AddPartialMatchedToResults(LinkIndex sequenceToMatch) + { + if (PartialMatch(sequenceToMatch)) + { + _results.Add(sequenceToMatch); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public LinkIndex HandlePartialMatched(IList restrictions) + { + var sequenceToMatch = restrictions[Links.Constants.IndexPart]; + if (PartialMatch(sequenceToMatch)) + { + return _stopableHandler(new LinkAddress(sequenceToMatch)); + } + return Links.Constants.Continue; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AddAllPartialMatchedToResults(IEnumerable sequencesToMatch) + { + foreach (var sequenceToMatch in sequencesToMatch) + { + if (PartialMatch(sequenceToMatch)) + { + _results.Add(sequenceToMatch); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AddAllPartialMatchedToResultsAndReadAsElements(IEnumerable sequencesToMatch) + { + foreach (var sequenceToMatch in sequencesToMatch) + { + if (PartialMatch(sequenceToMatch)) + { + _readAsElements.Add(sequenceToMatch); + _results.Add(sequenceToMatch); + } + } + } + } + + #endregion + } +} \ No newline at end of file