NewBie's Corner Session 28 Various Methods to Traverse a Global

Beginner, Caché

NewBie's Corner Session 28 Various Methods to Traverse a Global

Welcome to NewBie's Corner, a weekly or biweekly post covering basic Caché Material.

Judging from the number of responses to Session 27 Traversing A Global, developers are passionate about their methods. I am not here to judge the merit of the various methods.

Over the next few pages I will demonstrate a number of methods to Traverse a Global. If you don't already have a favorite they may help you pick one.

I will repeat the method from Session 27 just to have all methods in one post.

There may be variations on these methods and perhaps methods I am not aware of. Feel free to chime in and express your opinion, that is how we all learn.

If you use Caché Objects and Methods with Caché SQL you should never have to worry about traversing a Global, that is the best to my way of thinking.

Start()
 Kill ^Trans
 Set ^Trans("Cars","Chevy","Malibu")="4 Speed"
 Set ^Trans("Cars","Ford","Fairlane")="Cheap"
 Set ^Trans("Cars","Ford","Mustang")="Fast"
 Set ^Trans("Cars","Toyota","Camery")="Nice"
 Set ^Trans("Cars","Toyota","Tercel")="Not as Nice"
 ;ZW ^Trans
 Use 0
 
 Write !!,"******************************"
 Write !,"Method: For Structure"
 Write !,"******************************"
 Set Sub1="" For {
                 Set Sub1=$O(^Trans(Sub1)) Q:Sub1=""
                 Write !,"Sub1: ",Sub1
                 Set Sub2="" For {
                                Set Sub2=$O(^Trans(Sub1,Sub2)) Q:Sub2=""
                                Write !," ==> Sub2: ",Sub2
                                Set Sub3="" For {
                                                Set Sub3=$O(^Trans(Sub1,Sub2,Sub3)) Q:Sub3=""
                                                Write !," ====> Sub3: ",Sub3
                                                Set Data=(^Trans(Sub1,Sub2,Sub3))
                                                Write !," ======>"," Data: ",Data,!
                                }
                 }
 }
 
 
 Write !!,"******************************"
 Write !,"Method: For Do Dots"
 Write !,"******************************"
 
 Set Sub1="" For  Do  Q:Sub1=""
 . Set Sub1=$O(^Trans(Sub1)) Q:Sub1=""
 . Write !,"Sub1: ",Sub1
 . Set Sub2="" For  Do  Q:Sub2=""
 . . Set Sub2=$O(^Trans(Sub1,Sub2)) Q:Sub2=""
 . . Write !," ==> Sub2: ",Sub2
 . . Set Sub3="" For  Do  Q:Sub3=""
 . . . Set Sub3=$O(^Trans(Sub1,Sub2,Sub3)) Q:Sub3=""
 . . . Write !," ====> Sub3: ",Sub3
 . . . Set Data=(^Trans(Sub1,Sub2,Sub3))
 . . . Write !," ======>"," Data: ",Data,!

 Write !!,"******************************"
 Write !,"Method: Do While"
 Write !,"******************************"
   Set Sub1=""
   Do {
                                Set Sub1=$O(^Trans(Sub1)) Q:Sub1=""
                                Write !,"Sub1: ",Sub1
                                Set Sub2=""
                                Do {
                                                Set Sub2=$O(^Trans(Sub1,Sub2)) Q:Sub2=""
                                                Write !," ==> Sub2: ",Sub2
                                                Set Sub3=""
                                                Do {
                                                                 Set Sub3=$O(^Trans(Sub1,Sub2,Sub3)) Q:Sub3=""
                                                                 Write !," ====> Sub3: ",Sub3
                                                                 Set Data=(^Trans(Sub1,Sub2,Sub3))
                                                                 Write !," ======>"," Data: ",Data,!
                                                } While Sub3'=""
                                } While Sub2'=""
   } While Sub1'=""
   

 Write !!,"******************************"
 Write !,"Method: While"
 Write !,"******************************"
 Set Sub1=""
 Set Sub1=$O(^Trans(Sub1))
 While Sub1'="" {
                Write !,"Sub1: ",Sub1
                Set Sub2=""
                Set Sub2=$O(^Trans(Sub1,Sub2)) Q:Sub2=""
                While Sub2'="" {
                                Write !," ==> Sub2: ",Sub2
                                Set Sub3=""
                                Set Sub3=$O(^Trans(Sub1,Sub2,Sub3)) Q:Sub3=""
                                While Sub3'="" {
                                                Write !," ====> Sub3: ",Sub3
                                                Set Data=(^Trans(Sub1,Sub2,Sub3))
                                                Write !," ======>"," Data: ",Data,!
                                                Set Sub3=$O(^Trans(Sub1,Sub2,Sub3)) Q:Sub3=""
                                }
                Set Sub2=$O(^Trans(Sub1,Sub2)) Q:Sub2=""
                }
 Set Sub1=$O(^Trans(Sub1))
 }
                               
                               
 Write !!,"******************************"
 Write !,"Method: Levels"
 Write !,"******************************"
 Set Sub1=""
