; --------------------------------------------------------------------------------------------; Copyright (c) Fantaisie Software. All rights reserved.; Dual licensed under the GPL and Fantaisie Software licenses.; See LICENSE and LICENSE-FANTAISIE in the project root for license information.; --------------------------------------------------------------------------------------------;; Simple implementation of a compact prefix tree (radix tree) to store values ordered by a key; with fast lookup of all values matching a specific key prefix.;; Only "insert" and "free" are implemented because that is all we need for our purposes.; Deleting individual keys is not implemented for now.;; All keys are handled Case insensitive.;CompilerIf Not Defined(RadixNode, #PB_Structure) ; For local testing. Defined in Common.pb tooStructure RadixNodeChars$ ; Incoming prefix for this node (stored in uppercase)*Child.RadixNode ; First child node (if any)*Next.RadixNode ; Next sibling node in same parent (single linked list, sorted by prefix)*Value ; Stored value or nullEndStructureStructure RadixTree*Node ; First child of the root node (other children are under Child\Next). Null for an empty treeEndStructure#CharSize = 2CompilerEndIfProcedure Radix_FreeNode(*Node.RadixNode)While *Node*Next = *Node\NextRadix_FreeNode(*Node\Child)FreeStructure(*Node)*Node = *NextWendEndProcedureProcedure RadixFree(*Tree.RadixTree)Radix_FreeNode(*Tree\Node)*Tree\Node = #NullEndProcedure; Returns RadixNode or null;Procedure RadixFindPrefix(*Tree.RadixTree, Prefix,ドル ExactMatchOnly = #False); The algorithm below expects at least one character to be searched forIf Prefix$ = ""ProcedureReturn #NullEndIf; Use LCase() instead of UCase() to be compatiable to internal PB case insensitive compare (https://www.purebasic.fr/english/viewtopic.php?p=615646)PrefixL$ = LCase(Prefix$)*PrefixCursor.Character = @PrefixL$*Node.RadixNode = *Tree\NodeWhile *Node*NodeCursor.Character = @*Node\Chars$; Trie-invariant: Each child starts with a unique character so we only need to look at that;If *PrefixCursor\c = *NodeCursor\c; Node matches first char. Match further characters in the node*NodeCursor + #CharSize*PrefixCursor + #CharSizeWhile *NodeCursor\c And *PrefixCursor\c And *NodeCursor\c = *PrefixCursor\c*NodeCursor + #CharSize*PrefixCursor + #CharSizeWendIf *PrefixCursor\c = 0; prefix fully matched the nodeIf ExactMatchOnly And *NodeCursor\c <> 0ProcedureReturn #NullElseProcedureReturn *NodeEndIfElseIf *NodeCursor\c = 0; all characters consumed. continue searching child nodes*Node = *Node\ChildElse; difference in part of the node. no matchProcedureReturn #NullEndIfElseIf *NodeCursor\c < *PrefixCursor\c; Look at next node on same level*Node = *Node\NextElse; No more matches possible since nodes are sortedProcedureReturn #NullEndIfWend; Reached the end of child-node list without a match but unmatched prefix chars remainProcedureReturn #NullEndProcedureProcedure RadixLookupValue(*Tree.RadixTree, Name$)*Result.RadixNode = RadixFindPrefix(*Tree, Name,ドル #True)If *ResultProcedureReturn *Result\ValueElseProcedureReturn #NullEndIfEndProcedure; Fills values of first and last match for the prefix; This is useful if the underlying data is sorted too and the tree stores indexes rather than pointers;Procedure RadixFindRange(*Tree.RadixTree, Prefix,ドル *First.INTEGER, *Last.INTEGER)*First\i = #Null*Last\i = #Null*Result.RadixNode = RadixFindPrefix(*Tree, Prefix,ドル #False)If *Result; All leaf nodes have non-empty values but nodes in the tree might too (if they match a full word); So the first non-empty node must be on the path of each first child node of *Result or *Result itself*FirstNode.RadixNode = *ResultWhile *FirstNodeIf *FirstNode\Value*First\i = *FirstNode\ValueBreakEndIf*FirstNode = *FirstNode\ChildWend*Last\i = *First\i ; in case only *Result matched; Similarily the last non-empty node that matches must be on the path of each last child node; Not on the first level though because the *Result\Next pointers here point to nodes unrelated to the match*LastNode.RadixNode = *Result\ChildWhile *LastNode; skip to last childWhile *LastNode\Next <> #Null*LastNode = *LastNode\NextWendIf *LastNode\Value*Last\i = *LastNode\Value; no Break. Any children are sorted AFTER this node so keep lookingEndIf*LastNode = *LastNode\ChildWendIf *First\iProcedureReturn #TrueEndIfEndIfProcedureReturn #FalseEndProcedure; Helper functionProcedure Radix_EnumerateNodes(*Node.RadixNode, List *Values())While *NodeIf *Node\ValueAddElement(*Values())*Values() = *Node\ValueEndIfRadix_EnumerateNodes(*Node\Child, *Values())*Node = *Node\NextWendEndProcedureProcedure RadixEnumerateAll(*Tree.RadixTree, List *Values())ClearList(*Values())Radix_EnumerateNodes(*Tree\Node, *Values())EndProcedureProcedure RadixEnumeratePrefix(*Tree.RadixTree, Name,ドル List *Values())ClearList(*Values())*Result.RadixNode = RadixFindPrefix(*Tree, Name,ドル #False)If *Result; Do not look at the \Next nodes from the current node as we only want the current node and anything below itIf *Result\ValueAddElement(*Values())*Values() = *Result\ValueEndIfRadix_EnumerateNodes(*Result\Child, *Values())EndIfEndProcedure; Helper functionProcedure Radix_AllocNode(*PrefixCursor.Character, *Value)*Node.RadixNode = AllocateStructure(RadixNode)*Node\Chars$ = PeekS(*PrefixCursor)*Node\Value = *ValueProcedureReturn *NodeEndProcedureProcedure RadixInsert(*Tree.RadixTree, Name,ドル *Value)If *Value = 0ProcedureReturnEndIf; Use LCase() instead of UCase() to be compatiable to internal PB case insensitive compare (https://www.purebasic.fr/english/viewtopic.php?p=615646)PrefixL$ = LCase(Name$)*PrefixCursor.Character = @PrefixL$If *Tree\Node = #Null; Tree is empty so far*Tree\Node = Radix_AllocNode(*PrefixCursor, *Value)Else; Similar to the algorithm in RadixFindPrefix()*Node.RadixNode = *Tree\Node*Parent.RadixNode = #Null*Previous.RadixNode = #NullWhile *Node*NodeCursor.Character = @*Node\Chars$If *PrefixCursor\c = *NodeCursor\c*NodeCursor + #CharSize*PrefixCursor + #CharSizeWhile *NodeCursor\c And *PrefixCursor\c And *NodeCursor\c = *PrefixCursor\c*NodeCursor + #CharSize*PrefixCursor + #CharSizeWendIf *PrefixCursor\c = 0 And *NodeCursor\c = 0; exact match. do not add duplicatesIf *Node\Value = 0*Node\Value = *ValueEndIfProcedureReturnElseIf *NodeCursor\c = 0; all characters consumed.If *Node\Child; continue searching child nodes*Parent = *Node*Previous = #Null*Node = *Node\ChildElse; Add a child node*Node\Child = Radix_AllocNode(*PrefixCursor, *Value)ProcedureReturnEndIfElseIf *PrefixCursor\c = 0; Need to split the node to add the value (same prefix as the existing node)*SplitNode.RadixNode = Radix_AllocNode(*NodeCursor, *Node\Value)*SplitNode\Child = *Node\Child*Node\Chars$ = Left(*Node\Chars,ドル Len(*Node\Chars$)-Len(*SplitNode\Chars$))*Node\Value = *Value*Node\Child = *SplitNodeProcedureReturnElse; Need to split the node and add a separate sub-node because prefix and node chars differNodeChar.c = *NodeCursor\c ; save this as we re-alloc the string*SplitNode.RadixNode = Radix_AllocNode(*NodeCursor, *Node\Value)*SplitNode\Child = *Node\Child*NewNode.RadixNode = Radix_AllocNode(*PrefixCursor, *Value)*Node\Chars$ = Left(*Node\Chars,ドル Len(*Node\Chars$)-Len(*SplitNode\Chars$)) ; invalidates *NodeCursor !*Node\Value = #NullIf NodeChar < *PrefixCursor\c*Node\Child = *SplitNode*SplitNode\Next = *NewNodeElse*Node\Child = *NewNode*NewNode\Next = *SplitNodeEndIfProcedureReturnEndIfElseIf *NodeCursor\c < *PrefixCursor\c; Look at next node on same level*Previous = *Node*Node = *Node\NextElse; No more matches possible since nodes are sorted. Add a new node before this one*NewNode.RadixNode = Radix_AllocNode(*PrefixCursor, *Value)*NewNode\Next = *NodeIf *Previous*Previous\Next = *NewNodeElseIf *Parent*Parent\Child = *NewNodeElse*Tree\Node = *NewNodeEndIfProcedureReturnEndIfWend; Reached end of the node list. Add a new one here; There must be a *Previous node as the no-child case is handled above already*NewNode = Radix_AllocNode(*PrefixCursor, *Value)*Previous\Next = *NewNodeEndIfEndProcedure;- ------ Debugging and Testing ------CompilerIf #FalseProcedure DebugRadixGraph_Recursive(*Node.RadixNode, Spaces,ドル Parent,ドル *Selected.RadixNode, InSelection)While *NodeNodeName$ = "n" + Hex(*Node)If *Node\ValueLabel$ = PeekS(*Node\Value)ElseLabel$ = ""EndIfIf *Node = *SelectedDebug Spaces$ + NodeName$ + ~" [label=\"" + Label$ + ~"\", color=\"red\"]"ElseIf InSelectionDebug Spaces$ + NodeName$ + ~" [label=\"" + Label$ + ~"\", color=\"green\"]"ElseDebug Spaces$ + NodeName$ + ~" [label=\"" + Label$ + ~"\"]"EndIfDebug Spaces$ + Parent$ + " -> " + NodeName$ + ~" [label=\"" + *Node\Chars$ + ~"\"]"If *Node = *SelectedDebugRadixGraph_Recursive(*Node\Child, Spaces$ + " ", NodeName,ドル *Selected, #True)ElseDebugRadixGraph_Recursive(*Node\Child, Spaces$ + " ", NodeName,ドル *Selected, InSelection)EndIf*Node = *Node\NextWendEndProcedure; Go here to visualize the graph: https://dreampuf.github.io/GraphvizOnlineProcedure DebugRadixGraph(*Tree.RadixTree, *Selected.RadixNode = #Null)Debug "digraph G {"Debug ~" root [label=\"\"]"DebugRadixGraph_Recursive(*Tree\Node, " ", "root", *Selected, #False)Debug "}"EndProcedureXIncludeFile "ConstantsData.pbi"NewList Names.s()Restore BasicFunctionConstantsRepeatRead$ x$If x$ = ""BreakEndIfAddElement(Names())Names() = StringField(x,ドル 1, ",")ForEver; Test random insertionsRandomSeed(123)RandomizeList(Names())Define Tree.RadixTree; Insert all function names; ForEach Names(); RadixInsert(Tree, Names(), PeekI(@Names())) ; store the actual name as the node value; Next Names(); Insert just a few namesResetList(Names())For i = 1 To 25NextElement(Names())RadixInsert(Tree, Names(), PeekI(@Names()))Next i; Test lookup/enumerationNewList *Found();RadixEnumerateAll(Tree, *Found())RadixEnumeratePrefix(Tree, "de", *Found())ForEach *Found()Debug PeekS(*Found())NextRadixFindRange(Tree, "de", @*First, @*Last)Debug "First: " + PeekS(*First) + " Last: " + PeekS(*Last); Visualize the graph;DebugRadixGraph(Tree, RadixFindPrefix(Tree, "de"))CompilerEndIf
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。