1 value example 1 value extension1 1 value extension2 1 value norecurse 0 [IF] An efficient way to arrange a searchable binary tree that does not need to be added to or rebalanced often. Not good for trees that change often. First, an easy example. Say you have six items you want to arrange into a searchable tree. We'll number them 1 through 6. We put the items or pointers to the items into an array. The zero'th item will be the number of items, 6. Then we have the middle item, 4. Then the middle item of the first three, 2, followed by the middle item of the second three, 6. Etc. Here's the array: 6 4 2 6 1 3 5
values indices 0 holds 6, the number of items.
4 1
/ \ / \
/ \ / \
2 6 2 3
/ \ / / \ / \
1 3 5 4 5 6 (7) index 7 is out of bounds
Very simple to find the next lowest node in the array. Double the index to get the left item. Double the index and add one to get the right item. But the tree must be rebalanced any time you change it. You can make many changes and then rebalance once, but you can't make one change and use it before you rebalance. First is to arange an array in the correct order. If we can express the order so that we can easily do tree-address arithmetic then any sort routine will order the items. But I found that approach painful. The sort routine must be written to let you change the order of array indices as you prefer. Easier to first create the array, then build a scratch array that can be deleted later, and sort the items into the scratch array. Then copy them into the final array in the correct order. So, n sorted items starting at address a1, move them to the array starting at address a2. [THEN] extension1 [IF] \ from Speuler http://forthfreak.net/index.cgi?IsolateBits \ : lowbit ( u1 -- u2 ) dup negate and ; : lowbit- ( u1 -- u2 ) dup 1- and ; : highbit ( u1 -- u2 ) begin dup lowbit- ?dup while nip repeat ; [THEN] 0 [IF] With a routine that will find the leftmost node that's to the right of a given node, we can place them easily. First check whether you can go down and right. After you go down and right once, then go down-and-left until you run out of bounds. If you can't go down and right, go up. [THEN]
norecurse [IF]
: down-and-left ( bound n -- bound n' )
begin
2* 2dup <
until
1 rshift ;
: down-and-right ( bound n -- bound n' )
2* 1+ 2dup < if 1 rshift then ;
: up-tree ( n -- n' )
begin
dup 1 and while
1 rshift
repeat
1 rshift ;
: next-node ( bound n -- bound n' )
2dup down-and-right
nip tuck = if up-tree exit then
down-and-left ;
: fill-barray ( start-array bound tree-array -- )
2dup ! -rot \ tree-array start-array bound
dup highbit \ ta sa b low-node
over 0 do
2over i cells + @ swap \ ta sa b n value ta
2over nip cells + ! \ ta sa b n
next-node \ ta sa b n'
loop 2drop 2drop ;
[THEN]
0 [IF]
Here is an alternate method, provided by Gerry Jackson. It uses a routine that computes the address of the root of the tree, and then recursively computes the root of each subtree. [THEN]
norecurse 0= [IF]
: get-root ( n -- root )
dup highbit tuck 2/ - 1+ min ;
: rfill-tree ( src dest idx n -- )
?dup
if
dup get-root
{ src dest idx n root }
src root cells + @ dest idx cells + ! \ Copy to tree array
src dest idx 2* root 1- recurse \ left
src root cells + dest idx 2* 1+ n root - recurse \ right
else
drop 2drop
then ;
: fill-tree ( src n dest -- )
2dup ! 1 rot
rfill-tree ;
[THEN]
0 [IF]
To search the tree, we loop: At each level, first check that the index is not too large. ( < 7 in our example ) If it is too large, return an index that's out of bounds. Then check whether the value at the index matches the test value. If so, return the index. If not, compute the next index and search the next level. For generality the routine accepts an xt for the comparison. This gives us an extra stack item to juggle. The xt needs the stack result ( test-value tree-value – -1|0|1 ) If the test value is "larger" return -1, if the tree value is "larger" return 1. ) The test value should be the first input, so we can pile the search terms on top. [THEN]
extension2 [IF]
: 3dup ( a b c -- a b c a b c )
>r 2dup r@ -rot r> ;
: 4dup ( a b c d -- a b c d a b c d )
2over 2over ;
[THEN]
1 [IF]
: next-n ( -1|1 n -- 2n|2n+1 )
2* swap 2/ - ;
: get-addr ( addr n -- addr' )
cells + ;
\ xt compares two items ( a b -- -1|0|1) -1 if a<b
\ search-tree returns 0 if value is out of range
: search-tree ( value start-addr xt -- n' )
>r 1 begin
over @ over > while
3dup get-addr @ r@ execute tuck while
next-n
repeat
nip
then
rdrop nip nip ;
[THEN]
0 [IF]
Here are a couple of other ways to do the same thing that might be more readable or more efficient on your system. Note that one of them needs its inputs in a different order. [THEN]
0 [IF]
\ tail-recursive approach
\ xt compares two items ( a b -- -1|0|1) -1 if a<b
\ search-tree returns large number if value is out of range
: rsearch-tree ( value xt start-addr n -- n' )
over @ over > 0= if
0= nip nip nip exit then
4dup get-addr @ swap execute tuck 0= if
nip nip nip nip exit then
next-n recurse ;
[THEN]
0 [IF]
\ locals approach
\ xt compares two items ( a b -- -1|0|1) -1 if a<b
\ search-tree returns large number if value is out of range
: lsearch-tree { value start-addr comparer -- n' }
start-addr @ { end-n }
1 begin
dup end-n < while
start-addr over get-addr @ value swap comparer execute
tuck while
next-n
repeat
nip
then ;
[THEN]
0 [IF]
Here is a simple example and some simple testing code. [THEN] example [IF] norecurse [IF] \ sample array. create tree 32 cells allot : filltest 32 1 do i , loop ; create test filltest test 27 tree fill-barray \ debug aid, make trees and eyeball them : cc ( n -- ) tree 32 cells 0 fill test swap tree fill-barray tree 32 cells dump ; [THEN] norecurse 0= [IF] : filltest 666 , 32 1 do i , loop ; create test filltest test 27 tree fill-tree \ debug aid, make trees and eyeball them : x ( n -- ) tree 32 cells 0 fill test swap tree fill-tree tree 32 cells dump ; [THEN] \ sample compare operation to use for sample tree : comp ( a b -- -1|0|1 ) 2dup < 1 and -rot > or ; CR 9 tree ' comp search-tree cells tree + @ . .( should be 9 ) \ CR 9 ' comp tree 1 rsearch-tree cells tree + @ . .( should be 9 ) \ CR 9 tree ' comp lsearch-tree cells tree + @ . .( should be 9 ) : test 28 1 do i tree ['] comp search-tree cells tree + @ i - if i . then loop ; [THEN] 0 [IF] Copyright etc This code is public domain. Anyone is welcome to use it for any purpose. Use entirely at your own risk. If someone has copyrighted or patented anything in it, I do not yet know they have done so. The techniques involved are all described or at least hinted at in Knuth's third volume. [THEN] |