编程挑战:你是Swift忍者吗?(part 1& part 2)

发布于:2014-08-06 14:48阅读数:

你是Swift忍者吗? 虽然Swift刚放出一段时间,而且它仍然处在beta阶段,但许多人已经挖掘了很多东西。 你到目前为止走到了下面哪一步?你已经: 阅读了Apple的《Swift Programming Language》书了吗?

(via:泰然网)
 
编程挑战:你是Swift忍者吗?part 1
 
你是Swift忍者吗?
 
虽然Swift刚放出一段时间,而且它仍然处在beta阶段,但许多人已经挖掘了很多东西。
 
你到目前为止走到了下面哪一步?你已经:
 
阅读了Apple的《Swift Programming Language》书了吗?
阅读了Apple的《Using Swift with Cocoa and Objective-C》书了吗?
用Swift移植或做一个项目了吗?
阅读了至少5篇Swift的博客文章或教程了吗?
访问了#swift-lang IRC组了吗?
订阅了Swift语言邮件列表了吗?
申请了当Chris Lattner发表文章时的电子邮件更新了吗?
 
如果你在本测验中得到3分以上,那你可能就是一个Swift忍者。
 
好的,下面第2部分的系列将帮助你来确定!
 
我将给你一系列Swift的编程挑战,你可以用来测试你的Swift知识,然后看一看你是不是一个真的Swift忍者。
 
如果你在某些时间感觉不那么“忍者”,你也将有机会学习这些技术!无论你在Swift中已经是资深人员或者只是中级水平,你都会学到一些东西。
 
把你的飞镖和武士刀准备好 – 挑战就要开始了!
 
注: 这篇文章是为有Swift语言经验的程序员准备的。如果你感觉不那么轻松的话,查看我们其余的Swift教程.
 

挑战
 
这个系列跟我们之前在这个网站发表的文章风格上有一点的不同。它将提出一系列问题来不断增加复杂度。这里的许多问题用到了之前部分的技术,所以掌握它们是你成功的基本条件。
 
每一个问题突出至少一个Swift的语言特征,奇怪的语法,或者聪明的Hack。
 
不用担心,你不会被扔到狼堆里 – 这里会有提示。每一部分都有两个等级的提示,当然还有Apple的Swift和书,和你的好朋友Stack Overflow能帮助你。
 
如何对待每一个问题
每个问题的开始定义了你需要什么去完成代码,哪些Swift特性你是能用的,哪些是不能用的。我建议你使用Playground来完成每一个挑战。
 
如果你遇到了困难就打开提示部分。虽然提示没有解决你当前的问题,但它们指明了方向。
 
如果你仍然没有找到解决方法 — 打开问题的教程部分。你会发现解决问题需要使用的技术和解决问题的代码。无论如何,在本系列的末尾你会找到所有这些问题的解决方案。
 
哦 – 还有记得记录你的得分!
1. 如果你解决问题没有偷看提示部分或教程部分,你得到3个飞镖:3shurikens
2. 如果你解决问题查看了提示部分,你得到2个飞镖:2shurikens
3. 如果你解决问题查看了教程部分,你得到1个飞镖:1shuriken
 
即使你自己解决了问题,花一点时间查看一下教程提供的解决方法 – 比对代码总是一件好事情!
 
如果你用指定的(更困难的)方法解决问题,有些挑战可以得到一个额外的飞镖extrashuriken
 
用一张纸或你喜欢的记录app,记录你在每个挑战中得到了多少个飞镖。
 
别添加你的得分来欺骗自己。这不是高尚的忍者的行事方法。即使你没有收集到每一个飞镖,在文章的最后你也会开阔你的思维,大胆得走向未来。
 

Swift忍者挑战
 
挑战 #1
在Apple的Swift书中,有许多函数的例子来交换两个变量的值。里面的代码总是使用额外的变量来存储“经典”的方案。但是你可以比它做得更好。
 
你的第一个挑战是写一个具有两个参数(任何类型)的函数,它交换了两个变量的值。
 
要求:函数体只用一行代码
 
如果你没有打开提示或教程部分,给你自己3shurikens
 
解决方法:提示
Swift元组非常强大 – 你可以存储任何类型的变量到元组中。此外,如果它们是元组,你可以一次给很多变量赋值。一个元组,两个元组!:]
 
记得你偷看了提示,现在你只能在这次的挑战中得到2shurikens
 
