GPWiki.org
GPWiki.org
It is currently Wed May 22, 2013 10:30 am

All times are UTC




Post new topic Reply to topic  [ 8 posts ] 
Author Message
PostPosted: Fri Nov 30, 2012 2:31 am 
Lord of Cheesecakes

Joined: Sun Jun 24, 2012 12:49 am
Posts: 335
What do you think?

Code:
#include "stdio.h"
#define null 0

struct Branch
{
   Branch(float testProperty);
   ~Branch();

   void Process();

   Branch* parent;
   Branch* child;
   Branch* sibling;

   float testProperty;
};

Branch::Branch(float testProperty):
   parent(null),
   child(null),
   sibling(null),
   testProperty(testProperty)
{}

void Branch::Process()
{
   printf(">>> %f\n", testProperty);
}

/* Game Programming Wiki, commentation:
   You can even utilize the recursion principle to delete entire hierarchical branches in a very elegant way.
*/

Branch::~Branch()
{
   // Recursive magic!
   // This is capable of deleting all of the nodes under a certain parent node or even an entire tree.
   // It is recursive because similar branch instances are also destructed, which in turn may call even more destructors until every leaf node under the original branch is deleted (free'd from memory)!

   // This is unecessary if the instances are not dynamic. Only do this when you know you're always dealing with dynamic memory (i.e. in an isolated/internal implementation).

#ifdef DynamicDestructors
   delete child;
   delete sibling;

   // Just so you can test to see if it really happens for yourself:
   static int destructCount = 0;
   printf("Destructed %i instances.\n", destructCount++);
#endif
}

void ProcessHierarchyDynamicStack(Branch* treeTrunk)
{
   struct StackLink
   {
      Branch* node;
      StackLink* next;

      StackLink(Branch* node):
         node(node),
         next(null)
      {}
   };

   StackLink* stack = new StackLink(treeTrunk);
   StackLink* top = stack;

   while (stack)
   {
      stack->node->Process();

      if (stack->node->child)
      {
         top->next = new StackLink(stack->node->child);
         top = top->next;
      }

      if (stack->node->sibling)
      {
         top->next = new StackLink(stack->node->sibling);
         top = top->next;
      }

      StackLink* stack_t = stack->next;
      delete stack;
      stack = stack_t;
   }
}

void ProcessHierarchyRecursive(Branch* parent)
{
   // TODO.
}

int main()
{
   Branch tree(0.0f);
   Branch steve(22.0f);
   Branch paul(450.0f);
   Branch toast(3.0f);
   Branch pie(9001.0f);
   Branch berry(-5.0f);

   tree.child = &steve;
   steve.sibling = &paul;
   steve.child = &toast;
   toast.sibling = &pie;
   pie.child = &berry;

   ProcessHierarchyDynamicStack(&tree);

   // Pause at end.
   puts("\nPress <ENTER> to quit program.");
   getchar();

   return 0;
}

_________________
What most people don't understand is what I like to explain as a thing that I understand.


Top
 Profile  
 
PostPosted: Fri Nov 30, 2012 9:48 am 
Bytewise

Joined: Sun Oct 16, 2011 3:09 pm
Posts: 277
Location: Here (where else?)
I would consider this to be known, but apparently I am wrong :)

From a didactic point of view, variables created like in your main(), should not be delete-ed, the example is thus confusing in that respect
(use 'new' in main() to dynamically create the variables).

For clarification of the structure, a picture would be very helpful.


Otherwise: Yeah, it works! :D

_________________
My project: Messing about in FreeRCT, dev blog, and IRC #freerct at oftc.net


Top
 Profile  
 
PostPosted: Fri Nov 30, 2012 4:35 pm 
Lord of Cheesecakes

Joined: Sun Jun 24, 2012 12:49 am
Posts: 335
Quote:
From a didactic point of view, variables created like in your main(), should not be delete-ed, the example is thus confusing in that respect
(use 'new' in main() to dynamically create the variables).

