Tracks
/
Swift
Swift
/
Exercises
/
The 1st Rule of Poetry Club
The 1st Rule of Poetry Club

The 1st Rule of Poetry Club

Learning Exercise

Introduction

Importing

While Swift provides rich functionality in its Standard Library, you can access many additional features by importing external libraries and frameworks. These modules may come directly from the Swift project (such as Swift Argument Parser), from Apple platform frameworks (such as Foundation and the Network Framework), or from third-party open-source libraries (such as SwiftyBeaver).

Core modules like Foundation—one of the most widely used libraries in Swift—are included with the Swift toolchain. Third-party libraries must be added to your project configuration (for example, using Swift Package Manager) before they can be imported.

Importing modules is done by writing the import keyword followed by the name of the module. So one can import Foundation by adding the following to their program.

import Foundation

Importing a module makes its types, methods, and functions available to your code. For instance, importing Foundation extends String with useful utility methods like components(separatedBy:).

By convention, import statements are placed at the top of a Swift file so dependencies are immediately visible to anyone reading the code.

String indexing

Individual characters in a String can be accessed using the subscripting method used with arrays. However, the indices used for strings are not integers and can not be worked with directly. Instead they must be computed based off of some other known index, such as startIndex, which points to the position of the first character in a nonempty string, using the methods available in the String and NSString libraries (when the Foundation module is imported, strings in Swift have access to all of the NSString properties and methods).

For example, given the following string:

let csv = "apple,pear,peach,orange,cherry,lime,goosberry"

One cannot write csv[21] to get the "g" in "orange", they must instead compute a value of type String.Index and supply that index instead. Note that these indices are not meant to be human-consumable on their own. They are what is referred to as opaque indices ,as humans need not know what is inside them.

let index = csv.index(csv.startIndex, offsetBy: 21)
csv[index]
// "g"
print(index)
// prints Index(_rawBits: 1376513)

Note, however, that if the offset is not a valid index, i.e. if it would return the index before startIndex or after endIndex the operation will raise an error, crashing the program. To prevent this problem, one can specify a limiting index. This returns an optional index and it will return nil for otherwise invalid indices.

let tooFar = csv.index(csv.startIndex, offsetBy: 200)
// error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0).
let tooFarButSafe = csv.index(csv.startIndex, offsetBy: 200, limitedBy: csv.endIndex)
// nil

Additionally, indices cannot be shared between strings. For example, using the index of the "g" in "orange" computed above to index into the string fam = "This is my family: 👨‍👩‍👦‍👦, this is our dog: 🐶, this is our cat: 🐱!", i.e. fam[index] will crash your program.

There are many reasons for all of this, but they all basically boil down to "Unicode is tricky".

Generally speaking if you need random access to individual characters, you likely want to be using some other data structure, like Array<Character>.

Some useful methods

Swift has the methods first and last to get the first and last character of a string. These methods return an optional character, so if the string is empty, they will return nil.

let str = "Hello, world!"
print(str.first) // Prints Optional("H")
print(str.last)  // Prints Optional("!")

You can also use the prefix and suffix methods to get a substring of a string. These methods take an integer as an argument and return a substring containing the first or last n characters of the string.

let str = "Hello, world!"
print(str.prefix(5)) // Prints "Hello"
print(str.suffix(6)) // Prints "world!"

String methods

Working with text is a common task in programming. Swift—along with the Foundation framework—provides many convenient methods and properties to inspect, transform, and manipulate strings.

Splitting Strings with components(separatedBy:)

To split a string into an array of substrings based on a delimiter, use components(separatedBy:) (requires importing Foundation):

import Foundation

let sentence = "Hello, world!"
let words = sentence.components(separatedBy: " ")
print(words) // Prints ["Hello,", "world!"]

Checking Prefixes and Suffixes with hasPrefix(_:) and hasSuffix(_:)

To check whether a string begins or ends with a specific sequence of characters, use hasPrefix(_:) and hasSuffix(_:):

let greeting = "Hello, world!"
print(greeting.hasPrefix("Hello"))  // Prints true
print(greeting.hasSuffix("world!")) // Prints true

Changing Case

You can transform the casing of a string using these standard methods and properties:

  • lowercased(): Returns a lowercase copy of the string.
  • uppercased(): Returns an uppercase copy of the string.
  • capitalized: Returns a copy where the first letter of each word is capitalized (from Foundation).
let greeting = "hello, world!"
print(greeting.lowercased())  // Prints "hello, world!"
print(greeting.uppercased())  // Prints "HELLO, WORLD!"
print(greeting.capitalized)   // Prints "Hello, World!"

Replacing Substrings with replacingOccurrences(of:with:)

