-
Notifications
You must be signed in to change notification settings - Fork 17
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
Add Iterators #41
Add Iterators #41
Conversation
clean up a few use blocks
Still need to add docs and doc tests for this |
@Drakulix if you don't mind I'd love to get your thoughts on my approach here. It's still a WiP, of course, but any thoughts would be appreciated. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks quite good to me!
I am kinda disliking how Tree
get more "crowded" with functions that should belong to Node
. Calling node.anchestors()
would be far more intuitive. I undestand, why this is not possible right now, but I am really thinking, if a Node
should not contain a reference to it's Tree
at some point.
But thats a subject for a new ussue, the changes overall fit the current api and look mostly good. I have added some comments on stuff, that hit my eye.
src/tree/iterators.rs
Outdated
|
||
self.node_id = Some(parent_id.clone()); | ||
|
||
return Some(parent); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Spacing looks weird here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, will fix that shortly.
impl<'a, T> IteratorNew<'a, T, ChildrenIds<'a>> for ChildrenIds<'a> { | ||
fn new(tree: &'a Tree<T>, node_id: NodeId) -> ChildrenIds<'a> { | ||
ChildrenIds { child_ids: tree.get_unsafe(&node_id).children().as_slice().iter() } | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couldn't you just return the Iterator
directly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No particular reason to be honest.
So (if I'm understanding you correctly), basically the other way to do this would be to have
tree.children_ids(...)
just do this:
return self.get_unsafe(...).children().as_slice().iter()
(or whatever the equivalent code that type-checks properly would be) and then not have a ChildrenIds
struct at all, correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep.
What you really want here is this nightly feature: rust-lang/rfcs#1522
But since there is no real reason, this crate should not work on stable, on second thought, it might be better to hide the underlying Iterator, so you can more easily change it, until RFC 1522 has been stabilized.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I see what you're saying here, but I'm not sure I'm following how I would
hide the underlying Iterator, so you can more easily change it, until RFC 1522 has been stabilized.
Do you mind clarifying?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hiding would be essentially what you do now. Not exposing the type but instead wrapping it in your struct.
I basically changed my mind, thinking about what guarantees we want to make in the API.
You basically want to express, that we return an Iterator
, not what kind of Iterator
. Hiding it by wrapping makes it easier to change it later without the need to break the API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see what you mean. Sorry for the confusion, and yes I agree!
trait IteratorNew<'a, T, I> { | ||
fn new(tree: &'a Tree<T>, node_id: NodeId) -> I; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any particular reason, why you have this trait? Any reason not to make these new
functions "normal" struct methods?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I'm not mistaken, this is the only way to make sure that the new(...)
function is available the tree
module, but not have it available any higher up than that.
Essentially I just wanted to be extra safe and not allow the library user to call new(...)
on anything in the iterators
module directly and this is the only pattern I could think of that would provide that behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because the Iterators are in a lower module, I think you would not need to make them public.
But of course, if this is about visibility, it is understandable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll see if I can re-visit this so that I don't have to do the trait workaround. I just don't want them public from the library perspective. Internally, I don't really care where they are (although I was trying to keep them in their own file because the tree module is getting large).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would try if making them non-public "normal" methods does compile and otherwise just revert back to this solution.
Yeah, I agree it seems odd that you have to ask the |
Yes, this would need to be another issue. Just wanted to bring that up, instead of completely ignoring it. |
src/tree/iterators.rs
Outdated
data: Vec::with_capacity(tree.nodes.capacity()), | ||
}; | ||
|
||
traversal.process_nodes(node_id); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Drakulix I'm not 100% happy with this because it basically forces all of the computation to be run upon creating the Iterator, regardless of whether or not the user ends up using the whole iterator.
However, I couldn't come up with a good way to do this incrementally as next()
is called. (I found a way for PreOrder but PostOrder has stumped me). Do you have any thoughts on this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's certainly tricky, but I think not impossible, but maybe not worth the effort.
I guess you would need to store a stack inside the Iterator. Entries would be a node + the current index of its children and then start with index 0 and the root node. Then build up the stack on a next
call until you have the next element and so on. you are done with a node and pop it from the stack, when the index would go out of bounds.
That should be the minimal amount of state required. you could probably also just store the current id + index, but then you would need to back-track via parent()
and find out the child's position and that sounds unnecessary to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, good points. That would probably be the way to go. I'm probably going to leave it for now and create an issue for later. Thanks for taking a look!
@Drakulix I know it's been a while, but I'd like your opinion again if you have the time. I'm mostly happy with this as a v1.1.0 release, but I'm wondering if I should add a Level-Order Traversal as well. It seems like it might be useful enough to include, right? Also, all of the traversal iterators currently iterate directly over the |
Looks good to me, except for that
I even think, I have a use case for this in my window manager. So yes, it is definitely useful, but if it takes too much time now, I would not hold back 1.1.0 for too long and just push that to 1.2.0.
I cannot think of many use cases for this and generally this should be avoided, because you will be dealing with |
Yeah, I'd love to get it right and fast the first time, but I'd rather not deal with it right now (I've let this sit here much longer than I wanted to originally). Plus it should be something that can be optimized later without breaking anything.
It shouldn't be too hard to do, but the implementation will be a naive one (similar to the
I mostly agree, but I'm worried there are use-cases out there that might require |
Exactly. In the worst case you get a feature request half a year later. |
also, tweak testing to be more clear and precise
@Drakulix I've switched things up a bit (nothing too crazy, but I forgot I had some changes on a different machine that cleaned up one of the iterators a bit). I've added the LevelOrderTraversal as well. Do you mind taking a look when you have a chance? Edit: since this comment I was able to get rid of the |
Just took a look over everything. Looks good to me :)
Including this. This makes switching to this Iterator even more valuable for me. |
That's great to hear. I also simplified the |
No worries. Again looks fine. :) |
Fixes #40