Grails Cookbook - A collection of tutorials and examples

Groovy Set Examples

A Set is a collection of objects where all elements are unique and with no duplicates. The elements are not ordered and there are no repeating values. This is a very important data structure on certain programming scenarios. Below are some examples on how we declare and manipulate Sets in Groovy.

Declare or Create a Set in Groovy

Below are some ways on how we can declare or create a Set data structure.

We can declare the type to be Set just like in Java. Example:

class Test {
	static main(args) {
		Set stringSet = ["apple", "apple", "banana", "banana"]
		println "size = ${stringSet.size()}"
		stringSet.each {
			println it
		}
	}
}

Even when there are duplicates in the given collection, only unique elements will be stored. Hence, the output will be:

size = 2
banana
apple

We can use the toSet() function to convert a List to a Set.

class Test {
	static main(args) {
		def myCollection = ["apple", "apple", "banana", "banana"]
		println "collection size = ${myCollection.size()}"
		def mySet = myCollection.toSet()
		println "set size = ${mySet.size()}"
	}
}

The expected output is:

collection size = 4
set size = 2

We can also use "as Set" to express that we are declaring a Set.

class Test {
	static main(args) {
		def myCollection = ["apple", "apple", "banana", "banana"]
		def myFirstSet = myCollection as Set
		def mySecondSet = ["apple", "apple", "banana", "banana"] as Set
		println "first set size = ${myFirstSet.size()}"
		println "second set size = ${mySecondSet.size()}"
	}
}

Since it is a set, only unique values are retained. Hence the output will be:

first set size = 2
second set size = 2

Below are the specific class implementation of Set created, depending on how we declared the Set.

class Test {
	static main(args) {
		Set myFirstSet = ["apple", "apple", "banana", "banana"]
		def mySecondSet = ["apple", "apple", "banana", "banana"].toSet()
		def myThirdSet = ["apple", "apple", "banana", "banana"] as Set
		println "first set class = ${myFirstSet.getClass()}"
		println "second set class = ${mySecondSet.getClass()}"
		println "third set class = ${myThirdSet.getClass()}"
	}
}

Declaring a variable as type Set or invoking toSet() will make it use HashSet. While using "as Set" uses LinkedHashSet.

first set class = class java.util.HashSet
second set class = class java.util.HashSet
third set class = class java.util.LinkedHashSet

We can use "as" to declare a specific implementation of Set if we want to control the behavior. For example:

class Test {
	static main(args) {
		Set myFirstSet = ["apple", "apple", "banana", "banana"] as LinkedHashSet
		def mySecondSet = ["apple", "apple", "banana", "banana"] as TreeSet
		def myThirdSet = ["apple", "apple", "banana", "banana"] as HashSet
		println "first set class = ${myFirstSet.getClass()}"
		println "second set class = ${mySecondSet.getClass()}"
		println "third set class = ${myThirdSet.getClass()}"
	}
}

And the instances are what we declared it to be as shown by the output:

first set class = class java.util.LinkedHashSet
second set class = class java.util.TreeSet
third set class = class java.util.HashSet

Groovy Set Operations

Below are some operations supported by Set in Groovy

asImmutable() - we can use asImmutable() if we want to force the Set to be an immutable object. This is important for thread safety because it guarantees state of objects can never be changed. Below is an example:
class Test {
	static main(args) {
		def myCollection = ["apple", "banana", "carrots"].toSet().asImmutable()
		println "class: ${myCollection.getClass()}"
		myCollection.add("Grapes")
	}
}

And sice it is immutable, adding items to it will cause an exception:

class: class java.util.Collections$UnmodifiableSet
Caught: java.lang.UnsupportedOperationException
java.lang.UnsupportedOperationException
	at java_util_Set$add.call(Unknown Source)
	at test.Test.main(Test.groovy:9)

asSynchronized() - again another method useful for thread safety concerns:

class Test {
	static main(args) {
		def myCollection = ["apple", "banana", "carrots"].toSet().asSynchronized() 
		println "class: ${myCollection.getClass()}"
	}
}

Internally, it uses SynchronizedSet as the implementation.

class: class java.util.Collections$SynchronizedSet

each - we can use each to iterate over the items of a Set. For example:

class Test {
	static main(args) {
		Set mySet = ["apple", "banana", "carrots"]
		mySet.each { item ->
			println item
		}
	}
}

And the output are the items printed on each line:

banana
apple
carrots

