Hash Sets in Java


Hash Sets are collections of unique items i.e no element can be repeated.

Hash Sets were designed to give us a way of adding and looking for unique values in a collection in a quick manner.

The values in a Hash Set can be either simple primitives like strings or integers as well as more complex object types like object literals or arrays.

In Java, the Hash Set is implemented as a HashSet object.


Creation:

To create a new empty HashSet that holds Strings, you use the following syntax:

Set<String> mySet = new HashSet<>();

Creating an empty Set takes O(1) time.


Constructing from iterable

The Set constructor also accepts an optional iterable object. If you pass an iterable object to the Set constructor, all the unique elements of the object will be added to the new set:

List<String> elements = new ArrayList<>(List.of("b", "c", "c", "d"));
Set<String> mySet = new HashSet<>(elements);

// mySet contains {"b", "c", "d"}

This takes O(n) time, where n is the number of elements in the iterable object.


Adding elements:

To add an element to the set, you use the add() method:

List<String> elements = new ArrayList<>(List.of("b", "c", "c", "d"));
Set<String> mySet = new HashSet<>(elements);

// Adding new elements
mySet.add("x");

// Adding an existing element
mySet.add("d"); // nothing happens

// mySet contains {"b", "c", "d", "x"}

The Set first checks if that element already exists and if so, it does nothing and also doesn't raise an error.

Adding an element to a Set takes O(1) time.


Checking if a value exists:

To check if a Set has a specific element, you use the contains() method which returns true if the set contains the element, otherwise, it returns false.

List<String> elements = new ArrayList<>(List.of("b", "c", "c", "d"));
Set<String> mySet = new HashSet<>(elements);

boolean exists = mySet.contains("c"); // exists is true

exists = mySet.contains("x"); // exists becomes false

Checking if a value exists in a Set takes O(1) time.


Removing elements:

To delete a specified element from a set, you use the remove() method:

List<String> elements = new ArrayList<>(List.of("b", "c", "c", "d"));
Set<String> mySet = new HashSet<>(elements);

// Deleting elements
mySet.remove("c");

// Deleting a non-existent element
mySet.remove("z"); // does nothing

// mySet contains {"b", "d"}

The Set first checks if that key exists and if not, it does nothing and also doesn't raise an error, so it's safe to delete non-existing elements.

Removing an element from a Set takes O(1) time.


Iterating over the Set values:

If we want to iterate over values of the Set, we can use the for loop along with the : operator:

List<String> elements = new ArrayList<>(List.of("b", "c", "c", "d"));
Set<String> mySet = new HashSet<>(elements);

for (String val : mySet) {
    System.out.println(val);
}

// This will print the following:
// b
// c
// d


Iterating over a Set takes O(n) time.


Space Complexity

A Set uses O(n) space, where n is the number of elements existing in the Set.


Assignment
Follow the Coding Tutorial and let's play with some Hash Sets.


Hint
Look at the examples above if you get stuck.


Introduction

In this lesson, we will explore the concept of Hash Sets in Java. Hash Sets are a fundamental data structure that allows us to store unique elements and perform operations like adding, removing, and checking for the existence of elements efficiently. Understanding Hash Sets is crucial for solving problems that require quick lookups and ensuring uniqueness in collections.

Understanding the Basics

A Hash Set is a collection that contains no duplicate elements. It is part of the Java Collections Framework and is implemented as a HashSet object. The primary operations supported by a Hash Set include adding elements, removing elements, and checking if an element exists in the set. These operations are typically performed in constant time, O(1), making Hash Sets highly efficient for these tasks.

Simple Example

Set<String> mySet = new HashSet<>();
mySet.add("apple");
mySet.add("banana");
mySet.add("apple"); // Duplicate, will not be added
System.out.println(mySet); // Output: [apple, banana]

Main Concepts

Let's delve deeper into the key concepts and techniques involved in using Hash Sets.

Creating a Hash Set

To create a Hash Set, you can use the following syntax:

Set<String> mySet = new HashSet<>();

Adding Elements

To add an element to a Hash Set, use the add() method:

mySet.add("apple");
mySet.add("banana");

Checking for Existence

To check if an element exists in a Hash Set, use the contains() method:

boolean exists = mySet.contains("apple"); // true

Removing Elements

To remove an element from a Hash Set, use the remove() method:

mySet.remove("apple");

Examples and Use Cases

Let's look at some examples to understand how Hash Sets can be used in various contexts.

Example 1: Removing Duplicates from a List

List<String> list = Arrays.asList("apple", "banana", "apple", "orange");
Set<String> uniqueSet = new HashSet<>(list);
System.out.println(uniqueSet); // Output: [apple, banana, orange]

Example 2: Checking for Common Elements

Set<String> set1 = new HashSet<>(Arrays.asList("apple", "banana"));
Set<String> set2 = new HashSet<>(Arrays.asList("banana", "orange"));
set1.retainAll(set2);
System.out.println(set1); // Output: [banana]

Common Pitfalls and Best Practices

When working with Hash Sets, it's important to be aware of common pitfalls and follow best practices to write efficient and maintainable code.

Common Pitfalls

Best Practices

Advanced Techniques

Let's explore some advanced techniques related to Hash Sets.

Combining Hash Sets

You can combine two Hash Sets using the addAll() method:

Set<String> set1 = new HashSet<>(Arrays.asList("apple", "banana"));
Set<String> set2 = new HashSet<>(Arrays.asList("banana", "orange"));
set1.addAll(set2);
System.out.println(set1); // Output: [apple, banana, orange]

Using Custom Objects

Hash Sets can also store custom objects. Ensure that your custom objects override equals() and hashCode() methods for correct behavior.

class Person {
    String name;
    int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age && Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

Set<Person> people = new HashSet<>();
people.add(new Person("Alice", 30));
people.add(new Person("Bob", 25));

Code Implementation

Here is a complete example demonstrating the use of Hash Sets in Java:

import java.util.HashSet;
import java.util.Set;

public class HashSetExample {
    public static void main(String[] args) {
        // Creating a HashSet
        Set<String> mySet = new HashSet<>();

        // Adding elements
        mySet.add("apple");
        mySet.add("banana");
        mySet.add("apple"); // Duplicate, will not be added

        // Checking if an element exists
        boolean exists = mySet.contains("apple"); // true

        // Removing an element
        mySet.remove("apple");

        // Iterating over the set
        for (String val : mySet) {
            System.out.println(val);
        }
    }
}

Debugging and Testing

When working with Hash Sets, it's important to write tests to ensure your code behaves as expected. Here are some tips for debugging and testing:

Debugging Tips

Writing Tests

Write unit tests to verify the behavior of your Hash Set operations. Here's an example using JUnit:

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

import java.util.HashSet;
import java.util.Set;

public class HashSetTest {
    @Test
    public void testAdd() {
        Set<String> mySet = new HashSet<>();
        mySet.add("apple");
        assertTrue(mySet.contains("apple"));
    }

    @Test
    public void testRemove() {
        Set<String> mySet = new HashSet<>();
        mySet.add("apple");
        mySet.remove("apple");
        assertFalse(mySet.contains("apple"));
    }
}

Thinking and Problem-Solving Tips

When solving problems related to Hash Sets, consider the following strategies:

Conclusion

In this lesson, we covered the basics and advanced concepts of Hash Sets in Java. We explored how to create, add, remove, and check for elements in a Hash Set. We also discussed common pitfalls, best practices, and advanced techniques. Understanding Hash Sets is essential for efficient data management and quick lookups in programming. Keep practicing and exploring further applications to master this powerful data structure.

Additional Resources

For further reading and practice problems, check out the following resources: