Skip to content

Conversation

@mitchellirvin
Copy link
Contributor

In a nutshell: Implemented a BSTIterator that iterates in-order, given a root node. README.md in /binarysearchtree discusses the trivial solution, and an optimal solution (the one implemented in this PR) in depth.

Included comprehensive unit and integration tests. Refactored file structure to be friendly to future contributors with iterators of other data structures. Added JUnitPlatform to enable running test suite across all iterator implementations. Added README to /binarysearchtree to document what it does and how it works.

This is my first PR against an open source project, would love feedback!

… integration tests. Refactored file structure to be friendly to future contributors with iterators of more data structures. Added JUnitPlatform to enable running test suite across all iterator implementations. Added README to /binarysearchtree to document what it does and how it works.
@mitchellirvin mitchellirvin changed the title #778 Implemented BSTIterator. #778: Binary Search Tree Iterator Aug 5, 2018
@npathai npathai self-requested a review August 8, 2018 18:30
@npathai
Copy link
Contributor

npathai commented Aug 14, 2018

@mitchellirvin Thanks for the PR 👍 I have started the review. Will complete it by tomorrow.

@IAmPramod
Copy link

@mitchellirvin this looks great 👍

Copy link
Contributor

@npathai npathai left a comment

Choose a reason for hiding this comment

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

You have my initial review. Let me know once you have made required changes or have any questions.

