Kotlin Sets
Introduction
Sets are one of the fundamental collection types in Kotlin. A Set is an unordered collection that doesn't allow duplicate elements. This unique characteristic makes sets particularly useful when you need to ensure you're working with distinct elements or when you need to perform mathematical set operations.
In Kotlin, sets come in two main varieties:
- Immutable sets (read-only)
- Mutable sets (modifiable)
Let's dive into the world of Kotlin sets and explore how they can make your code more efficient and expressive.
Creating Sets in Kotlin
Creating an Immutable Set
The standard way to create a read-only set is using the setOf()
function:
val fruitSet = setOf("apple", "banana", "orange", "apple")
println(fruitSet) // Output: [apple, banana, orange]
println(fruitSet.size) // Output: 3
Notice that even though we added "apple" twice, it only appears once in the set. That's because sets automatically eliminate duplicates.
Creating a Mutable Set
When you need to modify a set after creation, use mutableSetOf()
:
val mutableFruitSet = mutableSetOf("apple", "banana", "orange")
mutableFruitSet.add("mango")
mutableFruitSet.remove("banana")
println(mutableFruitSet) // Output: [apple, orange, mango]
Empty Sets
You can create empty sets as well:
val emptyImmutableSet = emptySet<String>()
val emptyMutableSet = mutableSetOf<Int>()
Note the type parameter is required for empty sets to specify what type of elements the set will contain.
Set Implementation Types
Kotlin provides specialized implementations of sets for different use cases:
HashSet
The most commonly used implementation, optimized for fast lookups:
val hashSet = hashSetOf(1, 2, 3, 4)
println(hashSet) // Output: [1, 2, 3, 4] (order may vary)
HashSets offer constant-time performance for basic operations like add, remove, and contains, but don't maintain any specific order.
LinkedHashSet
Preserves the insertion order of elements:
val linkedHashSet = linkedSetOf("first", "second", "third")
println(linkedHashSet) // Output: [first, second, third]
SortedSet (TreeSet)
Keeps elements in their natural order (or using a custom comparator):
val sortedSet = sortedSetOf(5, 2, 3, 1, 4)
println(sortedSet) // Output: [1, 2, 3, 4, 5]
// With custom comparator (descending order)
val descendingSet = sortedSetOf(compareByDescending { it }, 5, 2, 3, 1, 4)
println(descendingSet) // Output: [5, 4, 3, 2, 1]
Basic Set Operations
Checking if an Element Exists
val numbers = setOf(1, 2, 3, 4, 5)
println(3 in numbers) // Output: true
println(numbers.contains(6)) // Output: false
Finding Set Size
val colors = setOf("red", "green", "blue")
println(colors.size) // Output: 3
println(colors.isEmpty()) // Output: false
Iteration
You can iterate through sets just like any collection:
val languages = setOf("Kotlin", "Java", "Python")
for (language in languages) {
println("I like $language")
}
// Output:
// I like Kotlin
// I like Java
// I like Python
Mathematical Set Operations
One of the most powerful features of sets is their built-in support for mathematical set operations.
Union
Combines two sets (all elements from both sets, without duplicates):
val set1 = setOf(1, 2, 3)
val set2 = setOf(3, 4, 5)
val union = set1.union(set2)
println(union) // Output: [1, 2, 3, 4, 5]
Intersection
Finds common elements between two sets:
val set1 = setOf(1, 2, 3, 4)
val set2 = setOf(3, 4, 5, 6)
val intersection = set1.intersect(set2)
println(intersection) // Output: [3, 4]
Difference
Returns elements in the first set that aren't in the second:
val set1 = setOf(1, 2, 3, 4)
val set2 = setOf(3, 4, 5, 6)
val difference = set1.subtract(set2)
println(difference) // Output: [1, 2]
Symmetric Difference
Elements that are in either set, but not in both:
val set1 = setOf(1, 2, 3, 4)
val set2 = setOf(3, 4, 5, 6)
// Symmetric difference can be calculated using union - intersection
val symmetricDiff = set1.union(set2).subtract(set1.intersect(set2))
println(symmetricDiff) // Output: [1, 2, 5, 6]
Practical Examples
Removing Duplicates from a List
One of the most common uses of sets is to eliminate duplicates from a collection:
val userInputs = listOf("apple", "banana", "apple", "orange", "banana", "mango")
val uniqueInputs = userInputs.toSet()
println(uniqueInputs) // Output: [apple, banana, orange, mango]
// If you need a list back:
val uniqueList = userInputs.toSet().toList()
Checking for Unique Elements
fun hasUniqueCharacters(str: String): Boolean {
return str.length == str.toSet().size
}
println(hasUniqueCharacters("hello")) // Output: false (duplicate 'l')
println(hasUniqueCharacters("world")) // Output: true (all characters unique)
Set as a Dictionary Lookup
Sets can be used for efficient lookups:
val validUsers = setOf("alice", "bob", "charlie")
fun isValidUser(username: String): Boolean {
return username.lowercase() in validUsers
}
println(isValidUser("Alice")) // Output: true
println(isValidUser("Dave")) // Output: false
Tracking Visited Elements
Sets are excellent for keeping track of visited nodes in graph algorithms:
fun depthFirstSearch(graph: Map<String, List<String>>, start: String) {
val visited = mutableSetOf<String>()
fun dfs(node: String) {
if (node in visited) return
visited.add(node)
println("Visiting: $node")
for (neighbor in graph[node] ?: emptyList()) {
dfs(neighbor)
}
}
dfs(start)
}
// Example usage
val graph = mapOf(
"A" to listOf("B", "C"),
"B" to listOf("A", "D"),
"C" to listOf("A", "D"),
"D" to listOf("B", "C")
)
depthFirstSearch(graph, "A")
// Output:
// Visiting: A
// Visiting: B
// Visiting: D
// Visiting: C
Performance Considerations
It's important to understand the performance characteristics of different set implementations:
- HashSet: O(1) for add, remove, and contains operations (on average)
- LinkedHashSet: O(1) for add, remove, and contains operations, with additional overhead for maintaining order
- TreeSet: O(log n) for add, remove, and contains operations
Choose the appropriate set implementation based on your specific requirements for ordering, performance, and memory usage.
Summary
Sets in Kotlin offer a powerful way to work with collections of unique elements. They provide:
- Automatic duplicate elimination
- Fast membership testing
- Mathematical set operations
- Multiple implementations with different performance characteristics
When to use sets:
- When you need to ensure uniqueness of elements
- When you need to perform set operations like union, intersection, or difference
- When you need efficient lookups (contains operations)
- When the order of elements isn't important (or when you want a specific ordering)
Exercises
-
Create a function that takes two lists and returns a list of elements that appear in both lists (using sets).
-
Write a program that counts the number of unique words in a text file.
-
Implement a function that determines if two strings are anagrams of each other using sets.
-
Create a simple spell checker that uses a set of known words and suggests corrections for misspelled words.
-
Implement a function that finds all pairs of numbers in a list that sum to a given target value, using a set for efficient lookup.
Additional Resources
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)