编程挑战:你是Swift忍者吗?第一节


原文地址:http://www.raywenderlich.com/76349/swift-ninja-part-1
泰然翻译组:Lyubishchev。校对:lareina。

Are you a Swift Ninja?

你是Swift忍者吗?

虽然Swift刚放出一段时间,而且它仍然处在beta阶段,但许多人已经挖掘了很多东西。

你到目前为止走到了下面哪一步?你已经:

如果你在本测验中得到3分以上,那你可能就是一个Swift忍者

好的,下面第2部分的系列将帮助你来确定!

我将给你一系列Swift的编程挑战,你可以用来测试你的Swift知识,然后看一看你是不是一个真的Swift忍者

如果你在某些时间感觉不那么“忍者”,你也将有机会学习这些技术!无论你在Swift中已经是资深人员或者只是中级水平,你都会学到一些东西。

把你的飞镖和武士刀准备好 – 挑战就要开始了!

: 这篇文章是为有Swift语言经验的程序员准备的。如果你感觉不那么轻松的话,查看我们其余的Swift教程.

挑战

这个系列跟我们之前在这个网站发表的文章风格上有一点的不同。它将提出一系列问题来不断增加复杂度。这里的许多问题用到了之前部分的技术,所以掌握它们是你成功的基本条件。

每一个问题突出至少一个Swift的语言特征,奇怪的语法,或者聪明的Hack。

不用担心,你不会被扔到狼堆里 – 这里会有提示。每一部分都有两个等级的提示,当然还有Apple的Swift和书,和你的好朋友Stack Overflow能帮助你。

如何对待每一个问题

每个问题的开始定义了你需要什么去完成代码,哪些Swift特性你是能用的,哪些是不能用的。我建议你使用Playground来完成每一个挑战。

如果你遇到了困难就打开提示部分。虽然提示没有解决你当前的问题,但它们指明了方向。

如果你仍然没有找到解决方法 — 打开问题的教程部分。你会发现解决问题需要使用的技术和解决问题的代码。无论如何,在本系列的末尾你会找到所有这些问题的解决方案。

哦 – 还有记得记录你的得分!

  • 如果你解决问题没有偷看提示部分或教程部分,你得到3个飞镖:3shurikens
  • 如果你解决问题查看了提示部分,你得到2个飞镖:2shurikens
  • 如果你解决问题查看了教程部分,你得到1个飞镖:1shuriken

即使你自己解决了问题,花一点时间查看一下教程提供的解决方法 – 比对代码总是一件好事情!

如果你用指定的(更困难的)方法解决问题,有些挑战可以得到一个额外的飞镖extrashuriken

用一张纸或你喜欢的记录app,记录你在每个挑战中得到了多少个飞镖。

别添加你的得分来欺骗自己。这不是高尚的忍者的行事方法。即使你没有收集到每一个飞镖,在文章的最后你也会开阔你的思维,大胆得走向未来。

Swift忍者挑战

挑战 #1

在Apple的Swift书中,有许多函数的例子来交换两个变量的值。里面的代码总是使用额外的变量来存储“经典”的方案。但是你可以比它做得更好。

你的第一个挑战是写一个具有两个参数(任何类型)的函数,它交换了两个变量的值。
要求:

  • 函数体只用一行代码

如果你没有打开提示教程部分,给你自己3shurikens

解决方法:提示

Swift元组非常强大 – 你可以存储任何类型的变量到元组中。此外,如果它们是元组,你可以一次给很多变量赋值。一个元组,两个元组!:]

记得你偷看了提示,现在你只能在这次的挑战中得到2shurikens

解决方法:教程

作为一个Swift忍者应该知道,Swift新特性中的元组可以存储变量。语法也很简单 – 用括号括住变量列表(或常量、表达式等)

var a = "Marin"
var b = "Todorov"
var myTuple = (a, b)

在上述例子中a和b是值类型(字符串),所以它们的值在创建元组的时候被复制了。有趣的是,你也可以给元组复制,像这样:

var name: String
var family: String

(name, family) = ("Marin", "Todorov")

上面的例子中根据另外一个元组的值同时设置了namefamily的值。

合并你在前面两个例子中学到的,你现在可以写一个函数,获取两个任意类型(它们是同一种数据类型)的变量并交换它们的值。下面是解决原来问题的代码,和在playground中r的测试代码:

func swap(inout a:T, inout with b:T) {
    (a, b) = (b, a)
}

//demo code
var a = "Marin", b = "Todorov"
swap(&a, &b)

[a, b]

你定义一个函数swap(a:, with:),包含两个可读写的同种类型T的参数。

在函数中的单行代码,你只需要根据函数的两个参数创建一个元组,然后把值赋给另外一个元组。然后你改变这两个参数的顺序来交换元组的值。