To replace all occurrences of a target substring with another string, use replacingOccurrences(of:with:) (from Foundation):

let greeting = "Hello, world!"
let updated = greeting.replacingOccurrences(of: "world", with: "Swift")
print(updated) // Prints "Hello, Swift!"

Joining Strings with joined(separator:)

The joined(separator:) method belongs to collections of strings, such as [String]. It joins elements together into a single string using a separator:

let words = ["Hello", "world"]
let sentence = words.joined(separator: ", ")
print(sentence) // Prints "Hello, world"

Instructions

A new poetry club has opened in town, and you're thinking of attending. Because there have been incidents in the past, the club has a very specific door policy which you'll need to master, before attempting entry.

There are two doors at the poetry club, both are guarded. In order to gain entry, you'll need to work out the password of that day:

1. Split a string into individual lines

The first thing you need to do is split the poem into individual lines. The guard will recite a poem, and you will have to split it into lines.

Implement the function splitOnNewlines that takes a string and splits it into an array of strings, using the newline character as the delimiter. The function should return an array of strings, where each string is a line from the poem.

splitOnNewlines("Hello.\nHow are you?\n\nI'm doing fine.")
// returns ["Hello." ,"How are you?", "", "I'm doing fine"].

2. Front door

  1. The guard will recite a poem
  2. You will have to split the poem into individual lines.
  3. You will have to get the first letter of each line.
    • If the line is empty, you should use _ instead.
  4. You will have to ensable the letters, which will form a word.

For example, one of their favorite writers is Michael Lockwood, who's written the following acrostic poem, which means that the first letter of each sentence form a word:

Stands so high
Huge hooves too
Impatiently waits for
Reins and harness
Eager to leave

When the guard recites the poem, you will split it into individual lines and respond with the first letters of each line, i.e. ["S", "H", "I", "R", "E"].

The guard will then give you the word formed by the array of letters you replied with for you to put into capitalized word form. Finally the password you return is "SHIRE", and you'll get in.

Implement the function frontDoorPassword that takes a string which represents the poem. The function should return the password that you would give to the guard.

frontDoorPassword("Stands so high\nHuge hooves too\nImpatiently waits for\nReins and harness\nEager to leave")
// returns "SHIRE"

3. Back door

In the back of the club you'll find the most renowned poets, which is like the VIP area. Because this is not for everyone, the back door process is a bit more convoluted.

  1. The guard will recite a poem.
  2. You will have to split the poem into lines.
  3. You will have to get the last letter of each line.
    • The line will sometimes have trailing spaces, so you will need to strip them off.
  4. You will have to assemble the letters and then add ", please" to the end of the word formed by the letters.

For example, the poem mentioned before is also telestich, which means that the last letter of each sentence form a word:

Stands so high
Huge hooves too
Impatiently waits for
Reins and harness
Eager to leave

When the guard recites the poem, you will split it into individual lines, strip off any trailing spaces, and respond with the first letters of each line, i.e. ["h", "o", "r", "s", "e"].

The guard will then give you the word formed by the array of letters you replied with for you to put into capitalized word form and append ", please". Finally the password you return is "horse, please", and you'll get in.

Implement the function backDoorPassword that takes a string which represents the poem. The function should return the password that you would give to the guard.

backDoorPassword("Stands so high\nHuge hooves too\nImpatiently waits for\nReins and harness\nEager to leave")
// returns "horse, please"

4. Secret room

Inside the back room of the club is another door that leads to the secret room that only the very top poets may enter.

  1. The guard will recite a poem.
  2. You will have to split the poem into lines.
  3. You will have to get the ith letter of the ith line.
  4. You will have to assemble the letters and then add "!" to the end of the word formed by the letters.
  5. You will have to put the word in uppercased form.

For example, a modified version of the poem mentioned before fits this pattern:

Stands so high
Huge hooves too
Impatiently waits for
Rider with harness
Eager to leave

When the guard recites the poem, you will split it into individual lines, strip off any trailing spaces, and respond with the ith letters of each line, i.e. ["S", "u", "p", "e", "r"].

The guard will then give you the word formed by the array of letters you replied with for you to put into uppercased word form and append "!". Finally the password you return is SUPER!, and you'll get in.

Implement the function secretRoomPassword that takes a string which represents the poem. The function should return the password that you would give to the guard.

secretRoomPassword("Stands so high\nHuge hooves too\nImpatiently waits for\nRider with harness\nEager to leave")
// returns "SUPER!"
Edit via GitHub The link opens in a new window or tab
Swift Exercism

Ready to start The 1st Rule of Poetry Club?

Sign up to Exercism to learn and master Swift with 35 concepts116 exercises, and real human mentoring, all for free.