Stuart Salzer · Jul 9, 2016 go to post

How is "best" defined here? If you wan't fastest, and shortest, I have two options for you. This following code also works with both locals and globals, and avoids the bug of using $PIECE() to trim off the global/local name which won't work on globals which contain a "(" in their namespace (admittedly unlikely).

This is the fast version:

        ; $$FDQ($NAME(a),$NAME(b))
        ;       Find first different nodes in two trees (or subtrees). Will
        ;       work with locals or globals, except locals of the form % or
        ;       %<digit>. Returns a string containing the two references where
        ;       the first difference separated by "'=". If a node is found in
        ;       one tree that is not present in the other, the missing
        ;       reference is replaced by a question mark ("?"). If both trees
        ;       are the same, an empty string is returned. 
        ;
FDQ(%1,%2)      ;                                                          [10]
        NEW %3,%4,%5,%6,%7,%8,%9,%0,% ;                                    [20]
        SET %3=$DATA(@%1,%5)#10,%4=$DATA(@%2,%6)#10
        QUIT:%3'=%4||(%3&&(%5'=%6)) $S(%3:%1,1:"?")_"'="_$S(%2:b,1:"?") ;  [30]
        SET %7=%1,%8=%2,%3=$QLENGTH(%1),%4=$QLENGTH(%2)
lq      SET %1=$QUERY(@%1,1,%5),%2=$QUERY(@%2,1,%6) ;                      [40]
        SET:%1'=""&&(%7'=$NAME(@%1,%3)) %1="" ;                            [50]
        SET:%2'=""&&(%8'=$NAME(@%2,%4)) %2=""
        QUIT:%1="" $SELECT(%2="":"",1:"?'="_%2) QUIT:%2="" %1_"'=?" ;      [60]
        FOR %=1:1 SET %9=$QS(%1,%3+%),%0=$QS(%2,%4+%) Q:%9'=%0  Q:%9="" ;  [70]
        IF %9="",%0="" GOTO:%5=%6 lq QUIT %1_"'="_%2 ;                     [80]
        QUIT:%9]]%0 "?'="_%2 QUIT %1_"'=?" ;                               [90]
        ; ------------
        ; [10]  %1,%2 Reference to nodes under test.
        ; [20]  %3,%4 Upto [30] used for Do %1,%2 exist (respectively)?
        ;             After [30] used for count of subscripts of %1,%2.
        ;       %5,%6 Values of %1,%2.
        ;       %7,%8 Copies of %1,%2 used to help find end subtree.
        ;       %9,%0 First different subscript of %1,%2.
        ;       %     Loop index for scanning down subscript list.
        ; [30]  Return if the existence of %1 and %2 differ or if either exist
        ;       (doesn't matter which), and the values differ.
        ; [40]  Go to next node on each side (which we know exist).
        ; [50]  Check if we have moved past the end of the subtree.
        ; [60]  If either or both %1,%2 put us at end of subtree, return.
        ; [70]  Find the first different subscript or both will be "".
        ; [80]  If both final subscripts "", subscripts are the same so check
        ;       values, and either return of loop.
        ; [90]  Subscripts don't match, return determine order so we can return
        ;       node that is missing.
 
This version may take 30% longer in my test runs, but is a lot simpler by using recursion:
        ; $$FDR($NAME(a),$NAME(b))
        ;       Find first different nodes in two trees (or subtrees). Will
        ;       work with locals or globals, except locals of the for %, %1,
        ;       %2, %3, or %4. Returns a string containing the two references
        ;       where the first difference separated by "'=". If a node is
        ;       found in one tree that is not present in the other, the missing
        ;       reference is replaced by a question mark ("?"). If both trees
        ;       are the same, an empty string is returned. 
        ;
FDR(%1,%2)      ;                                                          [10]
        NEW %3,%4,% ;                                                      [20]
        SET %3=$DATA(@%1,%5)#10,%4=$DATA(@%2,%6)#10
        QUIT:%3'=%4||(%3&&(%5'=%6)) $S(%3:%1,1:"?")_"'="_$S(%2:b,1:"?") ;  [30]
        SET (%3,%4)=""
lr      SET %3=$ORDER(@%1@(%3)),%4=$ORDER(@%2@(%4)) Q:%3=""&&(%4="") "" ;  [40]
        IF %3=%4 SET %=$$FDR($NA(@%1@(%3)),$NA(@%2@(%4))) G:%="" lr Q % ;  [50]
        QUIT:%3]]%4 "?'="_$NAME(@%2@(%4)) QUIT $NAME(@%1@(%3))_"'=?" ;     [60]
        ; ------------
        ; [10]  %1,%2 Reference to nodes under test.
        ; [20]  %3,%4 Upto [30] used for Do %1,%2 exist (respectively)?
        ;             After [30] Subscripts of %1,%2.
        ;       %     Results of recursive call.
        ; [30]  Return if the existence of %1 and %2 differ or if either exist
        ;       (doesn't matter which), and the values differ.
        ; [40]  Go to next subscript at this level.
        ; [50]  If the subscripts are the same, check the sub-tree
        ;       recursively. Loop or quit, depending upon finding a difference.
        ; [60]  If subscripts differ, there is a missing node. Return the
        ;       missing one.
Stuart Salzer · Jun 30, 2016 go to post

If you are really sensitive as to not defining new local variables, and having them pollute your variable list, use the variable named % (yes, just a percent sign is a valid variable name, and happens to sort first. Here is the simple case:

    WRITE:$DATA(%) !,"%"
    NEW % SET %="%" FOR SET %=$ORDER(@%) QUIT:%="" WRITE !,%

Putting that into a routine requires one more temporary variable. %0 sorts next, then %00, %000, and so on.

VARLIST() ; SRS 2016-06-30, Returns list of local variables.
    IF $DATA(%) ; This places the existence of variable % into $TEST.
    NEW % SET %=$SELECT($TEST:$LISTBUILD("%"),1:"")
    SET:$DATA(%0) %=%_$LISTBUILD("%0")
    NEW %0 SET %0="%0"
    FOR SET %0=$ORDER(@%0) QUIT:%0="" SET %=%_$LISTBUILD(%0)
    QUIT %

Many programmers consider it acceptable, if not desirable, to use variables %, and % followed by any digits for introspection only, and therefore not worry about if % and %digits are predefined, Using %, %1, %2, %3, etc, and starting loops with SET %="%9" or something like that.