上面的代码也声明了两个变量ab来展示如何使用swap(a:, with:)。最后一行代码将在窗口的右手边的playground输出区域输出ab的值,结果如下:

["Todorov", "Marin"]

如果你在Playground中运行了代码,学会了如何用元组交换值,给你自己另外一个1shuriken

挑战 #2

Swift函数是非常灵活的 — 它们可以接受可变数量的参数,返回一个或多个值,返回其他的函数等等。

在这次的挑战中,将测试你对Swift函数的语法的理解。写一个满足下列要求的,命名为flexStrings的函数:

  • 函数必须接受0,1或2个字符串参数。
  • 返回函数参数连接起来的字符串。
  • 如果没有参数传递给函数,它将返回字符串“none”。
  • 函数体只使用一行代码可获得一个额外的飞镖extrashuriken

下面是一些调用函数的示例和输出:

flexStrings() //--> "none"
flexStrings(s1: "One") //--> "One"
flexStrings(s1: "One", s2: "Two") //--> "OneTwo"

解决问题将获得3shurikens,如果你用一行代码完成了它并且完成了上面4个要求,你将获得一个额外的飞镖。

解决方法:提示

Swift函数参数可以有默认值,因此你调用函数时,可以省略参数。

记得现在在这次挑战中你只能获得2shurikens了 — 而且一行代码解决问题也没有额外的飞镖了!

解决方法:教程

Swift函数参数可以有一个默认值,这是旧的Objective-C和Swift之间的一个差别。当参数有默认值时,你可以用它的名称调用这个函数。

好处就在于如果你喜欢用它的默认值,你可以忽略参数。真棒!

解决方案?当你知道参数默认值后,它是如此的简单:

func flexStrings(s1: String = "", s2: String = "") -> String {
    return s1 + s2 == "" ? "none": s1 + s2
}

你定义了一个接受两个参数的函数,但是它们的默认值都为“”(空字符串)。你可以调用这个函数用如下方法:

  • 正常的2的参数。
  • 你传递1个参数,另外一个参数是默认值“”。
  • 没有参数,函数将都使用默认值。

所以在函数体里,你只需要检查是否两个参数都是空字符串(s1+s2 == ""),如果是就返回“none”,如果不是,就连接s1s2然后返回结果。看!所以要求都满足了。

完成一行代码的要求,你可以看到Objective-C的三元运算符?:也在Swift重现了。

试着在Playground中使用不同的参数调用这个函数 — 确定你理解了它是如何工作的。做完后给你自己一个1shuriken

挑战 #3

你已经在前面的挑战中掌握了使用可选参数的函数。相当有趣吧!

但是根据之前的方法,你只能有一个固定最大数量的参数。如果你想真的得到一个可变数量的输入参数的函数,这还有一个更好的方法。

这次挑战展示了如果更好的使用内建的Array方法和switch语句。你在读Apple的Swift Programming Language书时注意过吗?那里面讲过。:]

写一个命名为sumAny的函数,它接受任何类型的0或更多的参数。函数应满足如下要求:

  • 这个函数将根据下面的规则用String返回传递过来的参数值的和。
  • 如果参数是一个空的字符串或一个数值0,结果添加-10。
  • 如果参数是一个正数的字符串(比如“10″,“-5″则不符合),添加到结果中。
  • 如果参数是一个Int值,添加到结果中。
  • 如果是其他值,则忽略,不添加到结果中。
  • extrashuriken 额外的飞镖 – 函数使用一个return语句而且不使用任何循环(比如forwhile)。

这里是一些调用函数的示例和结果,你可以完成后检查你的函数:

let resultEmpty = sumAny() //--> "0"
let result1 = sumAny(Double(), 10, "-10", 2) //--> "12"
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内建的语言特性,所以在看最终的解决方案之前,让我们先来浏览一些概念。

首先来看一下如何定义一个接受可变数量参数的函数:

func sumAny(anys: Any...) -> String {
  //code
}

如果你在函数的参数后面加上“…”,Swift将接受0到多个这种类型的参数。当你指定Any...时,它意味着任何数量的任何类型的值可以传给这个函数。

你可以在函数中把上面例子中的anys当做一个普通的数组来对待。比如:

func sumAny(anys: Any...) -> String {
  return String(anys.count)
}

上面的代码返回了数组anys元素的数量,它也是传给函数的参数的数量。

下一步,你需要计算这些值的和了。你可以使用一个循环来遍历数组的值,然后用一个临时变量保存结果。但是用这种解决方案你就得不到额外的飞镖了 :]

你将使用一个不同的策略 — 使用Array.map((T)->(T))来(按要求)转化所有元素为Int值,然后使用Array.reduce(T, (T)->(T))来合计所有的值。

这里是转换每一个元素到需要合计的值的代码:

