Adapted to Swift 4 from Raymond Hettinger's talk at pycon US 2013 by Paul Ossenbruggen github video, slides. Many of the nice idioms of Python are here in Swift and some are even nicer. Python idioms apply well to Swift in most cases. Although, the types can add more verbosity. Even if you don't know Python, these approches should be easy to understand both becuase Python is sometimes thought of a runnable psuedocode and because the same approachs are encouraged in most modern languages.
The code examples and direct quotes are all from Raymond's talk where appropriate. If something does not make sense in Swift I deleted that section. I tried to stay close to the original examples, but in some cases added more where Swift has more alternatives.
See playground github
import Foundation
for i in [0, 1, 2, 3, 4, 5] {
print(i * i)
}
for i in 0..<6 {
print(i * i)
}
for i in 0...5 {
print(i * i)
}
Swift does not offer square of an integer, only the Pow
function which takes Doubles, like most C family languages. Another approach which is faster is to use the bit shift operator.
for i in 0...5 {
print(i << 2)
}
let colors = ["red", "green", "blue", "yellow"]
for i in 0..<colors.count {
print(colors[i])
}
for color in colors {
print(color)
}
for index in stride(from: colors.count-1, to: 0, by: -1) {
print(colors[index])
}
for i in (0..<colors.count).reversed() {
print(colors[i])
}
for color in colors.reversed() {
print(color)
}
for i in 0..<colors.count {
print("\(i) ---> \(colors[i])")
}
for (i, color) in colors.enumerated() {
print("\(i) ---> \(color)")
}
It's fast and beautiful and saves you from tracking the individual indices and incrementing them.
Whenever you find yourself manipulating indices [in a collection], you're probably doing it wrong.
var names = ["raymond", "rachel", "matthew"]
let n = min(names.count, colors.count)
for i in 0..<n {
print("\(names[i]) ---> \(colors[i])")
}
for (name, color) in zip(names, colors) {
print("\(name) ---> \(color)")
}
for color in colors.sorted() {
print(color)
}
for color in colors.sorted(by:>) {
print(color)
}
func compare_length(c1: String, c2: String) -> Bool {
if c1.count < c2.count { return true }
return false
}
print(colors.sorted(by: compare_length))
print(colors.sorted { $0.count < $1.count } )
var d = ["matthew": "blue", "rachel": "green", "raymond": "red"]
for k in d {
print(k) // returns key value tuples unlike python
}
for k in d.keys {
if k.hasPrefix("r") {
d[k] = nil
}
}
print(d)
When should you use the second and not the first? When you're mutating the dictionary.
If you mutate something while you're iterating over it, this is OK compared to Objective-C or Python because they use value semantics.
d = ["matthew": "blue", "rachel": "green", "raymond": "red"]
for k in d.keys {
print("\(k) ---> \(d[k])")
}
for k in d.keys {
print("\(k) ---> \(String(describing: d[k]))") // fix warning
}
for (k, v) in d {
print("\(k) ---> \(v)")
}
let moreColors = ["red", "green", "blue", "red", "green", "red"]
var counts: [String: Int] = [:]
for color in moreColors {
if var count = counts[color] {
count += 1
counts[color] = count
} else {
counts[color] = 1
}
}
print(counts)
{'blue': 1, 'green': 2, 'red': 3}
counts = [:]
for color in moreColors {
counts[color, default: 0] += 1
}
print(counts)
{'blue': 1, 'green': 2, 'red': 3}
names = ["raymond", "rachel", "matthew", "roger", "betty", "melissa", "judith", "charlie"]
In this example, we're grouping by name length
var dict:[Int: [String]] = [:]
for name in names {
let key = name.count
if var value = dict[key] {
value += [name]
dict[key] = value // assignment is important here because of value types.
} else {
dict[key] = [name]
}
}
print(dict)
dict = [:]
for name in names {
let key = name.count
if dict[key] == nil {
dict[key] = []
}
dict[key] = dict[key]! + [name] // can't be null so force is OK.
}
print(dict)
dict = [:]
for name in names {
dict[name.count, default: []] += [name]
}
print(dict)
let myDictionary = names.reduce([Int: [String]]()) { (dict, name) -> [Int: [String]] in
var dict = dict
dict[name.count, default: []] += [name]
return dict
}
print(myDictionary)
let dictx = names.reduce([Int: [String]]()) {
var dict = $0
dict[$1.count, default: []] += [$1]
return dict
}
print(dictx)
let dicty = names.reduce(into: [Int: [String]]()) {
dict[$1.count, default: []] += [$1]
}
print(dicty)
d = ["matthew": "blue", "rachel": "green", "raymond": "red"]
for _ in d {
if let (key, value) = d.popFirst() {
print("\(key) --> \(value)")
}
}
print(d)
popFirst
is atomic so you don't have to put locks around it to use it in threads.
- Positional arguments and indicies are nice
- Keywords and names are better
- The first way is convenient for the computer
- The second corresponds to how human’s think
Defeating Swift's parameter names is actually more work and less understandable.
func twitterSearch(_ name: String, _ retweets: Bool, _ numTweets: Int, _ popular: Bool) {
}
twitterSearch("@possen", false, 20, true)
func twitterSearch2(name: String, retweets: Bool, numTweets: Int, popular: Bool) {
}
twitterSearch2(name: "@possen", retweets: false, numTweets: 20, popular: true)
it is worth it for the code clarity and developer time savings. please see Apple's naming conventions for when it is approprate to drop param name. Usually when it is the direct object in the first parameter. Also, keep in mind Swift's parameters do not add the type in the paramter like Objective-C conventions.
func createTweetFromString(title: String, userNameString: String) {
}
func createTweetFromString(_ title: String, userNameString: String) {
}
func createTweet(title: String, userName: String) {
}
func runTests() -> (Int, Int) {
return (0, 4)
}
print(runTests())
You don't know because what the return values are not clear.
func runTests2() -> (failed: Int, attempted: Int) {
return (failed:0, attempted: 4)
}
print(runTests2())
They still work like a regular tuple, but are more friendly.
Python used a heterogenous array here for unpacking, but that is not supported in Swift but tuple is, by not allowing mixing of types Swift can be more efficient.
let p = ("Raymond", "Hettinger", 30, "swift@example.com")
let fname = p.0
let lname = p.1
let age = p.2
let email = p.3
let (fname2, lname2, age2, email2) = p
func fibonacci(n: Int) {
var x = 0
var y = 1
for _ in 0..<n {
print(x, terminator: " ")
let t = y
y = x + y
x = t
}
}
fibonacci(n:10)
print()
func fibonacci2(n: Int) {
var (x, y) = (0, 1)
for _ in 0..<n {
print(x, terminator: " ")
(x, y) = (y, x + y)
}
}
fibonacci2(n: 10)
print()
- An optimization fundamental rule
- Don’t cause data to move around unnecessarily
- It takes only a little care to avoid O(n**2) behavior instead of linear behavior
Basically, just don't move data around unecessarily.
names = ["raymond", "rachel", "matthew", "roger", "betty", "melissa", "judith", "charlie"]
var s = names[0]
for name in names[1...] {
s += "..." + name
}
print(s)
print(names.joined(separator: "..."))
names = ["raymond", "rachel", "matthew", "roger", "betty", "melissa", "judith", "charlie"]
names.remove(at: 0)
print(names)
The below are signs you're using the wrong data structure in Python, but in Swift insertions at either end are efficient and you don't need deque data structure.
names.removeFirst()
names.insert("mark", at: 0)
func writeFile() {
let fd = open("/tmp/scratch.txt", O_WRONLY|O_CREAT, 0o666)
if fd < 0 {
perror("could not open /tmp/scratch.txt")
} else {
let text = "Hello World"
write(fd, text, text.characters.count)
close(fd)
}
}
func writeFile2() {
let fd = open("/tmp/scratch.txt", O_WRONLY|O_CREAT, 0o666)
defer {
close(fd) // don't leave file descriptor open
}
if fd < 0 {
perror("could not open /tmp/scratch.txt")
} else {
let text = "Hello World"
write(fd, text, text.characters.count)
}
}
Although, you should use higher level functions in most cases. For example, you can read a smallish file into a string using contentsOf
let url = URL(fileURLWithPath: "/tmp/test.txt")
let va = try? String(contentsOf: url)
print(va ?? "could not load text")
Two conflicting rules:
- Don’t put too much on one line
- Don’t break atoms of thought into subatomic particles
Raymond’s rule:
- One logical line of code equals one sentence in English
var result: [Int] = []
for i in 0..<10 {
let s = i * i
result.append(s)
}
var sum = 0
for num in result {
sum += num
}
print(sum)
let result2 = (0..<10).map { $0 * $0 }.reduce(0, +)
print(result2)
First way tells you what to do, second way tells you what you want.
`Another approach which is faster is to use the bit shift operator.
for i in 0...5 {
print(i << 2)
}`
output is
0 4 8 12 16 20
not square