Skip to content

Conversation

FranticSkipper
Copy link

No description provided.

Copy link

@mateacademy-ai-mentor mateacademy-ai-mentor left a comment

Choose a reason for hiding this comment

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

Review Summary

I'm requesting changes because there's a critical bug in your hashing logic that can cause the application to crash, along with a few other required fixes.

High Priority:

  • Your hash() method's use of Math.abs(key.hashCode()) will return a negative number if the key's hash code is Integer.MIN_VALUE. This will lead to an ArrayIndexOutOfBoundsException. A safer way to ensure a non-negative hash is to use a bitwise AND operation, for example: key.hashCode() & 0x7fffffff.

Other Required Changes:

  • In the resize method, you are recalculating the hash for each node instead of using the hash already stored in node.hash. Please use the existing hash to determine the node's new position.
  • The constructor for the private inner Node class should not be public, as this violates encapsulation principles (Checklist #7).
  • Please consistently use the diamond operator (<>) when creating new Node instances to adhere to modern Java practices (Checklist #8).

You've built a solid structure for the hash map, and with these fixes, it will be much more robust. Keep up the great work!


✨ Please rate this review in the Platform's notification center1. I will be grateful for your feedback! ✨

Important

Fix the issues, commit the changes, and then re-request my review using the Re-request review button2 in the Reviewers tab.

Tip

If you have any comments about this review, please leave a message in the Add a comment section below and mention me with the @mateacademy-ai-mentor tag3.
I will read your message during my next review of your pull request. Please do not reply directly to my comments, as that feature is not supported yet.

Caution

Do not close the pull request until the review is complete. Closing it early can lead to unexpected behavior and disrupt the review process.

Footnotes

  1. Rate AI review example

  2. Re-request AI review example

  3. Add AI review comments example

Node<K, V> currentNode = this.getNode(key);

if (currentNode == null) {
this.elements[position] = new Node<K, V>(hash, key, value, null);

Choose a reason for hiding this comment

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

This violates checklist item #8: 'Diamond operator'. The diamond operator <> should be used here for conciseness. The compiler can infer the generic types from the context.

For example:
this.elements[position] = new Node<>(hash, key, value, null);

currentNode = currentNode.next;
}

lastNode.next = new Node<K, V>(hash, key, value, null);

Choose a reason for hiding this comment

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

This violates checklist item #8: 'Diamond operator'. The diamond operator <> should be used here for conciseness, as the compiler can infer the types.

For example:
lastNode.next = new Node<>(hash, key, value, null);

Comment on lines 77 to 78
int newNodeHash = this.hash(node.key);
int newPosition = newNodeHash % newCap;

Choose a reason for hiding this comment

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

There's an inconsistency and inefficiency in how the hash is handled here.

  1. Inefficiency: You are recalculating the hash for node.key on line 77, but the hash is already stored in node.hash.
  2. Inconsistency: You use the newly calculated newNodeHash to find the newPosition (line 78), but you use the original node.hash when creating the newNode (line 79).

You should not recalculate the hash. Instead, use the hash that is already stored in the node to determine its new position in the resized array.

Consider removing line 77 and changing line 78 to use node.hash.

}

private int hash(K key) {
return key == null ? 0 : Math.abs(key.hashCode());

Choose a reason for hiding this comment

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

This hash function has a subtle but important bug. The hashCode() method can return Integer.MIN_VALUE. When this happens, Math.abs(Integer.MIN_VALUE) also returns Integer.MIN_VALUE, which is a negative number.

While your current implementation with power-of-two capacities doesn't crash (because Integer.MIN_VALUE % powerOfTwo happens to be 0), this is not a robust solution. It causes all keys with this specific hash code to cluster in a single bucket, degrading performance. It would also cause an ArrayIndexOutOfBoundsException if the capacity were not a power of two.

A safer way to ensure a non-negative hash is to use a bitwise AND operation, for example: key.hashCode() & 0x7fffffff.

private V value;
private Node<K, V> next;

public Node(int hash, K key, V value, Node<K, V> node) {

Choose a reason for hiding this comment

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

This violates the encapsulation principle mentioned in checklist items #6 and #7. Since the Node class is private and only used within MyHashMap, its constructor does not need to be public. Changing the access modifier to private or package-private (default) would be more appropriate and improve encapsulation.

Copy link

@mateacademy-ai-mentor mateacademy-ai-mentor left a comment

Choose a reason for hiding this comment

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

Review Summary

Excellent work on this iteration! Your MyHashMap is functionally correct and meets all the core requirements of the task. I'm approving your solution.

While your code works perfectly, there are a couple of opportunities to improve its performance, which are great learning points for building production-quality data structures:

  1. put() method: You can make this method more efficient by avoiding the call to getNode(key). Since you already calculate the position, you can directly access the head of the bucket with this.elements[position] and traverse the list just once to find, update, or add a node.
  2. resize() method: A significant optimization here is to re-link the existing nodes to the new array instead of creating new Node objects for every element. This avoids unnecessary memory allocation and is much faster, especially for large maps.

These are suggestions for improvement, not required changes. You've done a great job fixing the previous issues. Keep up the fantastic progress!


✨ Please rate this review in the Platform's notification center1. I will be grateful for your feedback! ✨

Footnotes

  1. Rate AI review example

public void put(K key, V value) {
int hash = this.hash(key);
int position = hash % this.capacity;
Node<K, V> currentNode = this.getNode(key);

Choose a reason for hiding this comment

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

In the put method, you calculate the hash and position on lines 17 and 18. However, the getNode(key) call on this line recalculates the same values internally.

To avoid this redundant work, you can directly access the head of the bucket using the position you already calculated, like this:
Node<K, V> currentNode = this.elements[position];

Comment on lines +76 to +95
while (node != null) {
int newPosition = node.hash % newCap;
Node<K, V> newNode = new Node<>(node.hash, node.key, node.value, null);

if (newElements[newPosition] == null) {
newElements[newPosition] = newNode;
} else {
Node<K, V> currentNode = newElements[newPosition];
Node<K, V> lastNode = null;

while (currentNode != null) {
lastNode = currentNode;
currentNode = currentNode.next;
}

lastNode.next = newNode;
}

node = node.next;
}

Choose a reason for hiding this comment

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

This implementation of resize correctly rehashes all the elements, but it's inefficient for two main reasons:

  1. Object Creation: A new Node is created for every element in the map (line 78). It's more memory-efficient to move the existing nodes.
  2. List Traversal: To add a node to a new bucket, you traverse the entire linked list in that bucket to find the end (lines 83-89). This can be very slow if collision chains get long.

A much more efficient approach is to prepend the old nodes to the new buckets. This avoids both creating new objects and traversing lists.

Consider this logic inside your while (node != null) loop:

Node<K, V> nextNode = node.next; // Save the next node from the old chain
int newPosition = node.hash % newCap;
node.next = newElements[newPosition]; // Link the current node to the start of the new chain
newElements[newPosition] = node; // The current node becomes the new head
node = nextNode; // Move to the next node in the old chain

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.

3 participants