anys.map({item in
  switch item {
    case "" as String, 0 as Int:
      return -10
    case let s as String where s.toInt() > 0:
      return s.toInt()!
    case is Int:
      return item as Int
    default:
      return 0
  }
})

map迭代了数组中的每一个元素,然后闭包的函数处理它。改变元素或返回任何你想要的值是所有的选项。在上面闭包的函数中,你只添加了一个switch语句然后按指定的要求转换了每一个元素。

如果你不熟悉Swift中的switch进阶用法,这里有每一种用法的说明:

  1. 如果是一个空字符串“”或0,然后转换这种元素为值-10。
  2. 如果是一个字符串元素,而且转换为Int值后大于0 – 转换这种元素为它们的Int值。
  3. 如果是Int值返回它的值。
  4. 所有不匹配上面条件的数组中的其他元素将被转换为0。

好的!这个switch语句把你的随机类型的值全部转换成了整数。在你调用map之后,你实际上已经用一个Int数组替换了一个任何类型的数组。

获取数组元素的和是Array.reduce(T, (T)->(T))的完美应用。让我们看一下下面的示例中的函数是如何动作的:

[1,2,3].reduce(4) { result, item in
    return result + item
} //--> 10
  • 你定义了一个Array,包含元素1, 2和3。
  • 然后你用4做为起始值调用了reduce
  • reduce用4(result)和第一个元素1(item)调用了给定的闭包函数,闭包函数处理结果为5。
  • 接下来,reduce用结果5(之前的结果)和下一个数组中的元素(2)调用了闭包函数。
  • 这次的结果是7,所以当reduce用最后一个数组调用完函数然后添加3 – 最终结果则变为了10。

reduce是一个非常方便的函数 — 你不需要声明数组或变量,代码也看起来干净多了。

现在合并上面讨论的技术,产生了最终的解决方案:

func sumAny(anys: Any...) -> String {
    return String((anys.map({item in
        switch item {
        case "" as String, 0 as Int:
            return -10
        case let s as String where s.toInt() > 0:
            return s.toInt()!
        case is Int:
            return item as Int
        default:
            return 0
        }
        }) as [Int]).reduce(0) {
            $0 + $1
        })
}

这是怎么工作的,下面将一点一点说明:

  • 你使用map转换所有元素为它们需要合计的值。
  • 你转换map的结果为Int[],然后所有元素变为整数。
  • 你使用示例中的reduce合计了所有的元素。
  • 最后,你在返回之前把结果转换为String

你不需要使用任何循环或任何变量 — 只需要使用Array的内建函数。酷吧?

如果你学会并掌握了如何使用map和reduce,给你自己1个飞镖。

1shuriken

挑战 #4

写一个命名为countFrom(from:Int, #to:Int)的函数,它将输出(比如通过print()println())从fromto的数值。你不可以使用任何循环、变量或者任何内建的数组函数。假定from的值小于to(输入是有效的)。

下面是调用的示例和它的输出:

countFrom(1, to: 5) //--> 12345

3shurikens

解决方法:提示

使用递归函数,每次调用增加from的值,直到它与to参数的值相等。

2shurikens

解决方法:教程

本问题的解决方案将涉及递归。对于每一个从from开始的数值,你将递归调用countFrom同时每次给from的值增加1。当fromto相等时,你将停止递归。这将有效的把函数转换成一个简单的循环。

下面我们来看一下完成的解决方案:

func countFrom(from: Int, #to: Int) {
    print(from) // output to the assistant editor
    if from < to {
        countFrom(from + 1, to: to)
    }
}

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

下一步我们将去哪?

Get your revenge in part 2!

在第2节中得到复仇!

忍者也需要休息。如果你跟着本教程到了这里,你已经完成了很好的工作 -- 你应该休息一下了!

我想利用这个时间来考虑一下前面的问题和解决方案。Swift非常强大,而且比Objective-C更具表达性和安全性。

随着这次挑战中的最初4个问题,我想让你在Swift中的各种领域中浏览一下。我希望最初的几个问题到目前为止很有趣和你也得到了有益的经验。

让我们做个小总结:

  • 到现在为止,没有一个解决方案需要你去声明任何的变量 -- 你可能比你想像中更不需要它们!
  • 你也没有使用任何的循环。反思一下!
  • 具有默认值的参数和可变数量的参数让Swift的函数异常强大。
  • 了解强大的基本内建函数也是非常重要的,比如map, reduce, sort, countElements,等等。

如果你真的想放下编程,你可以看看开放的经典游戏忍者龙剑传的片段:

[video width="640" height="480" mp4="http://www.tairan.com/usr/uploads/2014/07/Nes-Ninja-Gaiden-Intro-YouTube_1.mp4"][/video]

敬请期待第二节的Swift的忍者编程挑战 - 你可以在那里得到复仇! :]

标签: Swift

?>