Skip to content

Instantly share code, notes, and snippets.

@simonwhitaker
Last active August 29, 2015 14:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save simonwhitaker/38673752159bf035635f to your computer and use it in GitHub Desktop.
Save simonwhitaker/38673752159bf035635f to your computer and use it in GitHub Desktop.
A demonstration of how you might use the decorate-sort-undecorate pattern in Swift, using a handful of cool Swift language features.
/*
Decorate-Sort-Undecorate in Swift
In Swift, you get the length of a String by calling the global function
countElements(). However, The Swift Programming Language states:
> The length of a string cannot be calculated without iterating through
> the string to consider each of its characters in turn. If you are
> working with particularly long string values, be aware that the
> countElements function must iterate over the characters within a string
> in order to calculate an accurate character count for that string.
See https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html#//apple_ref/doc/uid/TP40014097-CH7-XID_368
So, if you want to sort an array of strings by length, you'd ideally
want to be pre-calculating their lengths so that you only have to call
countElements() once per string.
This is a great place to use the decorate-sort-undecorate pattern (known
to gnarly old Perl programmers as the Schwartzian Transform). The basic
idea is to:
1. "Decorate" the elements to be sorted with the value of the expensive
function, countElements in our case
2. Sort the decorated array by the decoration value
3. "Undecorate" the sorted array, extracting the original values and
discarding the decorations.
This turned out to also be a great place to use various Swift language
features, including tuples, operator functions, type aliases and more.
I'll explain what I'm doing as I go through.
First, I use the typealias keyword to declare a type alias for a
(String, Int) tuple with named elements. Just like a #typedef in C, I've
done this purely to save myself having to type the more verbose type
in multiple places. */
typealias StringWithLength = (string: String, length: Int)
/*
Next, I use operator functions to define how the < and > operators work
when they're applied to my tuple type. In each case, they'll return the
results of applying the same operator to the length element of the tuple
operands. */
@infix func > (left: StringWithLength, right: StringWithLength) -> Bool {
return left.length > right.length
}
@infix func < (left: StringWithLength, right: StringWithLength) -> Bool {
return left.length < right.length
}
/*
Declare an array of sample strings */
let strings = [
"Hello world",
"W00t",
"Here's a somewhat longer string",
"Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam convallis. Nunc lacus. Curabitur nunc mauris, commodo vel, eleifend in, ornare sit amet, felis. Nullam mi neque, feugiat et, porttitor vitae, pharetra non, lacus. Fusce imperdiet sem quis dui. Nullam fermentum. Nullam sit amet mi. Nunc porttitor, tellus eget tincidunt blandit, neque nunc scelerisque nisi, vitae faucibus purus tellus id metus. Sed dapibus, nisl pretium laoreet aliquam, lacus eros blandit odio, sit amet interdum libero dolor at lorem. Curabitur pharetra diam in turpis. Nullam purus.",
"",
]
/*
Decorate: build an array of StringWithLength tuples for our strings,
doing the expensive countElements operation just once per string.
The Array.map() method returns the result of applying a closure to
each element of the array. The most verbose version of this code would
look like this:
let stringsWithLengths: StringWithLength[] = strings.map({ (s: String) -> StringWithLength in
return (s, countElements(s))
})
But the closure can infer its types from its input (an array of
Strings) and return type, so we don't need to explicitly state those.
That leads to:
let stringsWithLengths: StringWithLength[] = strings.map({ s in return (s, countElements(s)) })
Furthermore, single-expression closures can implicitly return the
result of their single expression by omitting the return keyword, which
gets us:
let stringsWithLengths: StringWithLength[] = strings.map({ s in (s, countElements(s)) })
We can also get rid of the named argument (s) and use the shorthand argument
names ($0, $1, etc) instead:
let stringsWithLengths: StringWithLength[] = strings.map({ ($0, countElements($0)) })
Finally, when a closure is passed as the final argument to a function or method,
it can be written outside the round brackets, like this:
let stringsWithLengths: StringWithLength[] = strings.map() { ($0, countElements($0)) }
And when this results in an empty set of round brackets, they can be omitted:
let stringsWithLengths: StringWithLength[] = strings.map { ($0, countElements($0)) }
Phew! */
let stringsWithLengths: StringWithLength[] = strings.map { ($0, countElements($0)) }
/*
Sort: sort the array of tuples, using the custom < operator defined above.
Here, I'm passing in the operation function < as the argument to the Array.sort() method,
which sorts the array in place. */
stringsWithLengths.sort(<)
/*
Undecorate: extract the strings from the array of tuples. I'm using map() again,
as in the decorate phase. */
let sortedStrings = stringsWithLengths.map{ $0.string }
for s in sortedStrings {
println(s)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment