< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 73
Coverable lines: 73
Total lines: 225
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 38
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)0%220%
Build()0%440%
Dispose()100%110%
BuildTrie(...)0%12120%
AddSuffixLinks()0%14140%
GenerateStartingAsciiCharsBitmap()0%660%

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Private.CoreLib/src/System/SearchValues/Strings/Helpers/AhoCorasickBuilder.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Collections.Generic;
 5using System.Diagnostics;
 6using System.Runtime.Intrinsics;
 7using System.Text;
 8
 9namespace System.Buffers
 10{
 11    /// <summary>
 12    /// Separated out of <see cref="AhoCorasick"/> to allow us to defer some computation costs in case we decide not to 
 13    /// </summary>
 14    internal ref struct AhoCorasickBuilder
 15    {
 16        private readonly ReadOnlySpan<string> _values;
 17        private readonly bool _ignoreCase;
 18        private ValueListBuilder<AhoCorasickNode> _nodes;
 19        private ValueListBuilder<int> _parents;
 20        private IndexOfAnyAsciiSearcher.AsciiState _startingAsciiChars;
 21
 22        public AhoCorasickBuilder(ReadOnlySpan<string> values, bool ignoreCase, ref HashSet<string>? unreachableValues)
 23        {
 024            Debug.Assert(!values.IsEmpty);
 025            Debug.Assert(!string.IsNullOrEmpty(values[0]));
 26
 27#if DEBUG
 28            // The input should have been sorted by length
 029            for (int i = 1; i < values.Length; i++)
 30            {
 031                Debug.Assert(values[i - 1].Length <= values[i].Length);
 32            }
 33#endif
 34
 035            _values = values;
 036            _ignoreCase = ignoreCase;
 037            BuildTrie(ref unreachableValues);
 038        }
 39
 40        public AhoCorasick Build()
 41        {
 042            AddSuffixLinks();
 43
 044            Debug.Assert(_nodes[0].MatchLength == 0, "The root node shouldn't have a match.");
 45
 046            for (int i = 0; i < _nodes.Length; i++)
 47            {
 048                _nodes[i].OptimizeChildren();
 49            }
 50
 051            if (IndexOfAnyAsciiSearcher.IsVectorizationSupported)
 52            {
 053                GenerateStartingAsciiCharsBitmap();
 54            }
 55
 056            return new AhoCorasick(_nodes.AsSpan().ToArray(), _startingAsciiChars);
 57        }
 58
 59        public void Dispose()
 60        {
 061            _nodes.Dispose();
 062            _parents.Dispose();
 063        }
 64
 65        private void BuildTrie(ref HashSet<string>? unreachableValues)
 66        {
 067            _nodes.Append(new AhoCorasickNode());
 068            _parents.Append(0);
 69
 070            foreach (string value in _values)
 71            {
 072                int nodeIndex = 0;
 073                ref AhoCorasickNode node = ref _nodes[nodeIndex];
 74
 075                for (int i = 0; i < value.Length; i++)
 76                {
 077                    char c = value[i];
 78
 079                    if (!node.TryGetChild(c, out int childIndex))
 80                    {
 081                        childIndex = _nodes.Length;
 082                        node.AddChild(c, childIndex);
 083                        _nodes.Append(new AhoCorasickNode());
 084                        _parents.Append(nodeIndex);
 85                    }
 86
 087                    node = ref _nodes[childIndex];
 088                    nodeIndex = childIndex;
 89
 090                    if (node.MatchLength != 0)
 91                    {
 92                        // A previous value is an exact prefix of this one.
 93                        // We're looking for the index of the first match, not necessarily the longest one, so we can sk
 94                        // We've already normalized the values, so we can do ordinal comparisons here.
 095                        unreachableValues ??= new HashSet<string>(StringComparer.Ordinal);
 096                        unreachableValues.Add(value);
 097                        break;
 98                    }
 99
 0100                    if (i == value.Length - 1)
 101                    {
 0102                        node.MatchLength = value.Length;
 0103                        break;
 104                    }
 105                }
 106            }
 0107        }
 108
 109        private void AddSuffixLinks()
 110        {
 111            // Besides the list of children which continue the current value, each node also contains a suffix link
 112            // which points to the node with the longest suffix of the current node.
 113            // When we're searching and can't find a child to extend the current string with, we will follow
 114            // suffix links to find the longest string that does match up until the current point.
 115            //
 116            // For example if we have strings "DOTNET" and "OTTER", we want
 117            // the 'O' and 'T' in "dotnet" to point into 'O' and 'T' in "OTTER".
 118            // If our text contains the word "dotter", we will walk it character by character.
 119            // Once we get to "DOTNET" and read the next character 'T', we can no longer continue with "DOTNET",
 120            // and will instead follow the suffix link to "ot" in "OTTER" where we can continue the search.
 121            //
 122            // We also remember when a node's suffix link points to the end of a different value, such that it is itself
 123            // If we also had the word "POTTERY", the 'R' would contain a suffix link to the 'R' in "OTTER",
 124            // but also mark that it is already a length=5 match.
 125            //
 126            //       +---> D  O  T  N  E  T
 127            //       |        |  |
 128            //       |     +--+  |
 129            // root--+     |     |
 130            //       |     |  +--+
 131            //       |     v  v
 132            //       +---> O  T  T  E  R
 133            //       |     ^  ^  ^  ^  ^
 134            //       |     |  |  |  |  | -- this is also a length=5 match
 135            //       |     |  |  |  |  |
 136            //       +> P  O  T  T  E  R  Y
 137
 0138            var queue = new Queue<(char Char, int Index)>();
 0139            queue.Enqueue(((char)0, 0));
 140
 0141            while (queue.TryDequeue(out (char Char, int Index) trieNode))
 142            {
 0143                ref AhoCorasickNode node = ref _nodes[trieNode.Index];
 0144                int parent = _parents[trieNode.Index];
 0145                int suffixLink = _nodes[parent].SuffixLink;
 146
 147                // If this node doesn't represent the first character of a value (doesn't immediately follow the root no
 148                // it may have a have a non-zero suffix link.
 0149                if (parent != 0)
 150                {
 0151                    while (suffixLink >= 0)
 152                    {
 0153                        ref AhoCorasickNode suffixNode = ref _nodes[suffixLink];
 154
 0155                        if (suffixNode.TryGetChild(trieNode.Char, out int childSuffixLink))
 156                        {
 0157                            suffixLink = childSuffixLink;
 0158                            break;
 159                        }
 160
 0161                        if (suffixLink == 0)
 162                        {
 163                            break;
 164                        }
 165
 0166                        suffixLink = suffixNode.SuffixLink;
 167                    }
 168                }
 169
 0170                if (node.MatchLength != 0)
 171                {
 172                    // This node represents the end of a match.
 173                    // Mark it in a special way we can recognize when searching.
 0174                    node.SuffixLink = -1;
 175
 176                    // If a node is a match, there is no need to assign suffix links to its children.
 177                    // If a child does not match, such that we would look at its suffix link,
 178                    // we have already saw an earlier match node that is definitely the earliest possible match.
 179                }
 180                else
 181                {
 0182                    node.SuffixLink = suffixLink;
 183
 0184                    if (suffixLink >= 0)
 185                    {
 186                        // Remember if this node's suffix link points to a node that is itself a match.
 0187                        node.MatchLength = _nodes[suffixLink].MatchLength;
 188                    }
 189
 0190                    node.AddChildrenToQueue(queue);
 191                }
 0192            }
 0193        }
 194
 195        // If all the values start with ASCII characters, we can use IndexOfAnyAsciiSearcher
 196        // to quickly skip to the next possible starting location in the input.
 197        private unsafe void GenerateStartingAsciiCharsBitmap()
 198        {
 0199            scoped ValueListBuilder<char> startingChars = new ValueListBuilder<char>(stackalloc char[128]);
 200
 0201            foreach (string value in _values)
 202            {
 0203                char c = value[0];
 204
 0205                if (_ignoreCase)
 206                {
 0207                    startingChars.Append(char.ToLowerInvariant(c));
 0208                    startingChars.Append(char.ToUpperInvariant(c));
 209                }
 210                else
 211                {
 0212                    startingChars.Append(c);
 213                }
 214            }
 215
 0216            if (Ascii.IsValid(startingChars.AsSpan()))
 217            {
 0218                IndexOfAnyAsciiSearcher.ComputeAsciiState(startingChars.AsSpan(), out _startingAsciiChars);
 219            }
 220
 0221            startingChars.Dispose();
 0222        }
 223    }
 224}
 225