Jump to content

Recommended Posts

Posted (edited)

Why I wrote this: the topic "Very simple tree data structure"
asks for exactly the two things this UDF tries to hold together - a tree that stays
simple to use, and one that is actually fast. Nothing here is taken from that code, it
is a separate implementation; I only reused the question. So this post is my answer to
the need posed in that thread, and the topic is where the discussion started.

Tree UDF is a tree data structure for AutoIt 3.3.16+. Every relation - parent, first
child, next sibling, depth, child count, payload - is one integer array read indexed by
the node id. There is no object, no DllCall, no UDT, no recursion in the hot paths, and
one instance per script.

tree_viewer.thumb.png.f97fee97431a2c8d5824db950d948243.png

What it gives you:

- Create a tree, add children to any node, add a whole "home\carol\docs" path in one call
- Read parent, child, sibling, depth, path, child count, subtree size
- Find a node by name without scanning the tree
- Move a subtree, delete a node with three different promises about its children
  (refuse / promote the children / delete the whole subtree)
- Walk in preorder through a callback, validate the whole structure.

32 public functions, every one documented in the file....
 The error codes as named constants ($TREE_E_*).
Remove is not left out: Tree_Remove covers refusing a non-empty node, promoting its
children, or dropping the whole subtree.   

#include "Tree.au3"

