1 将数组中每个元素的值乘以 2
第一个例子中没什么干货,我们都知道只要使用map
函数就可以简单地解决问题:
let arr = (1...20).map{$0*2} print(arr)//[2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40]
2 求一组数字的和
这个问题可以通过使用reduce
方法和加号运算符解决,这是因为加号运算符实际上也是一个函数。不过这个解法是非常显而易见的,待会儿我们会看到reduce
方法更具有创造力的使用。
print((1...20).reduce(0,+))//210
3 证明字符串中含有某个单词
let words = ["Swift","iOS","cocoa","OSX","tvOS"] let tweet = "This is an example tweet larking about Swift" print(!words.filter({tweet.contains($0)}).isEmpty) //true print(words.contains(where: tweet.contains))// true let result = tweet.characters .split(separator: " ") .lazy .map(String.init) .contains(where: Set(words).contains) print(result)// true
4 读取一个文件
和其他语言不同,Swift 不能使用内建的函数读取文件,并把每一行存放到数组中。不过我们可以结合split
和map
方法写一段简短的代码,这样就无需使用for
循环:
let path = Bundle.main.path(forResource: "test",ofType: "txt") let lines = try? String(contentsOfFile: path!).characters.split{$0 == "\n"}.map(String.init) if let lin = lines { print(lin) }
5 祝你生日快乐
这段代码会将“祝你生日快乐”这首歌的歌词输出到控制台中,它在一段区间内简单的使用了map
函数,同时也用到了三元运算符。
let name = "uraimo" (1...4).forEach { print("Happy Birthday " + (($0 == 3) ? "dear \(name)":"to You")) }
Happy Birthday to You
Happy Birthday to You
Happy Birthday dear uraimo
Happy Birthday to You
6 数组过滤
假设我们需要使用一个给定的过滤函数将一个序列(sequence)分割为两部分。很多语言除了有常规的map
,flatMap
,reduce
,filter
等函数外,还有一个partitionBy
函数恰好可以完成这个需求。正如你所知,Swift 没有类似的函数(我们不想在这里使用NSArray中的函数,并通过NSPredicate实现过滤功能)。
所以,我们可以通过拓展SequenceType
,并为它添加partitionBy
函数来解决这个问题。我们使用这个函数将整数数组分割为两部分:
extension Sequence { typealias Element = Self.Iterator.Element func partitionBy(fu: (Element)->Bool)->([Element],[Element]){ var first=[Element]() var second=[Element]() for el in self { if fu(el) { first.append(el) }else{ second.append(el) } } return (first,second) } } let part = [82,58,76,49,88,90].partitionBy{$0 < 60} print(part) // ([58,49],[82,90])实际上,这不是单行代码,而且使用了命令式的解法。我们可以使用
filter
对它略作改进
extension Sequence { func anotherPartitionBy(fu: (Self.Iterator.Element)->Bool)->([Self.Iterator.Element],[Self.Iterator.Element]){ return (self.filter(fu),self.filter({!fu($0)})) } }
let part2 = [82,90].anotherPartitionBy{$0 < 60} print(part2) // ([58,90])
这种解法略好一些,但是他遍历了序列两次。而且为了用单行代码实现,我们删除了闭合函数,这会导致很多重复的内容(过滤函数和数组会在两处被用到)。
能不能只用单个数据流就对原来的序列进行转换,把两个部分分别存入一个元组中呢?答案是是可以的,使用reduce
方法:
let part3 = [82,90].reduce( ([],[]),{ (a:([Int],[Int]),n:Int) -> ([Int],[Int]) in (n<60) ? (a.0+[n],a.1) : (a.0,a.1+[n]) }) print(part3) // ([58,90])
这里我们创建了一个用于保存结果的元组,它包含两个部分。然后依次取出原来序列中的元素,根据过滤结果将它放到第一个或第二个部分中。
我们终于用真正的单行代码解决了这个问题。不过有一点需要注意,我们使用append
方法来构造两个部分的数组,所以这实际上比前两种实现慢一些。
7 找到数组中最小(或最大)的元素
我们有多种方式求出 sequence 中的最大和最小值,其中一种方式是使用minElement
和maxElement
函数:
let aaa = [10,-22,753,55,137,-1,-279,1034,77] //Find the minimum of an array of Ints print(aaa.sorted().first ?? 0)//-279 print(aaa.reduce(Int.max,min))//-279 print(aaa.min() ?? 0) //-279 //Find the maximum of an array of Ints print(aaa.sorted().last ?? 0)//1034 print(aaa.reduce(Int.min,max))//1034 print(aaa.max() ?? 0)//1034
8 埃拉托色尼选筛法(就是求小于N的所有质数)
古老而优秀的埃拉托色尼选筛法被用于找到所有小于给定的上限 n 的质数。
首先将所有小于 n 的整数都放入一个序列(sequence)中,这个算法会移除每个数字的倍数,直到剩下的所有数字都是质数。为了加快执行速度,我们其实不必检查每一个数字的倍数,当检查到 n 的平方根时就可以停止。
基于以上定义,最初的实现可能是这样的:
let n = 50 var primes = Set(2...n) (2...Int(sqrt(Double(n)))).forEach { let _ = primes.subtract(stride(from: 2*$0,through: n,by: $0)) } print(primes.sorted())
结果是: [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47]
在外层的区间里,我们遍历每一个需要检查的数字。对于每一个数字,我们使用stride(through:Int by:Int)
函数计算出由它的倍数构成的序列。最初,我们用所有 2 到 n 的整数构造了一个集合(Set),然后从集合中减掉每一个生成的序列中的元素。
不过正如你所见,为了真正的删除掉这些倍数,我们使用了一个外部的可变集合,这会带来副作用。
我们总是应该尝试消除副作用,所以我们先计算所有的子序列,然后调用flatMap
方法将其中所有的元素展开,存放到单个数组中,最后再从原始的集合中删除这些整数。
let n = 50 var sameprimes = Set(2...n) sameprimes.subtract((2...Int(sqrt(Double(n)))).flatMap{ stride(from: 2*$0,by: $0) }) print(sameprimes.sorted())结果是: [2,47]
9 福利:使用析构交换元组中的值
和其他具有元组类型的语言一样,Swift 的元组可以被用来交换两个变量的值,代码很简洁:
var a1 = 1,b1 = 2 (a1,b1) = (b1,a1) print(a1,b1)// 2 1