I know... doesn't
#ifdef DynamicDestructors imply that they aren't? Also destructors are automatically called when you leave a scope and the instances are not dynamic, but rather when just introduced like: Type instance; (instead of Type* instance = new Type) -- otherwise you must use the delete operator, which I don't. I don't really understand your problem here. Would you prefer I add a second #ifdef Dynamic in the main loop, where one uses dynamic memory, and the other doesn't?

Quote:
For clarification of the structure, a picture would be very helpful.

Ok! :)
Unfortunately I didn't provide any description with this topic in the first place, sorry about that. I might create a diagram when I'm ready. I wanted more input on the code actually.

Quote:
Otherwise: Yeah, it works! :D

Well, for practical purposes do you think it can be improved, and how? If you have no idea, then that's okay too. :)

Thank you very much for your input. I hope you clarify your point about the delete operator.

_________________
What most people don't understand is what I like to explain as a thing that I understand.


Top
 Profile  
 
PostPosted: Fri Nov 30, 2012 5:41 pm 
Bytewise

Joined: Sun Oct 16, 2011 3:09 pm
Posts: 277
Location: Here (where else?)
Pieman wrote:
Quote:
From a didactic point of view, variables created like in your main(), should not be delete-ed, the example is thus confusing in that respect
(use 'new' in main() to dynamically create the variables).

I know... doesn't
#ifdef DynamicDestructors imply that they aren't?
Sort-of, but if you have to compile without, what's the point of adding the delete code in the first place?

Many of the comments are about how wonderful the recursive delete is, except the code does not actually use it.


Tbh, in a real project I would consider such conditional compilation code way too dangerous to ever include. I consider it too likely that -DDynamicDestructors is added or removed by accident, the compiler will not complain, and during execution it fails horribly in all kinds of interesting ways. It takes several days before you find this problem.

Usually, I design a data structure for exactly one purpose. If I need two cases, I either make it completely separate, or use inheritance to create the variation.


Pieman wrote:
Also destructors are automatically called when you leave a scope and the instances are not dynamic, but rather when just introduced like: Type instance; (instead of Type* instance = new Type) -- otherwise you must use the delete operator, which I don't. I don't really understand your problem here. Would you prefer I add a second #ifdef Dynamic in the main loop, where one uses dynamic memory, and the other doesn't?
What do you actually want to demonstrate here?
If you're aiming for a demonstration of a tree library, making the code more generally usable is a good idea, provided it has some text explaining how to use it.
If you're aiming for an example of recursive delete, I'd drop everything that distracts from the core idea.

The difference (for me, that is) between the two is that a 'tree library' would be used with copy/paste in one's project, while an example is only for making the idea clear, the code itself is not aimed at copy/paste use.


Pieman wrote:
Quote:
For clarification of the structure, a picture would be very helpful.

Ok! :)
Unfortunately I didn't provide any description with this topic in the first place, sorry about that. I might create a diagram when I'm ready. I wanted more input on the code actually.
I have seen too many tree-structures to give useful input probably; I use and manipulate recursive structures just as easily as integers.

The only thing I was wondering about is what type of data you are storing in the tree, ie what is the 'testProperty' about? Perhaps you can use a somewhat more interesting problem to model. The first thing that comes to mind is a decision tree; the parent states which variable to use, and the siblings are value intervals of that variable.

Pieman wrote:
Well, for practical purposes do you think it can be improved, and how? If you have no idea, then that's okay too. :)
Probably, but the direction is not clear as I don't understand what you're trying to explain.
If you're looking for a use-case for tree-like structures, perhaps a scanning or parsing problem would be fun, ie design your own input format for (game) data, or even code-ish (eg read a decision-tree or expression from file to be used in the game).

Quote:
I hope you clarify your point about the delete operator.
I hope so too. If not, please explain what part is not clear or missing.

_________________
My project: Messing about in FreeRCT, dev blog, and IRC #freerct at oftc.net


Top
 Profile  
 
PostPosted: Fri Nov 30, 2012 8:01 pm 
Bibliotherapist
User avatar

Joined: Wed Nov 03, 2004 1:28 pm
Posts: 6714
Location: Oxford, Englandshire
Alberth wrote:
The only thing I was wondering about is what type of data you are storing in the tree, ie what is the 'testProperty' about?


I wondered that too. The code looks fine from a quick skim, but I'm unsure about where this could be used.

