令人惊叹的swift

2016-12-11  本文已影响16人  jetson

原文链接

几年前,函数式编程突然火了起来,一篇10 Scala functional one liners became文章迅速火了起来,同时也涌现出很多类似的的文章(Haskell,Python,CoffeScript)

我不知道有多少人对这些代码段留下了深刻的影响,但是我觉得这些例子可以让初学者领略到函数式编程的魅力

每个元素乘以2

这个没什么可以说的,用map可以很容易实现

(1...1024).map{$0 * 2}
列表求和

这个例子同样很简单,用reduce和+操作符即可完成(加号操作符其实也是一个函数)

(1...1024).reduce(0,combine: +)

(PS:下面的的代码讲逐步展示swift作为一个现代语言所拥有的强大魔力)

判断是否包含字符串

我们来判断下一个句子是否包含特定的字符

let words = ["Swift","iOS","cocoa","OSX","tvOS"] 
let tweet = "This is an example tweet larking about Swift" 
let valid = !words.filter({tweet.containsString($0)}).isEmpty
 valid //true

@oisdk提供一个更加简洁的方法

words.contains(tweet.containsString)

还有更绝的

tweet.characters 
.split(" ")
.lazy 
.map(String.init) 
.contains(Set(words).contains)

(PS:有关swift中lazy很好解释的一个文章以及这个简而言之就是Lazy方法返回一个可以用来筛选或者映射的序列或者集合类型,并且不会产生任何中间数组,使用这些结果作为另一个处理程序的输入。
也就是Lazy Evaluation。不同于OC中的lazyload)

读取一个文件

通过一些自带的标准库直接把文件读取到一个数组中在其他语言中几乎是不可能的,但是我们可以用splitmap来简化这个工作。

let path = NSBundle.mainBundle().pathForResource("test", ofType: "txt") 
let lines = try? String(contentsOfFile: path!).characters.split{$0 == "\n"}.map(String.init) 
if let lines=lines { 
lines[0] 
lines[1]
lines[2] 
lines[3] 
 }
祝你生日快乐

我们讲会给你展示一首生日歌

let name = "uraimo" 
(1...4).map{print("Happy Birthday " + (($0 == 3) ? "dear \(name)":"to You"))}
过滤列表

在这个案例中我们被要求按照一定的条件来划分数组,我们将会扩展SequenceType这个协议来完成这个任务。

extension SequenceType{
 typealias Element = Self.Generator.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} 
  part // ([58, 49], [82, 76, 88, 90])

恩~这还不够震撼

extension SequenceType{ 
func anotherPartitionBy(fu: (Self.Generator.Element)->Bool)->([Self.Generator.Element],[Self.Generator.Element]){
 return (self.filter(fu),self.filter({!fu($0)})) } } 
 let part2 = [82, 58, 76, 49, 88, 90].anotherPartitionBy{$0 < 60}
  part2 
  // ([58, 49], [82, 76, 88, 90])