eachWithIndex - this can be used to also iterate over the items in a Set but also provides an index for each item.

class Test {
	static main(args) {
		Set mySet = ["apple", "banana", "carrots"]
		mySet.eachWithIndex{ item, idx ->
			println "Index # ${idx} is: ${item}"
		}
	}
}

Below is the expected output where the index starts from 0.

Index # 0 is: banana
Index # 1 is: apple
Index # 2 is: carrots

findAll - this can be used to filter the items on a list based on some criteria. Below is an example:

class Test {
	static main(args) {
		Set mySet = ["apple", "banana", "carrots"]
		println mySet.findAll {it.contains("s")}
	}
}

And the output is the subset of the list that has an "s" in it's letters:

[carrots]

flatten() - flatten can be used to flatten a list when some of it's elements are also a collection. Here is an example:

class Test {
	static main(args) {
		Set mySet = [["apple", "banana"], "carrots", ["egg", "guava"]]
		println mySet
		println mySet.flatten()
	}
}

And the output is the flattened items in the list of lists.

[[apple, banana], carrots, [egg, guava]]
[apple, banana, carrots, egg, guava]

grep - this can be used to find items in a Set. When used without parameters, it looks for all elements in a Set that are not 0, null, empty String, or empty collection. Example:

class Test {
	static main(args) {
		Set mySet = [10, 20, 0, false, true, '', 'test', [], null, ""]
		println mySet.grep()
	}
}

And the result are the collection of items with no null, 0 or empty String/Collection.

[20, test, 10, true]

We can also use grep to filter by data type. For example:

class Test {
	static main(args) {
		Set mySet = [10, 20, 0, false, true, 'hello', [1, 2], [3, 4], null, "world"]
		println mySet.grep(Number)
		println mySet.grep(String)
		println mySet.grep(Boolean)
		println mySet.grep(Collection)
	}
}

The result is shown below where items for each data type are shown:

[0, 20, 10]
[world, hello]
[false, true]
[[1, 2], [3, 4]]

We can also use grep to match by regular expression. Below is an example of using grep to find item that starts with letter b.

class Test {
	static main(args) {
		Set mySet = ["apple", "banana", "carrots"]
		println mySet.grep(~/b.+/)
	}
}

And the output is:

[banana]

We can also use grep by using any logical expression just like in findall. For example:

class Test {
	static main(args) {
		Set mySet = ["apple", "banana", "carrots"]
		println mySet.grep{it.contains("e")}
	}
}

The result is:

[apple]

intersect - this method can be used to find the intersection between two collections. Here is an example:

class Test {
	static main(args) {
		Set firstSet = [1, 2, 3, 4, 5]
		Set secondSet = [4, 5, 6, 7, 8]
		println firstSet.intersect(secondSet)
	}
}

Since the common items between the two sets are 4 and 5, the output of the code will be:

[4, 5]

leftShift we can use the left shift operator to add items to a Set. Here are two ways on how to use this:

class Test {
	static main(args) {
		Set mySet = [1, 2, 3, 4, 5]
		mySet.leftShift(6)
		mySet << 7
		println mySet
	}
}

Since we added 6 and 7, the output will be:

[1, 2, 3, 4, 5, 6, 7]

minus - we can use minus to subtract elements from a Set that exists on another Collection. Below are two ways of invoking minus:

class Test {
	static main(args) {
		Set mySet = [1, 2, 3, 4, 5]
		println mySet.minus([4, 5])
		println mySet - [1, 2]
	}
}

And the output expected is:

[1, 2, 3]
[3, 4, 5]

plus - we can use plus to combine another collection to our Set. Here are two ways of doing that:

class Test {
	static main(args) {
		Set mySet = [1, 2, 3, 4, 5]
		println mySet.plus([6, 7])
		println mySet + [9, 10]
	}
}

And the output is:

[1, 2, 3, 4, 5, 6, 7]
[1, 2, 3, 4, 5, 9, 10]

split - we can use split to divide a set into two. One set contains those that satisfies the given criteria, while the other contains all that failed. Below is an example:

class Test {
	static main(args) {
		Set mySet = [1000, 2000, 3000, 4000, 5000, 6000]
		println mySet.split{it> 3000}
	}
}

Since the criteria is items greater than 3000, all items that are bigger than 3000 are grouped together, while those that failed are grouped in another collection:

[[4000, 6000, 5000], [2000, 1000, 3000]]