* expect to retrieve TreeNodes according to the Integer's natural ordering (1, 2, 3...)
* @param <T> This Iterator has been implemented with generic typing to allow traversal of TreeNodes of various types
*/
public class BstIterator<T> implements Iterator {
Copy link
Contributor

Choose a reason for hiding this comment

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

Iterator is used as raw type here, change it to Iterator<TreeNode<T>>

import java.util.ArrayDeque;

/**
* This BstIterator iterates IN order. For example, given a BST with Integer values,
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggestion: The writeup of javadoc should be changed to something like

An in-order implementation of BST iterator.

/**
* This BstIterator iterates IN order. For example, given a BST with Integer values,
* expect to retrieve TreeNodes according to the Integer's natural ordering (1, 2, 3...)
* @param <T> This Iterator has been implemented with generic typing to allow traversal of TreeNodes of various types
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggestion:

This Iterator has been implemented with generic typing to allow traversal of TreeNodes of various types

I guess the statement should be "it allows TreeNodes with different value types." Or something on that line.

@Override
public TreeNode<T> next() throws IllegalStateException {
if (pathStack.isEmpty()) {
throw new IllegalStateException();
Copy link
Contributor

Choose a reason for hiding this comment

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

Change the exception to NoSuchElementException

*/
public TreeNode(T val) {
this.val = val;
this.left = null;
Copy link
Contributor

Choose a reason for hiding this comment

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

No need to explicitly initialize class properties to null.

@BeforeAll
void createTrees() {
nonEmptyRoot = new TreeNode(Integer.valueOf(5));
nonEmptyRoot.setLeft(new TreeNode(3));
Copy link
Contributor

Choose a reason for hiding this comment

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

There are lot of raw type warnings wherever new object of TreeNode is being created, it is because of raw types. TreeNode is generic but it is being used in raw manner. So change creation of TreeNode to new TreeNode<>(val)

void nextAndHasNextAcrossEntirePopulatedTree() {
BstIterator iter = new BstIterator(nonEmptyRoot);
assertTrue(iter.hasNext(), "Iterator hasNext() should be true.");
assertEquals(iter.next().getVal(), 1, "First Node is 1.");
Copy link
Contributor

Choose a reason for hiding this comment

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

At all places where assertEquals is being used the order of arguments is incorrect. assertEquals(expected, actual) should be the order of arguments

public class TreeNodeTest<T> {

@Test
void treeNodeGetters() {
Copy link
Contributor

Choose a reason for hiding this comment

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

TreeNodeTest should not be part of unit testing. The setters and getters are being tested indirectly by BSTIteratorTest already.
Testing beans is not a good practice. Yes there are exceptions to this, if beans have validation logic then it makes sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Appreciate the feedback! Wasn't sure if I needed this to ensure code coverage or not.

This means that given a binary search tree like the following, the iterator would
return values in order: 1, 3, 4, 6, 7, 8, 10, 13, 14.

![BST](../../../../../../../etc/bst.png "Binary Search Tree")
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we reduce the image size a bit? Its quiet big

* access the container's elements. The Iterator pattern decouples algorithms from containers.
* <p>
* In this example the Iterator ({@link ItemIterator}) adds abstraction layer on top of a collection
* In this example the Iterator ({@link Iterator}) adds abstraction layer on top of a collection
Copy link
Contributor

Choose a reason for hiding this comment

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

While investigating this change I found out that the current TreasureChestIterator implementation is incorrect. It is making assumptions about the underlying datastructure being ArrayList. And if it was changed to LinkedList, then iterator would perform really badly, which violates the whole purpose of iterator. So will have to investigate how to rectify that.

@npathai
Copy link
Contributor

npathai commented Aug 15, 2018

@iluwatar There are a few things that I needed your help with.

  • How do we represent flavour implementations of same pattern, like in this cast list and bst
  • I guess current implementation of TresureChestIterator is incorrect, because it is making assumption about underlying type to be ArrayList and if it was changed to LinkedList, then iterator will perform badly, which violates the purpose of Iterator pattern. Will need to rectify that. What do you think? I will work on that and raise PR if you agree.

*
* Runs a test suite containing all tests within the com.iluwatar.iterator package
*/
@RunWith(JUnitPlatform.class)
Copy link
Contributor

Choose a reason for hiding this comment

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

How does adding JUnitPlatform dependency help? I haven't switched to using Junit 5 yet, hence the question.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

From the limited experience I have with it, I've found it's a great tool to define and run suites of tests instead of individual test classes one at a time. If you want to run all the tests for the iterator package you can run the AppTest class instead of manually running each *Test class.

Copy link
Contributor

Choose a reason for hiding this comment

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

I am still unsure about this change. I read about JUnitPlatform and I understand that it makes running all test cases simple. But AppTest wasn't meant for that. It tests App class and it's not being run currently.
Here is what we can do, we can revert this change and raise an issue to discuss about having AllTests class in every pattern. Till then I am afraid you will have to revert this change. Let me know what are your thoughts.

@mitchellirvin
Copy link
Contributor Author

@npathai committed the requested changes. left the explicit null assignments in the TreeNode constructor because of "Clean Code"'s preference for the explicit over the implicit.

Copy link
Contributor Author

@mitchellirvin mitchellirvin left a comment

Choose a reason for hiding this comment

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

requested changes made and committed, build passing

/**
*
* @return TreeNode next. The next element according to our in-order traversal of the given BST
* @throws IllegalStateException if this iterator does not have a next element
Copy link
Contributor

Choose a reason for hiding this comment

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

Javadoc needs to be updated accordingly.

*
* Runs a test suite containing all tests within the com.iluwatar.iterator package
*/
@RunWith(JUnitPlatform.class)
Copy link
Contributor

Choose a reason for hiding this comment

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

I am still unsure about this change. I read about JUnitPlatform and I understand that it makes running all test cases simple. But AppTest wasn't meant for that. It tests App class and it's not being run currently.
Here is what we can do, we can revert this change and raise an issue to discuss about having AllTests class in every pattern. Till then I am afraid you will have to revert this change. Let me know what are your thoughts.

@mitchellirvin
Copy link
Contributor Author

@npathai I agree with removing AppTest and JUnitPlatform after further research. I've committed the changes (along with the updated javadoc).

@npathai
Copy link
Contributor

npathai commented Aug 19, 2018

@mitchellirvin Looks good.
Few points:

  • we will need to keep AppTest class for now. Its a pattern that is followed in other patterns, so we should keep it for now. We can surely discuss about these points in a new issue related to JunitPlatform.
  • Also add BST iterator sample in App.java. This class is client code of pattern and serves the purpose of showcasing how pattern should be used. I hope that makes sense.

@mitchellirvin
Copy link
Contributor Author

@npathai Roger that! Should have them in within the next several days.

…e each implementation of the Iterator interface. Removed the redundant ItemIterator interface. Added insert() method to TreeNode class to allow for more elegant construction of BSTs.
@mitchellirvin
Copy link
Contributor Author

mitchellirvin commented Aug 25, 2018

@npathai Sorry for the delay, ended up doing more refactoring than originally planned. Should be good for a final review. Let me know what you think of the changes? Tried hard to make the code clean and readable.

Copy link
Contributor

@npathai npathai left a comment

Choose a reason for hiding this comment

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

A quick little change needed. Otherwise it's 👍

}

ItemIterator iterator(ItemType itemType) {
public TreasureChestItemIterator iterator(ItemType itemType) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Return type should be changed to Iterator

public void testIterator(Item expectedItem) {
final TreasureChest chest = new TreasureChest();
final ItemIterator iterator = chest.iterator(expectedItem.getType());
final TreasureChestItemIterator iterator = chest.iterator(expectedItem.getType());
Copy link
Contributor

Choose a reason for hiding this comment

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

Same change here. You being the client of TreasureChest don't care if the concrete implementation of Iterator is TreasureChestItemIterator or something else. You just need an Iterator. This allows for easier refactoring as you can freely change name of the class and client code will not be affected. I hope that makes sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, crap. Good catch!

@mitchellirvin
Copy link
Contributor Author

@npathai should be good now. Thanks for all your help in this review!

Copy link
Contributor

@npathai npathai left a comment

Choose a reason for hiding this comment

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

I don't know how I missed these changes before. Caught my eye when I saw unchecked cast to Item in test case. Apologies.

private static void demonstrateTreasureChestIteratorForType(ItemType itemType) {
LOGGER.info("------------------------");
LOGGER.info("Item Iterator for ItemType " + itemType + ": ");
Iterator itemIterator = TREASURE_CHEST.iterator(itemType);
Copy link
Contributor

Choose a reason for hiding this comment

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

Raw type: Iterator<Item> should be returned from TREASURE_CHEST.iterator()

}

ItemIterator iterator(ItemType itemType) {
public Iterator iterator(ItemType itemType) {
Copy link
Contributor

Choose a reason for hiding this comment

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

return type should not be raw, it should be Iterator<Item>. Previously this was not needed because there was a dedicated interface called ItemIterator which only worked for Items

*
*/
public class TreasureChestItemIterator implements ItemIterator {
public class TreasureChestItemIterator implements Iterator {
Copy link
Contributor

Choose a reason for hiding this comment

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

Raw type: it should be implements Iterator<Item>

public void testIterator(Item expectedItem) {
final TreasureChest chest = new TreasureChest();
final ItemIterator iterator = chest.iterator(expectedItem.getType());
final Iterator iterator = chest.iterator(expectedItem.getType());
Copy link
Contributor

Choose a reason for hiding this comment

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

Raw type: Iterator<Item>


while (iterator.hasNext()) {
final Item item = iterator.next();
final Item item = (Item) iterator.next();
Copy link
Contributor

Choose a reason for hiding this comment

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

No need for cast now, it was needed because of raw types. It will be removed now.

@npathai npathai merged commit 038befe into iluwatar:master Aug 30, 2018
@npathai
Copy link
Contributor

npathai commented Aug 30, 2018

@mitchellirvin It's done. Thanks 👍

@mitchellirvin mitchellirvin deleted the bst-iterator branch August 30, 2018 11:38
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.

3 participants