This is slightly better, but it traverses the sequence two times and trying to turn this into a one liner removing the enclosing function will get us something with too much duplicated stuff (the filtering function and the array that will be used in two places.

Can we build something that will transform the original sequence into a partition tuple using a single stream of data? Yes we can, using reduce.

var part3 = [82, 58, 76, 49, 88, 90]
.reduce( ([],[]), combine: {
 (a:([Int],[Int]),n:Int) -> ([Int],[Int]) in (n<60) ? (a.0+[n],a.1) : (a.0,a.1+[n]) }) 
 part3 // ([58, 49], [82, 76, 88, 90])

(这一段我不能很准确的描述,我个人理解是map,flatmapfilter这种链式操作会造成极大的性能损失,浪费了CPU周期,对集合不断的进行重复无意义的访问。但是在数据量巨大的情况下reduce性能反而只有map的百分之一(没错,就是百分之一),原因是某些情况下reduce会对底层序列的每个元素都产生一份 copy,相关文章链接)

查找最大值最小值

恩..想起来孔乙己中字有几种写法梗了


//Find the minimum of an array of Ints [10,-22,753,55,137,-1,-279,1034,77].sort().first [10,-22,753,55,137,-1,-279,1034,77].reduce(Int.max, combine: min) [10,-22,753,55,137,-1,-279,1034,77].minElement() //Find the maximum of an array of Ints 

[10,-22,753,55,137,-1,-279,1034,77].sort().last 

[10,-22,753,55,137,-1,-279,1034,77].reduce(Int.min, combine: max) [10,-22,753,55,137,-1,-279,1034,77].maxElement()
并行计算

一些语言允许用flatmap和map来产生一个简单透明的并行计算方式,但是swift还不能这么干,swift采用了基于C的GCD库 链接

(补充一个在gist看到的一个对于GCD一个很好的封装)

protocol ExcutableQueue {
    var queue: dispatch_queue_t { get }
}

extension ExcutableQueue {
    func execute(closure: () -> Void) {
        dispatch_async(queue, closure)
    }
}

enum Queue: ExcutableQueue {
    case Main
    case UserInteractive
    case UserInitiated
    case Utility
    case Background

    var queue: dispatch_queue_t {
        switch self {
        case .Main:
            return dispatch_get_main_queue()
        case .UserInteractive:
            return dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0)
        case .UserInitiated:
            return dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)
        case .Utility:
            return dispatch_get_global_queue(QOS_CLASS_UTILITY, 0)
        case .Background:
            return dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0)
        }
    }
}

enum SerialQueue: String, ExcutableQueue {
    case DownLoadImage = "myApp.SerialQueue.DownLoadImage"
    case UpLoadFile = "myApp.SerialQueue.UpLoadFile"

    var queue: dispatch_queue_t {
        return dispatch_queue_create(rawValue, DISPATCH_QUEUE_SERIAL)
    }
}
//包装后的GCD将会是这样,链式调用将会使得代码变得更加清楚
let url = NSURL(string: "http://image.jpg")!
    let data = NSData(contentsOfURL: url)!
    let image = UIImage(data: data)

    Queue.Main.execute {
        imageView.image = image
    }
}

素数筛

依稀好记得本科学C语言时候老师让我们用C语言写一个素数筛,那一个个循环好痛苦

要求:给定的n为集合上限,返回集合中的素数

var n = 50
var primes = Set(2...n)
(2...Int(sqrt(Double(n)))).forEach{primes.subtractInPlace((2*$0).stride(through:n, by:$0))}
primes.sort()

We use the outer range to iterate over the integers we want to check and for each one we calculate a sequence of multiples of those numbers using stride(through:Int by:Int). Those sequences are then substracted from a Set initialized with all the integers from 2 to n.

But as you can see, to actually remove the multiples we use an external mutable Set, introducing a side-effect.

To eliminate side-effects, as we should always try to do, we will first calculate all the subsequences, flatMap them in a single array of multiples and remove these integers from the original Set.
(PS:由于本人的辣鸡水平,这段只能再次丢上英文了,我实在不知道怎么用中文表达出来,其中这段英文中的关键词side-effect翻译为"副作用",指的是函数内部与外部互动(最典型的情况,就是修改全局变量的值),产生运算以外的其他结果。
函数式编程强调没有"副作用",意味着函数要保持独立,所有功能就是返回一个新的值,没有其他行为,尤其是不得修改外部变量的值。其余的只能自行脑补,我就不误导了)

var sameprimes = Set(2...n) sameprimes.subtractInPlace((2...Int(sqrt(Double(n)))) .flatMap{ (2*$0).stride(through:n, by:$0)}) 
sameprimes.sort()

这里有一个关于flatmap很好的解释[usage of flatMap to flatten nested arrays](usage of flatMap to flatten nested arrays),
中文文章(有点抽象),以及一个通俗易懂的文章还有一个用haskell解释的中文文章

用元组来交换数据
var a=1,b=2 
(a,b) = (b,a) 
a //2 b //1

上一篇下一篇

猜你喜欢

热点阅读