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

Add Iterators #41

Merged
merged 27 commits into from
Mar 9, 2017
Merged

Add Iterators #41

merged 27 commits into from
Mar 9, 2017

Conversation

iwburns
Copy link
Owner

@iwburns iwburns commented Feb 2, 2017

Fixes #40

@iwburns iwburns changed the title Add Iterators WiP: Add Iterators Feb 2, 2017
@iwburns
Copy link
Owner Author

iwburns commented Feb 2, 2017

Still need to add docs and doc tests for this

@iwburns
Copy link
Owner Author

iwburns commented Feb 2, 2017

@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.

Copy link
Contributor

@Drakulix Drakulix left a 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.


self.node_id = Some(parent_id.clone());

return Some(parent);
Copy link
Contributor

Choose a reason for hiding this comment

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

Spacing looks weird here.

Copy link
Owner Author

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() }
}
Copy link
Contributor

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?

Copy link
Owner Author

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?

Copy link
Contributor

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.

Copy link
Owner Author

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?

Copy link
Contributor

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.

Copy link
Owner Author

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;
}

Copy link
Contributor

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?

Copy link
Owner Author

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.

Copy link
Contributor

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.

Copy link
Owner Author

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).

Copy link
Contributor

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.

@iwburns
Copy link
Owner Author

iwburns commented Feb 2, 2017

Yeah, I agree it seems odd that you have to ask the Tree for these iterators, but I'd have to think through the implications of having Nodes know which Tree they belong to.

@Drakulix
Copy link
Contributor

Drakulix commented Feb 2, 2017

Yeah, I agree it seems odd that you have to ask the Tree for these iterators, but I'd have to think through the implications of having Nodes know which Tree they belong to.

Yes, this would need to be another issue. Just wanted to bring that up, instead of completely ignoring it.

data: Vec::with_capacity(tree.nodes.capacity()),
};

traversal.process_nodes(node_id);
Copy link
Owner Author

@iwburns iwburns Feb 9, 2017

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?

Copy link
Contributor

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.

Copy link
Owner Author

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!

@iwburns
Copy link
Owner Author

iwburns commented Mar 8, 2017

@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 Nodes themselves, but I'm wondering if it would be nice to also have a NodeId version of each of them (just like the child and ancestor iterators).

@Drakulix
Copy link
Contributor

Drakulix commented Mar 8, 2017

Looks good to me, except for that process_node call we already talked about. But I would most likely go the easy route as well, this is difficult to optimize and get right.

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?

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.

Also, all of the traversal iterators currently iterate directly over the Nodes themselves, but I'm wondering if it would be nice to also have a NodeId version of each of them (just like the child and ancestor iterators).

I cannot think of many use cases for this and generally this should be avoided, because you will be dealing with Ids and therefor a bunch of unwraps or excepts again. Having only Node iterators is a feature not a short-coming in my eyes.

@iwburns
Copy link
Owner Author

iwburns commented Mar 8, 2017

Looks good to me, except for that process_node call we already talked about. But I would most likely go the easy route as well, this is difficult to optimize and get right.

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.

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.

It shouldn't be too hard to do, but the implementation will be a naive one (similar to the process_node situation) for now. I'm fine with leaving those as future optimizations so that I can get this functionality out there sooner rather than later.

I cannot think of many use cases for this and generally this should be avoided, because you will be dealing with Ids and therefor a bunch of unwraps or excepts again. Having only Node iterators is a feature not a short-coming in my eyes.

I mostly agree, but I'm worried there are use-cases out there that might require NodeId iterators instead of Node iterators that I'm just not aware of. Maybe that's not something that I should be worrying too much over though.

@Drakulix
Copy link
Contributor

Drakulix commented Mar 8, 2017

I mostly agree, but I'm worried there are use-cases out there that might require NodeId iterators instead of Node iterators that I'm just not aware of. Maybe that's not something that I should be worrying too much over though.

Exactly. In the worst case you get a feature request half a year later.

@iwburns
Copy link
Owner Author

iwburns commented Mar 9, 2017

@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 process_nodes call in LevelOrderTraversal.

@Drakulix
Copy link
Contributor

Drakulix commented Mar 9, 2017

Just took a look over everything. Looks good to me :)

Edit: since this comment I was able to get rid of the process_nodes call in LevelOrderTraversal.

Including this. This makes switching to this Iterator even more valuable for me.

@iwburns
Copy link
Owner Author

iwburns commented Mar 9, 2017

Including this. This makes switching to this Iterator even more valuable for me.

That's great to hear. I also simplified the PreOrderTraversal a bit just now. I'm sorry to keep asking, but if you don't mind I'd love another set of eyes on it. This is the last set of changes that I wanted to make before merging this.

@iwburns iwburns changed the title WiP: Add Iterators Add Iterators Mar 9, 2017
@Drakulix
Copy link
Contributor

Drakulix commented Mar 9, 2017

I also simplified the PreOrderTraversal a bit just now. I'm sorry to keep asking, but if you don't mind I'd love another set of eyes on it.

No worries. Again looks fine. :)

@iwburns iwburns merged commit 02612f1 into master Mar 9, 2017
@iwburns iwburns deleted the iterators branch March 9, 2017 23:42
@Drakulix Drakulix mentioned this pull request Mar 9, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants