Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tree #1169

Merged
merged 16 commits into from
Oct 24, 2023
Merged

Tree #1169

merged 16 commits into from
Oct 24, 2023

Conversation

Licini
Copy link
Contributor

@Licini Licini commented Jul 21, 2023

Adding the tree class. @tomvanmele for serialization strategy, I ended up opting for directed nested dictionaries. This way it uses least amount of memory and not needing any types of uuid referencing. I'm aware the docstrings is not formatted entirely correctly, but besides that let me know how does the class structure itself look like. thx!

What type of change is this?

  • Bug fix in a backwards-compatible manner.
  • New feature in a backwards-compatible manner.
  • Breaking change: bug fix or new feature that involve incompatible API changes.
  • Other (e.g. doc update, configuration, etc)

Checklist

Put an x in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code.

  • I added a line to the CHANGELOG.md file in the Unreleased section under the most fitting heading (e.g. Added, Changed, Removed).
  • I ran all tests on my computer and it's all green (i.e. invoke test).
  • I ran lint on my computer and there are no errors (i.e. invoke lint).
  • I added new functions/classes and made them available on a second-level import, e.g. compas.datastructures.Mesh.
  • I have added tests that prove my fix is effective or that my feature works.
  • I have added necessary documentation (if appropriate)

@Licini Licini marked this pull request as ready for review July 21, 2023 14:55
Copy link
Member

@gonzalocasas gonzalocasas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I quickly glanced over it, I think it's a great idea to add trees in general! Here's a first pass of comments!
Other random comments would include considering whether adding a more sophisticated type of tree would make sense (red-black, for example?)

src/compas/datastructures/tree/tree.py Outdated Show resolved Hide resolved
yield descendant

def traverse(self):
"""Traverse the tree from this node."""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Traverse can be done many ways, is it ok to have a single top level traverse method and no alternative for choice which traversal strategy?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added pre-order and post-order, things like in-order are for binary tree so not included here

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add parameter to the docs and document, return, raise, ...

src/compas/datastructures/tree/tree.py Outdated Show resolved Hide resolved
src/compas/datastructures/tree/tree.py Outdated Show resolved Hide resolved
@Licini
Copy link
Contributor Author

Licini commented Jul 26, 2023

I quickly glanced over it, I think it's a great idea to add trees in general! Here's a first pass of comments! Other random comments would include considering whether adding a more sophisticated type of tree would make sense (red-black, for example?)

Thx for the feedbacks! For more specific type of trees like binary tree, red black tree etc we can add them in the same folder inheriting from this class. For instance for binary tree we'd add left/right property, in-order traverse, for red black tree we add color property and corresponding algorithms, but it is very important that the base tree class does not create any limitation on later more specific implementations, as well as not creating any significant computational overhead due to the way we structure it. The primary use cases we have in mind for now are things like scene objects graph (like spatial hierarchy in ifc files), classes inheritance tree etc, rather high-level stuff.

btw I'm curious to know your opinion on the serialization strategy, is it good the way it is now as nested dicts or should we do flat node list + uuid identifiers?

yield descendant

def traverse(self):
"""Traverse the tree from this node."""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add parameter to the docs and document, return, raise, ...

src/compas/datastructures/tree/tree.py Outdated Show resolved Hide resolved
src/compas/datastructures/tree/tree.py Outdated Show resolved Hide resolved
src/compas/datastructures/tree/tree.py Show resolved Hide resolved
src/compas/datastructures/tree/tree.py Show resolved Hide resolved


class Tree(Datastructure):
"""A tree data structure.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps a short overview of the most important characteristics of the data structure would be useful.

src/compas/datastructures/tree/tree.py Outdated Show resolved Hide resolved
src/compas/datastructures/tree/tree.py Outdated Show resolved Hide resolved
src/compas/datastructures/tree/tree.py Outdated Show resolved Hide resolved
src/compas/datastructures/tree/tree.py Outdated Show resolved Hide resolved
@jf---
Copy link
Contributor

jf--- commented Oct 6, 2023

Greenwashing compas with trees ;)

@Licini
Copy link
Contributor Author

Licini commented Oct 6, 2023

@tomvanmele How does it look now?
Besides the requested changes, one more thing:

  • I changed Tree.nodes attribute to be dynamically inferred. Because maintaining this list separately is a bit error-prone especially when you start to trim branches or merge trees (you would need always traverse the given branch and update the node list every-time you add or remove). The decision comes with a bit computational penalty when you print the tree. But since we are not likely to operate on super huge trees yet, I'd vote for keeping the base class simple, and later have more advanced implementations when performance issue arises.

Copy link
Member

@tomvanmele tomvanmele left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe you should also add a few tests :)

src/compas/datastructures/tree/tree.py Show resolved Hide resolved
src/compas/datastructures/tree/tree.py Outdated Show resolved Hide resolved
src/compas/datastructures/tree/tree.py Outdated Show resolved Hide resolved
Comment on lines 183 to 194
if strategy == "preorder":
yield self
for child in self.children:
for node in child.traverse(strategy):
yield node
elif strategy == "postorder":
for child in self.children:
for node in child.traverse(strategy):
yield node
yield self
else:
raise ValueError("Unknown traversal strategy: {}".format(strategy))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

preorder and postorder is the same as depth-first, breadth-first?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought they are, but just realized I got it confused too... Turned out pre and post order are both subcategory of depth-first, the difference is whether you visit current node first or not. None of them are breath-first. Will make some adjustments here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tomvanmele ok now there are two arguments strategy and order, strategy refers to depth first of breath first, and order is pre or post order (only for depth first). This should better aligns the terminologies.

src/compas/datastructures/tree/tree.py Outdated Show resolved Hide resolved
raise TypeError("The node is not a TreeNode object.")

if node.parent:
raise ValueError("The node is already part of another tree, remove it from that tree first.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can also be THIS tree, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the confusion, I adjusted a bit the sentence here, the idea is that if a node is already added somewhere (a parent link already exists, no matter it is this tree or another), we can ask user to explicitly remove that link first. Another option would be to not care about it and directly overwrite this link, which one you think is better?

The traversal strategy. Options are ``"preorder"`` and ``"postorder"``.
strategy : {"depthfirst", "breadthfirst"}, optional
The traversal strategy.
Default is ``"depthfirst"``.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in my opinion you don't have to mention the default. this is already clear from line 163

----------
strategy : {"depthfirst", "breadthfirst"}, optional
The traversal strategy.
Default is ``"depthfirst"``.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here. no mention of default needed in my opinion

@Licini Licini closed this Oct 9, 2023
@Licini Licini reopened this Oct 9, 2023
@Licini
Copy link
Contributor Author

Licini commented Oct 9, 2023

hey @gonzalocasas could you take another look at the current state of this PR, it needs your approval

@@ -379,11 +377,9 @@ def traverse(self, strategy="depthfirst", order="preorder"):
----------
strategy : {"depthfirst", "breadthfirst"}, optional
The traversal strategy.
Default is ``"depthfirst"``.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there can't be a blank line in parameter lists

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops.... fixed

@@ -168,11 +168,9 @@ def traverse(self, strategy="depthfirst", order="preorder"):
----------
strategy : {"depthfirst", "breadthfirst"}, optional
The traversal strategy.
Default is ``"depthfirst"``.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

@Licini
Copy link
Contributor Author

Licini commented Oct 23, 2023

@gonzalocasas Pin.... : ) Could you approve this so it can be merged?

@tomvanmele tomvanmele merged commit 6ff2f35 into compas-dev:main Oct 24, 2023
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants