GetIndexedObject and unstable indizes

Hi,

I have the following code

Function DoStuff()

    variable numEntries, i
    string str, path

    NewDataFolder/O root:tteest
    NewDataFolder/O root:tteest:a
    NewDataFolder/O root:tteest:ab
    NewDataFolder/O root:tteest:abc
    NewDataFolder/O root:tteest:abcd
    NewDataFolder/O root:tteest:abcde
    NewDataFolder/O root:tteest:abcdef

    DFREF tmpDFR = root:tteest

    numEntries = CountObjectsDFR(tmpDFR, 4)

    for(i = 0; i < numEntries; i += 1)
        str = GetIndexedObjNameDFR(tmpDFR, 4, i)
        path = "root:tteest:" + str
        printf "i=%d, path=%s\r", i, path
        DFREF dfr = $path  
        if(CountObjectsDFR(dfr, 4) == 0) // only kill if empty
            KillDataFolder dfr
        endif
    endfor
End


and got really puzzled today as this does not work as it outputs

  i=0, path=root:tteest:a
  i=1, path=root:tteest:abcdef
  i=2, path=root:tteest:abcd
  i=3, path=root:tteest:
  i=4, path=root:tteest:
  i=5, path=root:tteest:


or alternatively

  i=0, path=root:tteest:abc
  i=1, path=root:tteest:abcde
  i=2, path=root:tteest:abcd
  i=3, path=root:tteest:
  i=4, path=root:tteest:
  i=5, path=root:tteest:


It looks like the higher indizes are invalidated by the KillDataFolder call. Is that how it is supposed to work?
I can of course rework the code, but I would like to understand how CountObjects and friends work first.

Thanks,
Thomas

PS: I've found that with #pragma rtFunctionErrors=1 turned on.
I suspect that this code does what you are trying to do

Function DoStuff()
 
    variable numEntries, i
    string str, path
 
    NewDataFolder/O root:tteest
    NewDataFolder/O root:tteest:a
    NewDataFolder/O root:tteest:ab
    NewDataFolder/O root:tteest:abc
    NewDataFolder/O root:tteest:abcd
    NewDataFolder/O root:tteest:abcde
    NewDataFolder/O root:tteest:abcdef
 
    DFREF tmpDFR = root:tteest
 
    for(i = 0; i < CountObjectsDFR(tmpDFR, 4); i += 1)
        str = GetIndexedObjNameDFR(tmpDFR, 4, i)
        path = "root:tteest:" + str
        printf "i=%d, path=%s\r", i, path
        DFREF dfr = $path  
        if(CountObjectsDFR(dfr, 4) == 0) // only kill if empty
            KillDataFolder dfr
            i -= 1
        endif
    endfor
End
I see one fundamental problem with your code. You assume you can delete objects without affecting the indexes of the other objects. I would suggest using two loops. The first loop would identify the names or datafolder references of the datafolders you want to delete (using indexes). The second loop would then delete the folders (using names, not indexes).
You can still do it with one loop but using a list (inherently shifting the second loop to list searching), like so:

function DoStuff()
 
    NewDataFolder/O root:test
    NewDataFolder/O root:test:a
    NewDataFolder/O root:test:ab
    NewDataFolder/O root:test:abc
    NewDataFolder/O root:test:abcd
    NewDataFolder/O root:test:abcde
    NewDataFolder/O root:test:abcdef

   
    string s_path = "root:test:"
    string s_object
    dfref d_object

    string s_list_objects = stringbykey("FOLDERS", DataFolderDir(1, $s_path) )
    variable v_num_objects = itemsinlist( s_list_objects, "," )
   
    variable i
    for(i = 0; i < v_num_objects; i += 1)
        s_object = stringfromlist(i, s_list_objects, ",")
        d_object = $(s_path + s_object)
       
        printf "i=%d, path=%s\r", i, s_path + s_object
       
        if(CountObjectsDFR(d_object, 4) == 0) // only kill if empty
            KillDataFolder d_object
        endif
    endfor
end


With relevant help found here:  displayhelptopic "DataFolderDir"

Best,
_sk
I agree with Ole (Hej, long time no see!) that the indices are messed up.