Level1
     Set Sub1=$O(^Trans(Sub1))
     If Sub1="" GoTo EndLevels
     Write !,"Sub1: ",Sub1
     Set Sub2="" Goto Level2
     Goto Level1
Level2
     Set Sub2=$O(^Trans(Sub1,Sub2))
     If Sub2="" Goto Level1
     Write !," ==> Sub2: ",Sub2
     Set Sub3="" Goto Level3 Q:Sub3=""
     Goto Level2
Level3
     Set Sub3=$O(^Trans(Sub1,Sub2,Sub3))
     If Sub3="" Goto Level2
     Write !," ====> Sub3: ",Sub3
     Set Data=(^Trans(Sub1,Sub2,Sub3))
     Write !," ======>"," Data: ",Data,!
     Goto Level3
EndLevels

 Write !!,"******************************"
 Write !,"Method: One Line"
 Write !,"******************************"

 Set Sub1="" For  Set Sub1=$O(^Trans(Sub1)) Q:Sub1=""  Write !,"Sub1: ",Sub1 Set Sub2="" For  Set Sub2=$O(^Trans(Sub1,Sub2)) Q:Sub2=""  Write !," ==> Sub2: ",Sub2 Set Sub3="" For  Set Sub3=$O(^Trans(Sub1,Sub2,Sub3)) Q:Sub3=""  Write !," ====> Sub3: ",Sub3 Set Data=(^Trans(Sub1,Sub2,Sub3)) Write !," ======>"," Data: ",Data,!

 

--Mike Kadow

PaulMikeKadow@gmail.com

If you have a comment, please respond through the InterSystems Developer Community, don't send me private email, unless of course you wish to address me only.

 

  • + 2
  • 0
  • 473
  • 13

Comments

The 'Do While' is in fact identical to the 'For' block structure except it adds a redundant while condition at the end which is never false as it will have already exited this block from the 'Quit:Sub1=""' condition earlier, so you should probably remove this as it just takes the 'For' structure and adds this extra test which will slow the loop down.

A disadvantage of the 'While' structure is first you need to repeat the iteration construct, e.g. 'Set Sub1=$O(^Trans(Sub1))' before entering the loop and at the end. Also if you need to skip an item with say the 'For' structure you can just issue a 'Continue' but with the 'While' structure you have to advance the current subscript value manually before issuing a 'Continue'

In the 'Method Levels' the lines like 'Goto Level1' are not needed as the previous line says 'Set Sub2="" Goto Level2' so it will always be evaluating this 'Goto Level2' and so will never get to the next line. Rather than having this you can simplify to just 'Set Sub2=""' and remove both 'Goto' statements so it will just drop down into Level2 automatically.

Quick comment/Note regarding the statement "A disadvantage of the 'While' structure is first you need to repeat the iteration construct, e.g. 'Set Sub1=$O(^Trans(Sub1))' before entering the loop and at the end." - this is a common perception for many newer Cache Developers.  

My suggestion for new folks would be to use a simplified structure, such as follows:

Set someVariable=""

While(1) {

     Set someVariable=$Order(^MyGlobal(someVariable))

     If (someVariable="") { Quit }

     // Do your processing here

}

The above structure keeps the looping construct at the start of the loop and is only needed once.  Within your processing logic, you can easily skip to the next iteration of the loop (if needed) via use of the Continue command.  Or if you need to exit the while loop, then you can do so via use of the Quit command.

Hope this was helpful!  And Happy Coding!!!

This is then identical to the 'For' loop example except you have a redundant 'If 1' test each time around the loop, so why not just use the 'For' structure in the original post?

Correct :)   I was just offering an alternate example for those developers who like/prefer While loops versus For loops.  From working with a vast array of Cache Developers over the years, I found that looping structures are one of those topics that provide "good discussion/debate" over which is better, faster, more useful, etc.... :)   Personally, I do not have a preference - I use both While loops and For loops. Which one I use is based on the content I am implementing.

Write !!,"******************************"
Write !,"Method: Levels With For Structure"
Write !,"******************************"
Set Sub1=""

Do FLevel1

Do ZOrder

Do EndLevels

Quit
FLevel1
     For  {

           Set Sub1=$O(^Trans(Sub1)) Quit:Sub1=""
           Write !,"Sub1: ",Sub1
            Set Sub2="" Do FLevel2

    }

    Quit
