When Swift met classic algorithms and data structures Caveats & Tips
Given an array of numbers, write a function to move all 0's to the end of it while maintaining the relative order of the non-zero elements. *Should not use extra space. 0 1 0 2 3 0 Example: 1 2 3 0 0 0
- Implementation in Objective-C - Time: O(n) - Space: O(1) - (void)movezeros:(nsmutablearray<nsnumber *> *)nums { NSUInteger nozero = 0; for (NSUInteger i=0; i<nums.count; i++) { if ([nums[i] integervalue]!= 0) { // swap id tmp = nums[nozero]; nums[nozero] = nums[i]; nums[i] = tmp; // move to next no zero position nozero ++; NSMutableArray *nums = [@[@0, @1, @0, @2, @3, @0] mutablecopy]; [self movezero:nums]; // nums: [1, 2, 3, 0, 0, 0]
Now do we achieve O(1) space complexity - Implementation in Swift without using any extra space? func movezeros(_ nums: inout [Int]) { var nozero = 0 for i in 0..<nums.count nums.indices where { nums[i]!= 0 { (nums[i], let if i tmp!= = nozero nums[nozero])!= 0 {{ = (nums[nozero], nums[i]) nums[nozero] nozero let swap(&nums[i], += tmp 1 = = nums[nozero] nums[i] &nums[nozero]) nums[i] nums[nozero] = tmp = nums[i] nozero nums[i] += 1 = tmp nozero += 1 nozero += 1 var nums = [0, 1, 0, 2, 3, 0] movezeros(&nums) // nums: [1, 2, 3, 0, 0, 0]
func movezeros(_ nums: inout [Int]) { // Copied var nozero = In-Out 0 parameters: for i in 0..<nums.count where nums[i]!= 0 { (nums[i], copy-in nums[nozero]) copy-out = (nums[nozero], nums[i]) nozero += 1
How to deal with it? - NSMutableArray func movezeros(_ nums: NSMutableArray) - UnsafeMutableBufferPointer var nums = [0, 1, 0, 2, 3, 0] nums.withunsafemutablebufferpointer { (buffer) in var nozero = 0 for i in buffer.indices where buffer[i]!= 0 { (buffer[i], buffer[nozero]) = (buffer[nozero], buffer[i]) nozero += 1 // nums: [1, 2, 3, 0, 0, 0]
var nums = [1, 2, 3] for n in nums { For-in loop: Iterate nums.append(100 with indices + n) :) iterate with copied array print(nums) // nums: [1, 2, 3, 101, 102, 103]
Ready for the next one?
Given an array of strings, we want to group the anagrams together. cat tac gat tag gta eat Example: cat tac gat tag gta eat
- Implementation in Swift - Time: O(nlogn) -> O(n 2 ) - Space: O(n) func groupanagrams(_ words: [String]) -> [[String]] { var anagrams = [String: [String]]() for word in words { let sortedword = String(word.characters.sorted()) if anagrams[sortedword] == nil { anagrams[sortedword] = [] anagrams[sortedword]?.append(word) // copy the whole array everytime return Array(anagrams.values) return Array(anagrams.values)
func groupanagrams(_ words: [String]) -> [[String]] { var anagrams = [String: Copy [String]]() on Write for word in words { let could sortedword = be String(word.characters.sorted()) triggered if anagrams[sortedword] == nil { anagrams[sortedword] = [] accidentally anagrams[sortedword]?.append(word) // copy the whole array everytime return Array(anagrams.values)
How to deal with it? - Use reference semantic final class ReferenceBox<Value> { var value: Value init(_ value: Value) { self.value = value // anagrams[sortedword]?.value.append(word) // O(1) - It probably will be improved in the future Swift release
Follow up: what if we now want to do something more whenever a word contains some specific characters? func groupanagrams(_ words: [String]) [String], -> _ char: [[String]] Character) { -> [[String]] var { anagrams = [String: [String]]() for var word anagrams in words = [String: { [String]]() for let word sortedword in words { = String(word.characters.sorted()) if let anagrams[sortedword] sortedword = String(word.characters.sorted()) == nil { if word.characters.contains(char) anagrams[sortedword] = [] { // O(n) // do something anagrams[sortedword]?.append(word) if anagrams[sortedword] == nil { return Array(anagrams.values) anagrams[sortedword] = [] anagrams[sortedword]?.append(word) return Array(anagrams.values)
func binarysearch(_ s: String, _ t: Character) -> Bool { var start = s.startindex, end = s.index(before: s.endindex) while start <= end { let mid = s.index(s.startindex, offsetby: s.distance(from: s.startindex, to: start) + s.distance(from: start, to: end)/2) // start + (end - start) / 2 // O(n) instead of O(1) if s[mid] == t { return true else if s[mid] < t { start = s.index(after: mid) else { end = s.index(before: mid) s.distance(from: s.startindex, to: start) + s.distance(from: start, Use binary search return false // -> O(n*logn)
Without random access: - Calculate distance between two characters - Swap between two characters - Access nth character - Substring from x to y Linear Complexity
How to deal with it? - Convert into Array<Character> func binarysearch(_ s: [Character], _ t: Character) -> Bool { var start = 0, end = s.count - 1 while start <= end { let mid = start + (end - start)/2 if s[mid] == t { return true else if s[mid] < t { start = mid + 1 else { end = mid - 1 return false // -> O(logn)
Follow up: what if now we know that input will be only ASCII value? - Convert into NSString - Use counting sort O(nlogn) -> O(n) func groupanagrams(_ words: [String]) -> [[String]] { var anagrams = [String: [String]]() for word in words { let sortedword = String(word.characters.sorted()) let sortedword = sortbycounting(nsstring(string: word)) if anagrams[sortedword] == nil { anagrams[sortedword] = [] anagrams[sortedword]?.append(word) return Array(anagrams.values)
How we choose? Swift.String NSString/ String.utf16 Array <Character> Unicode Friendly How we choose? Memory Usage Random Access
Recap Don t need to worry, Value semantic. Copy on Write. Swift String. it ll just work
Where to go from here Open the playground and try something yourself. Follow the future release of Swift: Swift 4 this fall. Thinking in Swift when implement algorithm. More Swift features: generic, closure, extension, enum
Thank you. Victor Wang email: wangshengjia01@gmail.com github: wangshengjia twitter: @wangshengjia