Tree_Create("company", 4096)          ; 4096 = how many nodes you expect
Local $iSales = Tree_Add(Tree_Root(), "", "sales")
Tree_Add($iSales, "alice", "alice")
Tree_AddPath("sales\paris\contracts", "\", 1240)

Local $i = Tree_Find("alice")
ConsoleWrite(Tree_Name(Tree_Parent($i)) & " / depth " & Tree_Depth($i) & @CRLF)

Local $aKids[1]
Local $n = Tree_Children($iSales, $aKids)   ; fills a 1-based array

The two things that decide whether it is fast are in the header: declare the capacity you
expect, because growing copies every array, and rebuild the derived preorder index once at
the end of a batch instead of after every change. Tree_IsClean() tells you where you stand.
For loops that touch every node, the $g_aTr* globals are public and documented as the fast
path - around a raw array read, the API call itself is most of the cost.

Honest limits, all of them named in the file and in the PDF: one tree instance per script;
not re-entrant, so a Tree_Walk callback must stay read-only; ids are recycled on delete, so
a stored id is only trustworthy until the next delete; names are case sensitive; payloads
containing a tab or a line break are flattened by the text export.

; Tree_Example - a worked example of the Tree UDF.
#AutoIt3Wrapper_UseX64=n
#include "Tree.au3"

Opt("MustDeclareVars", 1)
Opt("TrayIconHide", 1)

; ==================================================================
; Tree_Example.au3 - the whole public surface in one readable pass.
;   AutoIt3.exe Tree_Example.au3
; Every number printed by section 7 is a measurement, not a claim; see
; Tree_Bench.log for the head-to-head.
; ==================================================================

; The walk callback has to be a top-level function. Returning < 0 stops.
Func _Show($iNode)
    Local $d = $g_aTrDepth[$iNode], $s = "", $k
    For $k = 1 To $d
        $s &= "  "
    Next
    Local $sName = $g_aTrName[$iNode]
    ConsoleWrite($s & $iNode & " " & ($sName <> "" ? $sName : "-") & " = " & $g_aTrData[$iNode] & @CRLF)
    Return 0
EndFunc   ;==>_Show

; --- 1. create and add named children -----------------------------------
Tree_Create("srv")
Local $app = Tree_Add(Tree_Root(), "apache 2.4", "app")
Local $log = Tree_Add(Tree_Root(), "/var/log", "log")
Tree_Add($log, "app.log", "app_log")
Tree_Add($log, "sys.log", "sys_log")
Tree_Add($app, "httpd.conf", "cfg")
ConsoleWrite("1  nodes after build: " & Tree_Count() & @CRLF)

; --- 2. one call for a whole path ---------------------------------------
Local $doc = Tree_AddPath("home\carol\docs", "\", "report.txt")
ConsoleWrite("2  AddPath -> id " & $doc & ", exact=" & @extended & ", depth=" & Tree_Depth($doc) & @CRLF)

; --- 3. lookup by name is a Map read, not a scan ------------------------
Local $f = Tree_Find("app_log")
ConsoleWrite("3  Find(app_log) -> " & $f & ", parent " & Tree_Parent($f) & ", name of parent " & Tree_Name(Tree_Parent($f)) & @CRLF)

; --- 4. children, siblings, ancestry -------------------------------------
; Both Tree_Children and Tree_PathTo fill a 1-based array and return the count.
Local $aKid[1]
Local $nKid = Tree_Children($log, $aKid)
ConsoleWrite("4  log has " & $nKid & " children: ")
For $k = 1 To $nKid
    ConsoleWrite($aKid[$k] & "(" & Tree_Name($aKid[$k]) & ") ")
Next
Local $c = Tree_FirstChild($log)
ConsoleWrite(@CRLF & "   sibling chain: ")
While $c <> 0
    ConsoleWrite($c & " ")
    $c = $g_aTrNext[$c]
WEnd
ConsoleWrite(@CRLF & "   app_log under app? " & Tree_IsAncestor($app, $f) & " / cfg under app? " & Tree_IsAncestor($app, Tree_Find("cfg")) & @CRLF)

; --- 5. the whole tree, preorder, through a callback ---------------------
ConsoleWrite("5  walk:" & @CRLF)
Tree_Walk("_Show", Tree_Root())

; --- 6. move a subtree, then ask about size ------------------------------
Tree_Move($f, $doc)
Tree_Rebuild()
ConsoleWrite("6  after move: docs subtree = " & Tree_SubtreeSize($doc) & " nodes, index clean = " & Tree_IsClean() & @CRLF)
Local $nPath = Tree_PathTo($doc, $aKid)
ConsoleWrite("   path to docs: " & $nPath & " steps, root first = " & $aKid[1] & ", node last = " & $aKid[$nPath] & @CRLF)

; --- 7. the hot loop reads the globals, not the API ----------------------
Local $t = TimerInit(), $iSum = 0, $iN = 200000
For $k = 1 To $iN
    $iSum += $g_aTrParent[1 + Mod($k, $g_iTrCount)]
Next
Local $tRaw = TimerDiff($t)
$t = TimerInit()
For $k = 1 To $iN
    $iSum += Tree_Parent(1 + Mod($k, $g_iTrCount))
Next
Local $tApi = TimerDiff($t)
ConsoleWrite("7  " & $iN & " parent reads: raw arrays " & Round($tRaw, 1) & " ms (" & Int($tRaw * 1000 / $iN) & " us), API " & Round($tApi, 1) & " ms (" & Int($tApi * 1000 / $iN) & " us) - sum " & $iSum & @CRLF)

; --- 8. remove, the lazy name index, and a text round trip ---------------
Local $killed = Tree_Remove($log, 2)
ConsoleWrite("8  removed " & $killed & " nodes, live now " & Tree_Count() & ", stale keys " & Tree_NameStale() & @CRLF)
ConsoleWrite("   Find(log) after delete -> " & Tree_Find("log") & " (error " & @error & ")" & @CRLF)
ConsoleWrite("   CompactNames -> " & Tree_CompactNames() & " live names, validate = " & Tree_Validate() & @CRLF)
Tree_Export(@ScriptDir & "\Tree_Example.tree")
Tree_Create("empty")
Local $nBack = Tree_Import(@ScriptDir & "\Tree_Example.tree")
ConsoleWrite("   import back: " & $nBack & " nodes added to the root, live " & Tree_Count() & ", Find(cfg) = " & Tree_Find("cfg") & @CRLF)

Attached:

 Tree.au3   

 

Tree_Quick_Start.pdf 

 

Tree_viewer_1.0.zip 

 

Edited by Numeric1
From 0 and 1 to the stars, it all starts with binary, Numeric1
 
Posted

Added to the Wiki as well :) 

My UDFs and Tutorials:

Spoiler

UDFs:
Active Directory (NEW 2024-07-28 - Version 1.6.3.0) - Download - General Help & Support - Example Scripts - Wiki
ExcelChart (2017-07-21 - Version 0.4.0.1) - Download - General Help & Support - Example Scripts
OutlookEX (2021-11-16 - Version 1.7.0.0) - Download - General Help & Support - Example Scripts - Wiki
OutlookEX_GUI (2021-04-13 - Version 1.4.0.0) - Download
Outlook Tools (2019-07-22 - Version 0.6.0.0) - Download - General Help & Support - Wiki
PowerPoint (2021-08-31 - Version 1.5.0.0) - Download - General Help & Support - Example Scripts - Wiki
Task Scheduler (2022-07-28 - Version 1.6.0.1) - Download - General Help & Support - Wiki

Standard UDFs:
Excel - Example Scripts - Wiki
Word - Wiki

Tutorials:
ADO - Wiki
WebDriver - Wiki

 

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...