_________________
10 PRINT "Bad Monkey ";
20 GOTO 10


Top
 Profile  
 
PostPosted: Fri Nov 30, 2012 8:51 pm 
Lord of Cheesecakes

Joined: Sun Jun 24, 2012 12:49 am
Posts: 335
This is mostly all just for demonstration purposes, though I do want input on efficiency issues and optimization. For potential wiki content, such improvements would come in another swipe, where I would show the original code first and then discuss optimisation in a followup section etc.

Of course, the #ifdef crap shouldn't be used in any real projects, but it's helpful for those who just want to do a quick round of experimentation. I'm going to divide the code into multiple parts if I use it anywhere on the wiki, but this is how it looks in one whole ("nasty") file. It's ideal for simple testing purposes. It's easy & fast to compile for the sake of ensuring its validity.

Quote:
I wondered that too. The code looks fine from a quick skim, but I'm unsure about where this could be used.
Well, useless variables are an ancient ritual to pray to Zeus. :doh No... it's just a demonstration. It isn't for anything except for printing at least *some* content that is hierarchically connected.

Note: I haven't actually written any real explanation details regarding this code yet. Yes, it is intended for the wiki. I am sorry for not clarifying things earlier.

Quote:
What do you actually want to demonstrate here?

A diverse sample of techniques and ideas.

_________________
What most people don't understand is what I like to explain as a thing that I understand.


Top
 Profile  
 
PostPosted: Sat Dec 01, 2012 11:26 am 
Dexterous Droid
User avatar

Joined: Wed Aug 18, 2004 7:40 pm
Posts: 3735
Location: South Africa
I found the following code section kinda horrible to read:
Code:
      if (stack->node->child)
      {
         top->next = new StackLink(stack->node->child);
         top = top->next;
      }

      if (stack->node->sibling)
      {
         top->next = new StackLink(stack->node->sibling);
         top = top->next;
      }

Implementing your stack as a linked list is not the most memory efficient way of doing things. The objects are going to end up somewhere in the heap and I'd guess this would result in a higher cache miss rate. Tree traversal should be done through recursion. If you're going to create your own stack I think a cleaner implementation would be:

Code:
void ProcessHierarchyDynamicStack(Branch* treeTrunk)
{
   std::stack<Branch*> branchStack;

   branchStack.push(treeTrunk);

   Branch* cursor;

   while (!branchStack.empty())
   {
      cursor = branchStack.top();
      branchStack.pop();

      cursor->Process();

      if (cursor->child)
      {
         branchStack.push(cursor->child);
      }

      if (cursor->sibling)
      {
         branchStack.push(cursor->sibling);
      }
   }
}


Also, I'm not sure this data structure is particularly useful. You've called it parent, child and sibling but this actually boils down to a binary tree. What is the point of using this unordered binary tree as apposed to a straight up vector? If you're sure you want the tree structure and know it's always going to be a binary tree you could implement it as a binary heap, which keeps all the data in a nice contiguous block of memory. Once again, I'm thinking this will reduce cache misses.

Leaving the Branch pointers public allows for programmers to do bad things with your functions like
Code:
   tree.child = &steve;
   steve.child = &tree;


You've created a Branch* called parent but you don't assign it or use it anywhere so it's just a null pointer sitting there waiting to cause an exception.

_________________
Whatever the mind can conceive and believe, it can achieve


Top
 Profile  
 
PostPosted: Sat Dec 01, 2012 8:08 pm 
Lord of Cheesecakes

Joined: Sun Jun 24, 2012 12:49 am
Posts: 335
This is exactly what I needed to hear. Thank you for your advice! :yeah

Edit: I'm not sure why I wanted to try managing the stack manually in the first place, but here are some performance results:

Quote:
Dynamic Stack
0.640 s
0.343 s
0.374 s
0.390 s

Recursive
0.178 s
0.125 s
0.140 s
0.156 s

_________________
What most people don't understand is what I like to explain as a thing that I understand.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 8 posts ] 

All times are UTC


Who is online

Users browsing this forum: No registered users and 2 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
Jump to:  
cron
Powered by phpBB® Forum Software © phpBB Group