Deleting top down however seems to work. Using
    for(i = numEntries-1; i >= 0 ; i -= 1)

yields
  i=5, path=root:tteest:abcdef
  i=4, path=root:tteest:abcde
  i=3, path=root:tteest:abcd
  i=2, path=root:tteest:abc
  i=1, path=root:tteest:ab
  i=0, path=root:tteest:a

as expected.
HJ
Thanks for all your replies!

@aclight: This seems to exploit some implementation detail (linked list IIRC) of the datafolders.

@olelytken: Avoiding the second loop would be nice but seems difficult.

@_sk: Does that really differ performance-wise from getting all folders with Make/T/N=(numEntries) folders = GetIndexedObjNameDFR(tmpDFR, 4, p)? My guess is that DataFolderDir loops also over all folders.

@HJDrescher: Nice idea!

I guess I will be using a precomputed list as olelytken and _sk suggested as this should also work with future Igor Pro versions as it does not exploit an implementation detail.

Thanks again!

Thomas
If you have an aversion to for loops there are other options:

Function Test()
 
    NewDataFolder/O root:tteest
    NewDataFolder/O root:tteest:a
    NewDataFolder/O root:tteest:ab
    NewDataFolder/O root:tteest:abc
    NewDataFolder/O root:tteest:abc:xxx
    NewDataFolder/O root:tteest:abcd
    NewDataFolder/O root:tteest:abcde
    NewDataFolder/O root:tteest:abcdef
   
    DFREF MainFolder=root:tteest
   
    //  Counts the number of subfolders in the main folder
    Variable n=CountObjectsDFR(MainFolder, 4)

    //  Creates a list of all subfolders in the main folder
    Make/FREE/O/DF/N=(n) AllFolders=MainFolder:$GetIndexedObjNameDFR(MainFolder, 4, p)
   
    //  Counts the number of subfolders in each subfolder
    Make/FREE/O/N=(n) SubfoldersInSubfolder=CountObjectsDFR(AllFolders[p], 4)
       
    //  Extracts the empty subfolders
    Extract/INDX/FREE/O AllFolders, EmptyFolders, SubfoldersInSubfolder[p]==0
   
    //  Kills all the empty data folders
    EmptyFolders[]=KillDataFolderFunc(AllFolders[EmptyFolders[p]])
end


Function KillDataFolderFunc(EmptyFolder)
DFREF EmptyFolder
    KillDataFolder EmptyFolder
    Return 0
end
Thanks olelytken that is a pretty nice solution!

Just to be clear. With "loops" I meant to say "requiring igor to iterate over all datafolders".
thomas_braun wrote:

@aclight: This seems to exploit some implementation detail (linked list IIRC) of the datafolders.


That's not exploiting any implementation detail. Your original code has two related problems. Outside of the loop, you get the number of children and store that in numEntries. That value is correct before you start the loop, but within the loop you are deleting some of the children without recalculating the value of numEntries. Furthermore, your iterator, i, increases with each trip through the loop, even when you kill one of the child objects.

In situations like this where you may be deleting items from a list of things, I typically iterate in reverse like HJDrescher suggested. However if for some reason you need to iterate forward, then my modifications to your code should work and aren't dependent on the underlying implementation of data folders.

In case it's still not clear, consider the following two functions:
Function DoStuff()
 
    variable numEntries, i
    Make/O/N=(6) ddd = p
 
    numEntries = numpnts(ddd)
 
    for(i = 0; i < numEntries; i += 1)
        printf "%d\r", ddd[i]
        if (1)
            DeletePoints i, 1, ddd
        endif
    endfor
End

Function DoStuffBetter()
 
    variable numEntries, i
    Make/O/N=(6) ddd = p
 
    numEntries = numpnts(ddd)
 
    for(i = 0; i < numpnts(ddd); i += 1)
        printf "%d\r", ddd[i]
        if (1)
            DeletePoints i, 1, ddd
            i -= 1
        endif
    endfor
End


Here's the output you get from them:
•DoStuff()
  0
  2
  4
  5
  5
  5
•DoStuffBetter()
  0
  1
  2
  3
  4
  5


You'll also get a run time error with the first version when i >= 3 because the printf statement's "ddd[p]" will be out of range.