Tuples, Sets, and Structures
Variables are good for storing individual chunks of data while arrays and dictionaries are good for storing lists of the same data type. For greater flexibility, Swift also offers additional data structures called tuples and sets.
A tuple lets you store related data in one place that may consist of different data types such as a string and an integer, which could represent a person’s name and an employee ID number. A set is similar to an array or a dictionary in letting you store two or more chunks of data that consists of the same data type, such as strings or integers.
To group related data together, Swift also offers structures. With a structure, you can define the data type you want to group together in any combination, such as two strings and a floating-point value. Structures are a way to group different data types together.
Perhaps the biggest advantage of both tuples and structures is when you combine them with other data structures. Instead of creating an array of integers, you could create an array of tuples or an array of structures. This gives you the flexibility to store different data types together and store multiple copies of similar data.
Using Tuples
Suppose you wanted to store somebody’s name and age. You could create two separate variables like this:
var name : String
var age : Int
name = "Janice Parker"
age = 47
Creating two or more separate variables to store related data can be troublesome because there’s no connection between the two variables to show any relationship. To solve this problem, Swift offers a unique data structure called a tuple.
A tuple can store two or more chunks of data in a single variable where the separate chunks of data can even be completely different data types such as a string and an integer. Declaring a tuple is just like declaring a variable. The main difference is that instead of defining a single data type, you can define multiple data types enclosed in parentheses like this:
var tupleName : (DataType1, DataType2)
Just like declaring variables, you have to declare a unique name for your tuple. Then you have to define how many different chunks of data to hold and their data types. Tuples can hold two or more chunks of data, but the more data a tuple holds, the clumsier it can be to understand and retrieve data.
One way to create a tuple is to declare a tuple name and the data types it can hold. The number of data types listed also defines the number of data chunks the tuple can store. So if you wanted to store a string and an integer in a tuple, you could use the following Swift code:
var person : (String, Int)
Then you could store data in that tuple like this:
person = ("Janice Parker", 47)
When assigning data to a tuple, make sure you have both the proper data types and the correct number of data chunks. So the following would fail because the person tuple expects a string first and then an integer, not an integer first and then a string:
person = (47, "Janice Parker") // This would fail
A second way to define a tuple is to simply give it data and let Swift infer the data type such as:
var person = ("Janice Parker", 47)
If you define a tuple by listing its data types, or if you let Swift infer the data types by the type of data you store, it’s not always clear what each chunk of data represents. To make it easier to identify the different chunks of data, Swift also lets you name your data types such as:
var person : (name: String, age: Int)
or if you want Swift to infer the data types by assigning data directly into the tuple:
var person = (name: "Janice Parker", age: 47)
Named tuples help clarify what each data chunk represents. In this case, the string represents a name and the integer represents an age.
Accessing Data in a Tuple
Once you’ve created a tuple and stored data in it, you’ll need to retrieve that data eventually. Since a tuple contains two or more chunks of data, Swift offers three ways to retrieve the data you want.
First, you can create multiple variables to access the tuple data as follows:
var petInfo = ("Rover", 38, true)
var (dog, number, yesValue) = petInfo
print (dog)
print (number)
print (yesValue)
In the first line of code, petInfo is a tuple. that contains three chunks of data: a string (“Rover”), an integer (38), and a Boolean value (true).
The second line of code creates three variables (dog, number, yesValue) and assigns their values to the corresponding data stored in the petInfo tuple. That means dog stores “Rover,” number stores 38, and yesValue stores true. The print commands simply print this data out to verify that it retrieved information from the tuple.
To access data from a tuple, you must know the order that the tuple stores data. So if you wanted to retrieve a string from the petInfo tuple, you’d have to know that the petInfo tuple stores names as its first chunk of data, a number as its second chunk of data, and a Boolean value as its third chunk of data.
If you don’t want to retrieve all the values stored in a tuple, you can just create a variable to store the data you want and use the underscore character (_) as an empty placeholder to represent each additional value in the tuple that you want to ignore.
That means you still need to know the number of data chunks the tuple holds so you can identify the one chunk of data that you want to retrieve. For example, if a tuple contains three items, you can retrieve the first item like this:
var petInfo = ("Rover", 38, true)
var (pet,_,_) = petInfo
print (pet)
This would store “Rover” in the pet variable so the print command would print “Rover.”.
If you just wanted to retrieve the middle value, you could type the following:
var petInfo = ("Rover", 38, true)
var (_,aValue,_) = petInfo
print (aValue)
This would store the number 38 into the aValue variable and then the print command would print 38.
The underscore characters act as placeholders that identify data in a tuple that you want to ignore. If you omit the underscore characters, Swift won’t know which particular data you want out of a tuple. To retrieve data from a tuple, you must know the number and order of the items that the tuple stores.
A second way to retrieve values from a tuple is to use index numbers where the first element is assigned index number 0, the second element is assigned index number 1, and so on. For example, you might store data in a tuple like this:
var petInfo = ("Rover", 38, true)
print (petInfo.0)
print (petInfo.1)
print (petInfo.2)
The first item in the tuple is assigned index number 0 so the combination of the tuple name (such as petInfo) followed by the index number lets you directly retrieve a specific tuple value. So petInfo.0 retrieves “Rover,” petInfo.1 retrieves 38, and petInfo.2 retrieves true.
A third method for accessing data in a tuple involves using names. To uses this method, you must first assign names to each tuple data. For example, the following Swift code defines two names called “name” and “age”:
var tupleName = (name: "Bridget", age: 31)
Now you can access the tuple data by referencing the tuple name followed by the identifying name for each data like this:
print (tupleName.name) // Prints "Bridget"
print (tupleName.age) // Prints 31
All three methods give you different ways to access data stored in tuples so just use the method you like best. For clarity, naming the specific elements of a tuple makes your code easier to understand but at the sacrifice of forcing you to name your tuple data. The index number method and the multiple variable method may be simpler, but can be less clear and requires you to know the exact order of the data you want to retrieve.
To see how to work with tuples, create a new playground by following these steps:
import Cocoa
var tupleName = (greeting: "Happy Birthday", age: 58, happy: true)
// Method #1
var (message, howOld, mood) = tupleName
print (message)
print (howOld)
print (mood)
var (_,stuff,_) = tupleName
print (stuff)
// Method #2
print (tupleName.0)
print (tupleName.1)
print (tupleName.2)
// Method #3
print (tupleName.greeting)
print (tupleName.age)
print (tupleName.happy)
When you try all three methods for accessing values from a tuple, you can see how they all work alike. Tuples make it easy to group related data together in a single variable so you can easily keep related information organized. Then you can choose to retrieve data from a tuple using one of three different methods as shown in Figure 10-1.
Figure 10-1. Three different ways to retrieve data from a tuple
Using Sets
Arrays and dictionaries are designed to store lists of the same data types such as a list of strings or a list of integers. The difference between arrays and dictionaries is how you retrieve data. Arrays force you to retrieve data by location using index values. Dictionaries let you retrieve data using a key, but you must define a unique key for each chunk of data you store.
Sets represent another way to store lists of identical data types such as strings or floating-point numbers. One speed advantage of a set is determining if something is stored or not. If you store data in an arrays, you’d have to exhaustively search throughout the entire array to determine if an array stores a particular item. If you store data in a dictionary, you have to know the key stored with that value, or you have to exhaustively search through the dictionary’s list of values.
Sets let you quickly determine the following far faster than arrays or dictionaries:
Sets make it easy to compare two different groups of data in ways that arrays and dictionaries can’t do as easily.
Creating a Set
To create a set, you can define the name of the set and the type of data the set can hold like this:
var setName = Set<DataType>()>()
The set name should ideally be descriptive of its contents such as memberSet. The data type can be Int (integer), String (strings), Float or Double (decimal numbers), or even the name of another data structure.
A second way to create a set to define it contents within square brackets and let Swift infer the data type. All data must be of the same data type such as:
var setName = Set ([Data1, Data2, Data2 ... DataN])
Notice that if you omit the “Set” keyword and the enclosing parentheses, you’ll define an array, not a set like this:
var thisIsAnArray = [Data1, Data2, Data2 ... DataN]
Adding and Removing Items from a Set
With a set, you can add new items at any time, just as long as the new item is the same data type as the existing data. To add an item to a set, just specify the set name, the insert command, and the data you want to add inside parentheses like this
setName.insert(data)
You must specify the array name to add data to and put the actual data in parentheses. Make sure the data you add is of the proper data type. So if you want to add data to a set that currently holds only integers, you can only add another integer to that set.
If you try to add data that already exists in the set, nothing will happen. That means if a set contains the number 53, you can’t add another copy of 53 into that set.
To remove data from a set, you just specify the set name, the remove command, and the data you want to remove enclosed in parentheses like this:
setName.remove(data)
If you try to remove data that doesn’t exist in the set, then the remove command returns a nil value. However if the remove command succeeds in removing data from a set, it returns an optional variable. So if you remove data from a set and store it in a variable like this:
var variableName = setName.remove(data)
The value stored in the variable can be accessed by using an exclamation mark like this:
print (variableName!)
If you want to remove all items in a set, specify the set name and the removeAll command like this:
setName.removeAll()
To see how to create a set and add and remove data from it, create a new playground by following these steps:
import Cocoa
var setOne = Set([2, 45, -9, 8, -34])
var setTwo = Set<Int>()
setTwo = [34, 90, -83]
setOne.insert(65)
setTwo.insert(-381)
var temp = setOne.remove(45)
print (temp)
print (temp!)
setOne.removeAll()
This Swift code creates a set called setOne by storing data directly and letting Swift infer the data types, which are integers. Then it creates a second set called setTwo, defined as holding only integers (Int).
After creating setTwo, the next line stores three integers in that set. The two insert commands store different integers into both setOne and setTwo. When the code removes the number 45 from setOne, it stores 45 as an optional variable in the temp variable. To access the actual value, you have to unwrap the optional variable using the exclamation mark (!).
Finally, the removeAll command empties setOne so it contains nothing as shown in Figure 10-2.
Figure 10-2. Creating sets, inserting data in a set, and removing data from a set
Querying a Set
Once you have a set, you can use the following commands to get information about that set:
Whatever the result of these commands, they never affect the data stored in a set.
The count command returns an integer value and just requires the set name and the count command like this:
setName.count
You can also assign this value to a variable such as:
var total = setName.count
The isEmpty command returns a Boolean value of true if a set has zero items in it. Otherwise it returns false. Just specify the set name followed by the isEmpty command like this:
setOne.isEmpty
The isSubsetOf command checks if one set contains items that are also contained in a second set. Only if this second set contains every item from the first set is it considered a subset.
The isSupersetOf command checks if one set is larger and contains all the same items as a smaller set. Only if this first set is larger and has all its items stored in the second set is it considered a superset.
The isDisjointWith command compares two sets. If they have no items in common, then this command returns true. Otherwise it returns false.
To see how to query a set, follow these steps:
import Cocoa
var mySet = Set(["Fred", "Cindy", "Jody", "Grant"])
print (mySet.isEmpty)
mySet.count
mySet.removeAll()
print (mySet.isEmpty)
var myNewSet = Set(["John", "Oscar"])
var myOtherSet = Set(["John", "Oscar", "Sally"])
var myThirdSet = Set(["Rick", "Vinny"])
myNewSet.isSubsetOf(myOtherSet)
myOtherSet.isSupersetOf(myNewSet)
myNewSet.isDisjointWith(myThirdSet)
This code creates a set that holds four strings. It uses the isEmpty command to check if the set is empty (false). Then it counts the set (it has 4 items). Finally, it removes all the items from the set and uses the isEmpty command again to check if the set is empty (true).
The next batch of code creates three different sets called myNewSet, myOtherSet, and myThirdSet, which are filled with different names. Then the isSubsetOf command checks if all the items in myNewSet [“John,” “Oscar”]are also in the myOtherSet [“John,” “Oscar,” “Sally”], which is true.
The isSupersetOf command checks if myOtherSet [“John,” “Oscar,” “Sally”] contains more items and also contains all the items stored in myNewSet [“John,” “Oscar”], which is also true.
Finally, the isDisjointWith command checks if myNewSet [“John,” “Oscar”] has nothing in common with myThirdSet [“Rick,” “Vinny”], which is also true as shown in Figure 10-3.
Figure 10-3. Querying a set
Manipulating Sets
When you have two or more sets, you can perform operations on both of sets that creates a third set. Some common set operations include:
Think of the union command like adding two sets together and the subtract command like subtracting one set from another. The union command specifies two set names like this:
firstSet.union(secondSet)
Think of this as equivalent to firstSet + secondSet where all items from both sets get combined into a third set.
The subtract command specifies two set names, but the order makes a difference such as:
thirdSet.subtract(firstSet)
firstSet.subtract(thirdSet)
These two commands can give different results depending on the contents of each set. Suppose thirdSet contains {1, 2, 3, 4, 5} and firstSet contains [1, 3, 5, 7}. The thirdSet.subtract(firstSet) command works like this:
{1, 2, 3, 4, 5} thirdSet
[1, 3, 5, 7} firstSet
Both sets contain 1, 3, and 5 so those numbers get eliminated. Take 1, 3, and 5 out of thirdSet and you’re left with {2, 4}.
The firstSet.subtract(thirdSet) command works like this:
[1, 3, 5, 7} firstSet
{1, 2, 3, 4, 5} thirdSet
Both sets contain 1, 3, and 5 so those numbers get eliminated. Take 1, 3, and 5 out of firstSet and you’re left with {7}.
The intersect command finds items in common between two sets. The exclusiveOr command finds items that are not in both sets. Think of the exclusiveOr command as the opposite of the intersect command.
To see how these four different ways to manipulate sets work, follow these steps:
import Cocoa
var firstSet = Set([1, 3, 5, 7])
var secondSet = Set([2, 4, 6, 8])
var thirdSet = Set ([1, 2, 3, 4, 5])
firstSet.union(secondSet)
secondSet.subtract(firstSet)
firstSet.subtract(secondSet)
firstSet.subtract(thirdSet)
thirdSet.subtract(firstSet)
firstSet.intersect(thirdSet)
firstSet.exclusiveOr(thirdSet)
Notice how the subtract command works differently depending on the order you list the two sets as shown in Figure 10-4.
Figure 10-4. Manipulating sets
Using Structures
Both tuples and sets are built-in features of Swift. Structures are a unique way for you to group different types of data in a single place. With a traditional variable, you can only store one chunk of data at a time. With a structure, you can define two or more chunks of data to store in a single variable. Variables declared inside a structure are called properties.
A structure that stores two chunks of data would look like this:
struct structureName {
var variableName1 : dataType
var variableName2 : dataType
}
Remember, the data type of each variable can be simple data types such as Int, String, or Double, or they can be more complicated data types such as arrays, tuples, sets, or dictionaries.
You can define two or more variables inside a structure where each variable can contain different data types such as a string or an integer. No matter how many properties a structure holds, you must initialize the properties before you can store data in them.
Swift provides three ways to initialize properties. First, you can define an initial value for each property like this:
struct structureName {
var variableName1 : dataType = initialValue
var variableName2 : dataType = initialValue
}
To simplify the property declaration, a second way is to omit the data type and let Swift infer the data type like this:
struct structureName {
var variableName1 = initialValue
var variableName2 = initialValue
}
When you assign an initial value to properties, you can create a variable to represent that structure like this:
var variableName = structureName()
A third way to initialize structure properties is to create an init function. This means you must create a list of property names and their data types. Then assign values to those properties when you create a variable to represent that structure.
For example, suppose you wanted to store information about a person such as his or her name, e-mail address, and phone number. Instead of creating three separate variables, you could create a single structure like this:
struct person {
var name : String
var email : String
var phone : String
}
When you create a variable based on this structure, you need to include initial values such as:
var workers = person(name: "Sue", email: "[email protected]", phone: "555-1234")
Creating a structure involves two parts:
Storing and Retrieving Items from a Structure
A structure lets you define the type of related data to store. Then you need to create a variable name to represent that structure. Finally, to store in a structure, you need to specify the structure name and the variable name separated by a period like this:
structureVariable.variableName = value
To retrieve data from a structure, you can assign a variable to a structure’s variable like this:
var variableName = structureVariable.variableName
To see how to create a structure, initialize its properties, and add and retrieve data, create a new playground by following these steps:
import Cocoa
struct pet {
var name : String = ""
var age : Int = 0
}
var dog = pet()
dog.name = "Fido"
dog.age = 4
print (dog.name)
print (dog.age)
struct person {
var name : String
var email : String
var phone : String
}
var workers = person(name: "Sue", email: "[email protected]", phone: "555-1234")
let boss = workers.name
print (boss)
workers.name = "Bo"
print (workers.email)
print (workers.phone)
The first structure defines initial values for its properties so there’s no need for an init function. That’s why you can create a variable with empty parentheses like this:
var dog = pet()
The second structure does not define initial values for its properties, so when you create a variable to represent that structure, you must define initial values.
var workers = person(name: "Sue", email: "[email protected]", phone: "555-1234")
To store data in a structure, you need to specify the variable name representing the structure followed by the property name as shown in Figure 10-5.
Figure 10-5. Creating a structure and retrieving data
Using Structures in an OS X Program
By themselves, structures can only hold a limited number of data. For more flexibility, structures are often combined with other data structures such as arrays. That way you can create an array of structures, which you’ll use in this sample program. The program will let you type in multiple names, addresses, and phone numbers, then delete data as well.
Figure 10-6. The user interface of the StructureProgram
After you type a name, address, and phone number in the appropriate text fields, the Add button will store this information in a structure, and then store this structure in an array. Each time you add another name, address, and phone number, you’ll be adding another structure to the array while the Total Names text field constantly lists the total number of structures stored in the array.
The View button removes the last item from the array and displays its contents in an alert dialog so you can verify its contents. Then it displays the new total number of structures in the array.
Each text field will need a separate IBOutlet and each push button will need a separate IBAction method, which you’ll need to create by Control-dragging each item from the user interface to your AppDelegate.swift file:
@IBAction func viewData(sender: NSButton) {
}
@IBAction func addData(sender: NSButton) {
}
@IBOutlet weak var window: NSWindow!
@IBOutlet weak var nameField: NSTextField!
@IBOutlet weak var addressField: NSTextField!
@IBOutlet weak var phoneField: NSTextField!
@IBOutlet weak var totalField: NSTextField!
At this point we’ve connected the user interface to our Swift code so we can use the IBOutlets to retrieve and display data on the user interface. We’ve also created the IBAction methods so the push buttons on the user interface will make the program actually work. Now we just need to write Swift code to create an initial dictionary and then write more Swift code in each IBAction method to add, delete, or query the dictionary.
struct person {
var name = ""
var address = ""
var phone = ""
}
var employee = person()
var arrayOfStructures = [person] ()
@IBAction func addData(sender: NSButton) {
employee.name = nameField.stringValue
employee.address = addressField.stringValue
employee.phone = phoneField.stringValue
arrayOfStructures.append(employee)
totalField.integerValue = arrayOfStructures.count
nameField.stringValue = ""
addressField.stringValue = ""
phoneField.stringValue = ""
}
@IBAction func viewData(sender: NSButton) {
var myAlert = NSAlert()
if arrayOfStructures.isEmpty {
myAlert.messageText = "Array is empty"
myAlert.runModal()
} else {
var personData = person()
personData = (arrayOfStructures.removeLast())
totalField.integerValue = arrayOfStructures.count
myAlert.messageText = personData.name + " " + personData.address + " " + personData.phone
myAlert.runModal()
}
}
The code inside the viewData IBAction method creates an alert dialog. Then it checks if the array is empty. If so, it displays “Array is empty” in the alert dialog.
If the array is not empty, then it removes the last structure stored in the array, displays the new total number of array items in the Total Names text field, and displays the removed structure data in the alert dialog.
The complete contents of the AppDelegate.swift file should look like this:
import Cocoa
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
@IBOutlet weak var window: NSWindow!
@IBOutlet weak var nameField: NSTextField!
@IBOutlet weak var addressField: NSTextField!
@IBOutlet weak var phoneField: NSTextField!
@IBOutlet weak var totalField: NSTextField!
struct person {
var name = ""
var address = ""
var phone = ""
}
var employee = person()
var arrayOfStructures = [person] ()
func applicationDidFinishLaunching(aNotification: NSNotification) {
// Insert code here to initialize your application
}
func applicationWillTerminate(aNotification: NSNotification) {
// Insert code here to tear down your application
}
@IBAction func viewData(sender: NSButton) {
var myAlert = NSAlert()
if arrayOfStructures.isEmpty {
myAlert.messageText = "Array is empty"
myAlert.runModal()
} else {
var personData = person()
personData = (arrayOfStructures.removeLast())
totalField.integerValue = arrayOfStructures.count
myAlert.messageText = personData.name + " " + personData.address + " " + personData.phone
myAlert.runModal()
}
}
@IBAction func addData(sender: NSButton) {
employee.name = nameField.stringValue
employee.address = addressField.stringValue
employee.phone = phoneField.stringValue
arrayOfStructures.append(employee)
totalField.integerValue = arrayOfStructures.count
nameField.stringValue = ""
addressField.stringValue = ""
phoneField.stringValue = ""
}
}
To see how this program works, follow these steps:
Figure 10-7. An alert dialog showing the last structure added to the array
Summary
Tuples are a way to store different data types in a single variable. Like arrays and dictionaries, sets can store lists of the same data type. While arrays are best for storing ordered information and dictionaries are best for fast retrieval of data, sets are best for checking if items belong in a set or not as well as making it easy to manipulate two or more sets.
If you need to store large groups of related data, you could use a tuple, but a tuple gets clumsy with large numbers of data. As an alternative, you can create a structure that lets you define different types of data to hold. Beyond the basic data types (Int, String, Float, and Double), structures also let you hold other data structures such as arrays, dictionaries, or sets. For greater flexibility, you can even put structures in arrays, dictionaries, or sets as well.
In the sample OS X program that you created, you learned how to create a structure and store its data in an array. By now you should be getting comfortable using IBOutlets to display or retrieve data from a user interface, along with creating IBAction methods to make your program actually work.
With tuples, sets, and structures along with arrays and dictionaries, you can combine data structures in a variety of ways to store data in the most flexible way for your particular needs.