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.