FLevel2
     For  {

             Set Sub2=$O(^Trans(Sub1,Sub2)) Quit:Sub2=""
             Write !," ==> Sub2: ",Sub2
             Set Sub3="" Do FLevel3

     }
     Quit
FLevel3
     For  {

         Set Sub3=$O(^Trans(Sub1,Sub2,Sub3)) Quit:Sub3=""
        Write !," ====> Sub3: ",Sub3
        Set Data=(^Trans(Sub1,Sub2,Sub3))
        Write !," ======>"," Data: ",Data,!
    }

    Quit

ZOrder

    Write !!,"******************************"
    Write !,"Method: ZOrder"
    Write !,"******************************"
    Set Global="^Trans"

     For  set Global=$Zo(@Global) Quit:Global=""  Do

     . Set Indent=""

     . For Sub=1:1:$QL(Global) Do

     . . Write !,Indent,"Sub",Sub,": ",$QS(Global,Sub)

    . . Set:Indent="" Indent=">"

    . . Set Indent="=="_Indent

    . Set Indent="=="_Indent

    . Write !,Indent," Data: ",@Global,!

   Quit

EndLevels

What is the reason to use $Zo when its standard reincarnation - $Query - exists more than 25 years (as to http://71.174.62.16/Demo/AnnoStd)?
Poor newbies... their brains might explode of such amount of methods to do such simple thing.

Alexey,

"Poor newbies... their brains might explode of such amount of methods to do such simple thing."

Well said! 

We do give them a number of choices.

But I think that is a good thing, not a bad thing.

That is how we learn.

Thanks for chiming in.

Mike,
I fully agree with you that newbies should be aware of dotted syntax and some other old idioms that can be met in legacy code, but it seems that they also need some judgment from "oldies": which coding constructions are more or less acceptable and which should be avoided by any means (like usage of $ZOrder and $ZNext functions).

P.S. (offtop) Could we meet each other in Bratislava in 1992?

So, which one should I use? Every newbie would ask.

In terms of readability I like the FOR structure as it is the cleanest one, very easy to view. Both WHILE and DO WHILE are also great but after being rewritten as John Hotalen showed.

Another newbie's question would be: Is there any performance difference?

So I did some simple performance tests with all these traversing methods including a $Query one and working with this same ^Trans global but now populated with 50 million nodes. They all have very similar performance with exception of the FOR DO DOT syntax and $Query (uses indirection) that are much slower (~10%).

 

All that has come before is great stuff, and extremely interesting. However, the Newbie can ignore it all, by using Caché SQL. Let Caché define the necessary Globals by using Classes/Methods and let Caché SQL retrieve your data. This is by far the better and more efficient way and is much more maintainable. 

However, the Newbie can ignore it all, by using Caché SQL

If so, how do you answer the curious Newbie's question: why should I use Caché at all, as a few SQL implementations are available for free nowadays?

Usually those questions were answered like this: Caché provides Unified Data Architecture that allows several access methods to the same data (bla-bla-bla), and the quickest of them is Direct Global Access. If we answer this way, we should teach how to traverse across the globals, so you are doing the very right and useful thing!
There is only one IMHO: semantics can be more difficult to catch than syntax. Whether one writes `while (1) { ... }` or `for { ... }`, it's basically all the same, while using $order or $query changes traverse algorithm a lot, and it seems that this stuff should be discussed in more details.

Alexey Maslov wrote:

"If so, how do you answer the curious Newbie's question: why should I use Caché at all, as a few SQL implementations are available for free nowadays?"

I did not say SQL, I said "Caché SQL", with everything that implies.

 

A lot of the code we have to deal with is like the "One Line" method, but then some of it was written back in the days when there was very little space in the partition for the program, and the source code was interpreted directly, so it had to be kept small. What is different is that while the code to handle the traverse is all kept on one line, which I think makes the scanning method clear, other code for pulling and writing stuff is usually on lower "dotted" lines, like this:

S Sub1="" F  S Sub1=$O(^Trans(Sub1)) Q:Sub1=""  D
.W !,"Sub1: ",Sub1
.S Sub2="" F  S Sub2=$O(^Trans(Sub1,Sub2)) Q:Sub2=""  D
.. ; etc

And yes, it uses the short version of commands (as you did for the Quit, I notice). I'm not saying this is the best style to use, obviously, as things have moved on, but we do still tend to use it when amending the old code.

I think it is useful to be aware of all these variations, as you may come across old code using them. Also, sometimes the array structure can be better or faster than objects and SQL, whether in a global arrays, or in temporary local arrays. Its a very neat and flexible storage system.

Mike