Swift 算法实战之路:基本语法与技巧


Swift是苹果新推出的编程语言,也是苹果首个开源语言。相比于原来的Objective-C,Swift要更轻便和灵活。笔者最近使用Swift实践了大量的算法(绝大部分是硅谷各大公司的面试题),将心得体会总结于下。此文并不是纯粹讨论Swift如何实现某一个具体的算法或者数据结构,如冒泡排序、深度优先遍历,或是树和栈,而是总结归纳一些Swift常用的语法和技巧,以便大家在解决面试题中使用。

基本语法

先来看下面一段代码

func swap(chars:[Character],  p: Int, q: Int) {
  var temp = chars[p]
  chars[p] = chars[q]
  chars[q] = temp
}
// Assume array is a character array and it has enough elements
swap(array, p: 0, q: 1)

上面代码实现了一个非常简单的功能,就是交换一个数组中的两个值。乍一看非常正确,实际上存在以下几个问题。

  1. 在第一个参数的类型前应该加上 inout关键字。因为在Swift中,struct都是按值传递,class是按引用传递;数组和字典都是struct。所以要改变原来的chars数组,在其前部加入inout关键字,表示是按引用传递。
  2. p, q, 和chars 之前应该加入下划线。Swift 3.0 默认函数的所有数都是外部(external)变量,外部变量必须声明。如果在外部变量前加上下划线,则在调用函数时无需声明外部变量,这样调用起来更简便。
  3. temp前的var应该改为let。let用于声明常量(constant),var用于声明变量(variable)。swap函数中,temp并未进行修改,所以应当视为常量,用let来声明。

修改过后的代码为

func swap(chars: inout [Character],  _ p: Int, _ q: Int) {
  let temp = chars[p]
  chars[p] = chars[q]
  chars[q] = temp
}
// Assume array is a character array and it has enough elements
swap(&array, 0, 1)

更进一步,我们可以使用泛型编程,并配合Tuple,在配合上断言,就写出了灵活度很高的交换方法。代码如下:

func swap<T>(_ array: inout [T],  _ p: Int, _ q: Int) {
  assert(p >= 0 && p < array.count)
  assert(q >= 0 && q < array.count)
  
  (array[p], array[q]) = (array[q], array[p])
}

再来看一段代码

func toZero(x: Int) -> Int {
  while x > 0 {
    x -= 1
  }
  return x
}

这里在 x -= 1 处会报错。原因是函数中所有的参数是常量(let),是不可以修改的。解决的方法是在函数中写上var x = x,之后就可以对 x 进行修改了

循环

Swift循环分为for和while两种,注意他们的结构跟传统的 Java, C/C++有很大区别,笔者将其总结如下

// Assume names is an array holds enough Strings
// for loop
for (index, element) in names.enumerated()
for name in names { }
names.forEach { element in }
for i in 0 ... names.count - 1 { }
for i in 0 ..< names.count { }
for _ in 0 ..< names.count { }
for name in names.reverse() { }
for i in 0.stride(through: names.count - 1, by 2) { }
for i in 0.stride(to: names.count, by: 2) { }

// while loop
var i = 0
while i < names.count { }
repeat { } while i < names.count

以上代码非常简单。需要说明的有两个,一个是 for _ in 0 ..< names.count { } 。当我们不需要数组中每一个具体的元素值时,我们就可以用下划线来代表序号。
另一个是是 repeat { } while i < names.count 。这个相当于我们熟悉(java)的 do { } while (i < names.length)
注意,在循环中,i是let变量,是不可以修改的。

排序

swift排序效率很高,写法也很简洁。笔者将其总结如下

// Sort an Int array,ascending
nums.sort()

// Sort an Int array without mutating the original one, ascending
var sortedNums = nums.sorted()

// Sort an array with custom-defined objects, ascending
timeIntervals.sort { $0.startTime < $1.startTime }

// Sort keys in a dictionary according to its value, ascending
let keys = Array(dict.keys)
var sortedKeys = keys.sorted {
  let value0 = dict[$0]!
  let value1 = dict[$1]!
  return value0 < value1
}

活用Guard语句

使用Guard语句可以让逻辑变得非常清楚,尤其是在处理算法问题的时候,请看下面的代码

// Returns the index of the first occurrence of needle in haystack, 
// or -1 if needle is not part of haystack
func strStr(haystack: String, _ needle: String) -> Int {
  let hCount = haystack.count, nCount = needle.count
  if hCount == 0 || hCount < nCount {
    return -1
  }
  
  for i in 0...nCount - nCount {
    if haystack[i] == needle[0] {
      for j in 0..<needle.count {
        if haystack[i + j] != needle[j] {
          break
        } else {
          if j == needle.count - 1 {
            return i
          }
        }
      }
    }
  }  
  return -1
}

extension String {
  subscript(i: Int) -> Character {
    return self[index(startIndex, offsetBy: i)]
  }
}

上面这段代码是求字符串needle在字符串haystack里首次出现的位置。我们发现for循环中的嵌套非常繁琐,但是如果使用Guard语句代码会变得清晰很多:

func strStr(haystack: String, _ needle: String) -> Int {
  guard haystack.count != 0 && haystack.count >= needle.count else {
    return -1
  }
  
  for i in 0...haystack.count - needle.count {
    guard haystack[i] == needle[0] else {
      continue
    }
    
    for j in 0 ..< needle.count {
      guard haystack[i + j] == needle[j] else {
        break
      }
      if j == needle.count - 1 {
        return i
      }
    }
  }
  
  return -1
}

Guard语句的好处是判断条件永远是我们希望的条件而不是特殊情况,且成功避免了大量的if嵌套。

另外在上面代码中,笔者给字符串添加了下标实现(subscript)。这里用到的index(startIndex, offsetBy: i)方法,其时间复杂度为O(n)。所以如果考虑性能最优,最好将字符串转化为字符数组进行处理。

总结

Swift是一门独特的语言,本文对其基本知识和语法进行了适当归纳,文中提到的都是基本细节。下期我们会讨论更加进阶的内容。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容