解决方法:教程
作为一个Swift忍者应该知道,Swift新特性中的元组可以存储变量。语法也很简单 – 用括号括住变量列表(或常量、表达式等)
  1. var a = "Marin" 
  2. var b = "Todorov" 
  3. var myTuple = (a, b) 
 
在上述例子中 a 和 b 是值类型(字符串),所以它们的值在创建元组的时候被复制了。有趣的是,你也可以给元组复制,像这样:
  1. var name: String 
  2. var family: String 
  3.  
  4. (name, family) = ("Marin""Todorov"
 
上面的例子中根据另外一个元组的值同时设置了 name 和 family 的值。
 
合并你在前面两个例子中学到的,你现在可以写一个函数,获取两个任意类型(它们是同一种数据类型)的变量并交换它们的值。下面是解决原来问题的代码,和在 playground 中 r 的测试代码:
  1. func swap<T>(inout a:T, inout with b:T) { 
  2.     (a, b) = (b, a) 
  3.  
  4. //demo code 
  5. var a = "Marin", b = "Todorov" 
  6. swap(&a, &b) 
  7.  
  8. [a, b] 
 
你定义一个函数 swap(a:, with:),包含两个可读写的同种类型T的参数。
 
在函数中的单行代码,你只需要根据函数的两个参数创建一个元组,然后把值赋给另外一个元组。然后你改变这两个参数的顺序来交换元组的值。
 
上面的代码也声明了两个变量a和b来展示如何使用swap(a:, with:)。最后一行代码将在窗口的右手边的playground输出区域输出a和b的值,结果如下:
  1. ["Todorov""Marin"
如果你在 Playground 中运行了代码,学会了如何用元组交换值,给你自己另外一个1shuriken
 
挑战 #2
Swift函数是非常灵活的 — 它们可以接受可变数量的参数,返回一个或多个值,返回其他的函数等等。
 
在这次的挑战中,将测试你对 Swift 函数的语法的理解。写一个满足下列要求的,命名为 flexStrings 的函数:
 
1. 函数必须接受0,1或2个字符串参数。
2. 返回函数参数连接起来的字符串。
3. 如果没有参数传递给函数,它将返回字符串“none”。
4. 函数体只使用一行代码可获得一个额外的飞镖extrashuriken。
 
下面是一些调用函数的示例和输出:
  1. flexStrings() //--> "none" 
  2. flexStrings(s1: "One"//--> "One" 
  3. flexStrings(s1: "One", s2: "Two"//--> "OneTwo" 
解决问题将获得1shuriken1shuriken1shuriken,如果你用一行代码完成了它并且完成了上面4个要求,你将获得一个额外的飞镖。
 
解决方法:提示
Swift 函数参数可以有默认值,因此你调用函数时,可以省略参数。
 
记得现在在这次挑战中你只能获得1shuriken1shuriken了 — 而且一行代码解决问题也没有额外的飞镖了!
 
解决方法:教程
Swift 函数参数可以有一个默认值,这是旧的 Objective-C 和 Swift 之间的一个差别。当参数有默认值时,你可以用它的名称调用这个函数。
 
好处就在于如果你喜欢用它的默认值,你可以忽略参数。真棒!
 
解决方案?当你知道参数默认值后,它是如此的简单:
  1. func flexStrings(s1: String = "", s2: String = "") -> String { 
  2.     return s1 + s2 == "" ? "none": s1 + s2 
你定义了一个接受两个参数的函数,但是它们的默认值都为“”(空字符串)。你可以调用这个函数用如下方法:
 
1. 正常的2的参数。
2. 你传递1个参数,另外一个参数是默认值“”。
3. 没有参数,函数将都使用默认值。
 
所以在函数体里,你只需要检查是否两个参数都是空字符串 (s1+s2 == “”),如果是就返回 “none” ,如果不是,就连接 s1 和 s2 然后返回结果。看!所以要求都满足了。
 
完成一行代码的要求,你可以看到 Objective-C 的三元运算符?:也在 Swift 重现了。
 
试着在 Playground 中使用不同的参数调用这个函数 — 确定你理解了它是如何工作的。做完后给你自己一个1shuriken
 
挑战 #3
你已经在前面的挑战中掌握了使用可选参数的函数。相当有趣吧!
 
但是根据之前的方法,你只能有一个固定最大数量的参数。如果你想真的得到一个可变数量的输入参数的函数,这还有一个更好的方法。
 
这次挑战展示了如果更好的使用内建的 Array 方法和 switch 语句。你在读 Apple 的 Swift Programming Language 书时注意过吗?那里面讲过。:]
 
写一个命名为 sumAny 的函数,它接受任何类型的0或更多的参数。函数应满足如下要求:
 
1. 这个函数将根据下面的规则用String返回传递过来的参数值的和。
2. 如果参数是一个空的字符串或一个数值0,结果添加-10。
3. 如果参数是一个正数的字符串(比如“10″,“-5″则不符合),添加到结果中。
4. 如果参数是一个Int值,添加到结果中。
5. 如果是其他值,则忽略,不添加到结果中。
 
extrashuriken 额外的飞镖 – 函数使用一个return语句而且不使用任何循环(比如 for 或 while)。
 
这里是一些调用函数的示例和结果,你可以完成后检查你的函数:
  1. let resultEmpty = sumAny() //--> "0" 
  2. let result1 = sumAny(Double(), 10, "-10", 2) //--> "12" 
  3. let result2 = sumAny("Marin Todorov", 2, 22, "-3""10""", 0, 33, -5) //--> "42" 

3shurikens
 
解决方法:提示
你可以把函数的最后一个参数定义为name: Type…,这样就定义一个接受可变数量参数的函数。然后,你就可以用一个普通的Array来操作name了。
 
你可以用Array.map((T)->(T))来一个一个处理其中的元素。也可以用Array.reduce(T, (T)->(T))换算数组中的单个值。
 
最后,计算数字的和,然后在返回结果前转换为String。祝你好运!
 
2shurikens
 
解决方法:教程
这个问题涉及到了很多 Swift 内建的语言特性,所以在看最终的解决方案之前,让我们先来浏览一些概念。
 
首先来看一下如何定义一个接受可变数量参数的函数:
  1. func sumAny(anys: Any...) -> String { 
  2.   //code 
 
如果你在函数的参数后面加上 “…”,Swift 将接受 0 到多个这种类型的参数。当你指定 Any…时,它意味着任何数量的任何类型的值可以传给这个函数。
 
你可以在函数中把上面例子中的anys当做一个普通的数组来对待。比如:
  1. func sumAny(anys: Any...) -> String { 
  2.   return String(anys.count) 
 
上面的代码返回了数组 anys 元素的数量,它也是传给函数的参数的数量。
 
下一步,你需要计算这些值的和了。你可以使用一个循环来遍历数组的值,然后用一个临时变量保存结果。但是用这种解决方案你就得不到额外的飞镖了 :]
 
你将使用一个不同的策略 — 使用 Array.map((T)->(T)) 来(按要求)转化所有元素为Int值,然后使用 Array.reduce(T, (T)->(T)) 来合计所有的值。
 
这里是转换每一个元素到需要合计的值的代码:
  1. anys.map({item in 
  2.   switch item { 
  3.     case "" as String, 0 as Int: 
  4.       return -10 
  5.     case let s as String where s.toInt() > 0: 
  6.       return s.toInt()! 
  7.     case is Int: 
  8.       return item as Int 
  9.     default
  10.       return 0 
  11.   } 
  12. }) 
 
map 迭代了数组中的每一个元素,然后闭包的函数处理它。改变元素或返回任何你想要的值是所有的选项。在上面闭包的函数中,你只添加了一个 switch 语句然后按指定的要求转换了每一个元素。
 
如果你不熟悉 Swift 中的 switch 进阶用法,这里有每一种用法的说明:
 
1. 如果是一个空字符串“”或0,然后转换这种元素为值-10。
2. 如果是一个字符串元素,而且转换为Int值后大于0 – 转换这种元素为它们的 Int 值。
3. 如果是Int值返回它的值。
4. 所有不匹配上面条件的数组中的其他元素将被转换为0。
 
好的!这个switch语句把你的随机类型的值全部转换成了整数。在你调用 map 之后,你实际上已经用一个 Int 数组替换了一个任何类型的数组。
 
获取数组元素的和是 Array.reduce(T, (T)->(T)) 的完美应用。让我们看一下下面的示例中的函数是如何动作的:
  1. [1,2,3].reduce(4) { result, item in 
  2.     return result + item 
  3. //--> 10 
 
1. 你定义了一个 Array,包含元素 1, 2 和 3。
2. 然后你用4做为起始值调用了reduce。
3. reduce 用 4(result) 和第一个元素 1(item) 调用了给定的闭包函数,闭包函数处理结果为 5。
4. 接下来,reduce 用结果 5(之前的结果)和下一个数组中的元素(2)调用了闭包函数。
5. 这次的结果是 7,所以当 reduce 用最后一个数组调用完函数然后添加 3 – 最终结果则变为了10。
 
reduce是一个非常方便的函数 — 你不需要声明数组或变量,代码也看起来干净多了。
 
现在合并上面讨论的技术,产生了最终的解决方案:
  1. func sumAny(anys: Any...) -> String { 
  2.     return String((anys.map({item in 
  3.         switch item { 
  4.         case "" as String, 0 as Int: 
  5.             return -10 
  6.         case let s as String where s.toInt() > 0: 
  7.             return s.toInt()! 
  8.         case is Int: 
  9.             return item as Int 
  10.         default
  11.             return 0 
  12.         } 
  13.         }) as [Int]).reduce(0) { 
  14.             $0 + $1 
  15.         }) 
 
这是怎么工作的,下面将一点一点说明:
 
1. 你使用 map 转换所有元素为它们需要合计的值。
2. 你转换 map 的结果为 Int[] ,然后所有元素变为整数。
3. 你使用示例中的 reduce 合计了所有的元素。
4. 最后,你在返回之前把结果转换为 String。
 
你不需要使用任何循环或任何变量 — 只需要使用 Array 的内建函数。酷吧?
 
如果你学会并掌握了如何使用 map 和 reduce,给你自己1个飞镖。
 
1shuriken
 
挑战 #4
写一个命名为 countFrom(from:Int, #to:Int) 的函数,它将输出(比如通过 print() 或 println() )从 from 到 to 的数值。你不可以使用任何循环、变量或者任何内建的数组函数。假定 from 的值小于to(输入是有效的)。
 
下面是调用的示例和它的输出:
  1. countFrom(1, to: 5) //--> 12345 
3shurikens
 
解决方法:提示
使用递归函数,每次调用增加 from 的值,直到它与to参数的值相等。
 
1shuriken1shuriken
 
解决方法:教程
本问题的解决方案将涉及递归。对于每一个从 from 开始的数值,你将递归调用 countFrom 同时每次给 from 的值增加1。当 from 与 to 相等时,你将停止递归。这将有效的把函数转换成一个简单的循环。
 
下面我们来看一下完成的解决方案:
 
  1. func countFrom(from: Int, #to: Int) { 
  2.     print(from) // output to the assistant editor 
  3.     if from < to { 
  4.         countFrom(from + 1, to: to) 
  5.     } 
  6.   
  7. countFrom(1, to: 5) 
 
当你调用函数时,它打印 from 参数“1”然后把它与to对比。1小于5,所以函数继续递归调用它自己 countFrom(1 + 1, 5)。
 
第二次调用输出“2”然后对比2和5,然后再次调用它自己:countFrom(2 + 1, 5)。
 
函数将会继续递归调用它自己并增加 from 的值,直到 from 等于5,递归才会停止。
 
递归是一个强大的概念,接下来的问题将会需要递归,所以确保你理解了上面的解决方案!
 
为你学习了如何在Swift中使用递归给你自己1shuriken
 

 下一步我们将去哪?
 
 
在第2节中得到复仇!
 
忍者也需要休息。如果你跟着本教程到了这里,你已经完成了很好的工作 — 你应该休息一下了!
 
我想利用这个时间来考虑一下前面的问题和解决方案。Swift非常强大,而且比Objective-C更具表达性和安全性。
 
随着这次挑战中的最初4个问题,我想让你在Swift中的各种领域中浏览一下。我希望最初的几个问题到目前为止很有趣和你也得到了有益的经验。
 
让我们做个小总结:
1. 到现在为止,没有一个解决方案需要你去声明任何的变量 — 你可能比你想像中更不需要它们!
2. 你也没有使用任何的循环。反思一下!
3. 具有默认值的参数和可变数量的参数让 Swift 的函数异常强大。
4. 了解强大的基本内建函数也是非常重要的,比如 map, reduce, sort, countElements 等等。
 
如果你真的想放下编程,你可以看看开放的经典游戏忍者龙剑传的片段:
 
 

CocoaChina是全球最大的苹果开发中文社区,官方微信每日定时推送各种精彩的研发教程资源和工具,介绍app推广营销经验,最新企业招聘和外包信息,以及Cocos2d引擎、Cocos Studio开发工具包的最新动态及培训信息。关注微信可以第一时间了解最新产品和服务动态,微信在手,天下我有!

请搜索微信号“CocoaChina”关注我们!

搜索CocoaChina微信公众号:CocoaChina

顶部