map、flatMap、filter和reduce,几乎实现lambda表达式的语言里都会在集合里增长这些方法,javascript
见swift 学习(一)基础知识 (基本数据类型,操做符,流控制,集合)中的集合html
经过map
实现元素的映射,好处是咱们能够很是清楚的表示两个元素列表做了何种转换,实现起来更简单,却有更大的信噪比。减轻咱们理解代码的难度。github
func map<U>(transform: (T) -> U) -> U[]
它接受一个函数叫作 transform
,而后依次对原数组里的每个元素调用该函数,函数返回值组成另外一个数组:编程
[ x1, x2, ... , xn].map(f) -> [f(x1), f(x2), ... , f(xn)]
// foreach 表示 var newArray : Array<T> = [] for item in oldArray { newArray += f(item) }
示例swift
var oldArray = [10,20,45,32] var newArray = oldArray.map({"\($0)€"}) println(newArray) // [¥10, ¥20, ¥45, ¥32]
func hello(someName: String?) -> String { return someName.map { name in "Hello, \(name)" } ?? "Hello world!" }
map是一个高级函数,并不只仅对数组有意义。它能够在任何类型和方法中实现,包括一种或多种映射方式,一个或多个映射关系。设计模式
let number = Optional(815) let transformedNumber = number.map { $0 * 2 }.map { $0 % 2 == 0 } // transformedNumber: Optional.Some(true)
在Optional中使用map函数的好处是,它将为咱们自动处理空值,若是咱们试图在一个nil值处进行操做,能够先使用optional.map申请转换,若是原来为空的话,最终也将为空,这就能够避免使用if let嵌套打开Optional。 api
let nilNumber: Int? = .None let transformedNilNumber = nilNumber.map { $0 * 2 }.map { $0 % 2 == 0 } // transformedNilNumber: None
map函数在针对不一样的类型时能够有不一样的行为,这主要取决于该类型的语义。数组
class Box< T> { let unbox: T init(_ value: T) { self.unbox = value } } enum Result< T> { case Value(Box< T>) case Error(NSError) }
该Box类用来绕过当前Swift版本的一处限制(unimplemented IR generation feature non-fixed multi-payload enum layout)。
这是一种在一些语言中被称为Either的实现模式,只有在这种状况下,咱们必须使用一个NSError类来代替它,由于咱们须要用它来报告咱们的操做结果。
从概念上来说,Result与Optional是很是类似的:能够适用于任意类型的值,不管它是否有意义,然而在这种状况下,Result能够告诉咱们无心义的值是什么,且为何存在。
看下面的例子,读取一个文件的内容,做为Result对象的返回结果:
func dataWithContentsOfFile(file: String, encoding: NSStringEncoding) -> Result { var error: NSError? if let data = NSData(contentsOfFile: file, options: .allZeros, error: &error) { return .Value(Box(data)) } else { return .Error(error!) } }
这个函数将返回一个NSData对象,或一个NSError告知文件没法读取。
若是在之前,咱们可能为了读出这些值,须要作一些转换。而且须要检测每一步转换的值是否正确,这可能会致使咱们须要使用一些繁琐的if let 或switch嵌套来检测。在这种状况下,咱们只须要提供转换方法,若是不这么作,咱们也能够传递相同的error。
假设咱们要读取一个字符串的内容,咱们会获得一个NSData,而后咱们须要转化成一个字符串,以后咱们将它变成大写:
NSData -> String -> String
let data: Result< NSData> = dataWithContentsOfFile(path, NSUTF8StringEncoding) let uppercaseContents: Result = data.map { NSString(data: $0, encoding: NSUTF8StringEncoding)! }.map { $0.uppercaseString }
这相似于上面使用map函数处理数组的例子,咱们只须要描述清楚想要完成的目标便可。
相比之下,下面这份代码是不使用map函数:
let data: Result< NSData> = dataWithContentsOfFile(path, NSUTF8StringEncoding) var stringContents: String? switch data { case let .Value(value): stringContents = NSString(data: value.unbox, encoding: NSUTF8StringEncoding) case let .Error(error): break } let uppercaseContents: String? = stringContents?.uppercaseString
Result.map函数:
extension Result { func map< U>(f: T -> U) -> Result< U> { switch self { case let .Value(value): return Result< U>.Value(Box(f(value.unbox))) case let .Error(error): return Result< U>.Error(error) } } }
比map更上一层楼
var oldArray = [10,20,45,32] var newArray = oldArray.flatMap{ ["¥\($0)","$\($0 )"] } println(newArray) // [¥10, $10,¥20, $20, ¥45, $45, ¥32, $32]
相对的,java8中flatMap 的实现方式有很大不一样,java8中,被处理数据是n组元素,处理方法只有一个闭包,一样的结果是组合为一个集合,但组合的次序不同,是依次处理每一组元素的个体,并纳入同一个组。而swift是对一个数组的每一个元素进行多种处理,并纳入同一个组。
实际上就是对map的扩展,要求在实现元素的映射时,映射的结果一样是一个能够继续映射的类型。
extension Result { static func flatten< T>(result: Result< Result< T>>) -> Result< T> { switch result { case let .Value(innerResult): return innerResult.unbox case let .Error(error): return Result< T>.Error(error) } } }
extension Result { func flatMap< U>(f: T -> Result< U>) -> Result< U> { return Result.flatten(map(f)) } }
filter
就是筛选的功能,参数是一个用来判断是否筛除的筛选闭包func filter(includeElement: (T) -> Bool) -> [T]
// 传统的 foreach 实现的方法: var oldArray = [10,20,45,32] var filteredArray : Array<Int> = [] for money in oldArray { if (money > 30) { filteredArray.append(money ) } } // 用 filter 能够这样实现: var oldArray = [10,20,45,32] var filteredArray = oldArray.filter({ return $0 > 30 }) println(filteredArray) // [45, 32]
reduce
函数解决了把数组中的值整合到某个独立对象的问题func reduce<U>(initial: U, combine: (U, T) -> U) -> U
好比咱们要把数组中的值都加起来放到 sum
里
// foreach 实现 var oldArray = [10,20,45,32] var sum = 10 for money in oldArray { sum = sum + money } println(sum) // 117 // reduce var oldArray = [10,20,45,32] var sum = oldArray.reduce(10,{$0 + $1})
Write a function applyTwice(f:(Float -> Float),x:Float) -> Float that takes a function f and a float x and aplies f to x twice i.e. f(f(x))
func applyTwice(f:(Float -> Float),x:Float) -> Float { return f(f(x)) }
Write a function applyKTimes(f:(Float -> Float),x:Float,k:Int) -> Float that takes a function f and a float x and aplies f to x k times
// recursive version func applyKTimes(f:(Float -> Float), x:Float, k:Int) -> Float { return k > 0 ? applyKTimes(f, f(x), k - 1) : x } // unrolled by hand func applyKTimes(f:(Float -> Float),x:Float,k:Int) -> Float { var y : Float = x for _ in 0..<k { y = f(y) } return y }
Using applyKTimes write a function that raises x to the kth power
func getKthPower(x:Float, k:Int) -> Float{ return applyKTimes( {x * $0}, 1, k) } getKthPower(2.0, 3) // 8.0
Given an array of Users which have properties name:String and age:Int write a map function that returns an array of strings consisting of the user’s names
class User { var name: String? var age : Int? init (name: String, age:Int) { self.name = name self.age = age } } var user1 = User(name: "WHY1", age: 22) var user2 = User(name: "WHY2", age: 23) var user3 = User(name: "WHY3", age: 24) var user4 = User(name: "WHY4", age: 25) var users = [user1,user2,user3,user4] var names: [String] = [] users.map({ (user:User) in names.append(user.name!) }) println(names) // [WHY1, WHY2, WHY3, WHY4]
Given an array of of dictionaries containing keys for “name” and “age” write a map function that returns an array of users created from it
var users = [ ["name":"WHY1","age":"22"], ["name":"WHY2","age":"23"], ["name":"WHY3","age":"24"], ["name":"WHY4","age":"25"] ] var result = users.map({ (userDic:[String:String]) -> User in return User(name: userDic["name"]!, age:userDic["age"]!.toInt()!) })
Given an array of numbers write a filter method that only selects odd integers
var nums = [1,2,4,8,23,45,89,127] var odds = nums.filter({ $0 % 2 == 0 }) // 2 4 8
Given an array of strings write a filter function that selects only strings that can be converted to Ints
var strs = ["2333","1223","callmewhy","callherhh"] var intables = strs.filter({ $0.toInt() != nil }) // ["2333", "1223"]
Given an array of UIViews write a filter function that selects only those views that are a subclass of UILabel
import UIKit var view1 = UIView() var view2 = UIView() var view3 = UILabel() var view4 = UIView() var views = [view1,view2,view3,view4] var labels = views.filter({ $0.isKindOfClass(UILabel) }) // view3
Write a reduce function that takes an array of strings and returns a single string consisting of the given strings separated by newlines
var strs = ["str1","str2","str3","str4"] var str = strs.reduce("", combine: { "\($0)\n\($1)" }) println(str)
Write a reduce function that finds the largest element in an array of Ints
var ints = [1,2,3,4,5,6] var maxValue = ints.reduce(Int.min, { max($0, $1) }) // 6
You could implement a mean function using the reduce operation {$0 + $1 / Float(array.count)}. Why is this a bad idea?
var array = [1,2,3,4,6] var mean = array.reduce( 0, combine: {$0 + Float($1) / Float(array.count)} ) // make division 5 times
There’s a problem you encounter when trying to implement a parallel version of reduce. What property should the operation have to make this easier ?
// TODO
Implement Church Numerals in Swift (This is a difficult and open ended exercise)
// TODO
在过去的时间里,人们对于设计 API 总结了不少通用的模式和最佳实践方案。通常状况下,咱们老是能够从苹果的 Foundation、Cocoa、Cocoa Touch 和不少其余框架中总结出一些开发中的范例。毫无疑问,对于“特定情境下的 API 应该如何设计”这个问题,不一样的人老是有着不一样的意见,对于这个问题有很大的讨论空间。不过对于不少 Objective-C 的开发者来讲,对于那些经常使用的模式早已习觉得常。
随着 Swift 的出现,设计 API 引发了更多的问题。绝大多数状况下,咱们只能继续作着手头的工做,而后把现有的方法翻译成 Swift 版本。不过,这对于 Swift 来讲并不公平,由于和 Objective-C 相比,Swift 添加了不少新的特性。引用 Swift 创始人 Chris Lattner的一段话:
Swift 引入了泛型和函数式编程的思想,极大地扩展了设计的空间。
在这篇文章里,咱们将会围绕 Core Image
进行 API 封装,以此为例,探索如何在 API 设计中使用这些新的工具。 Core Image
是一个功能强大的图像处理框架,可是它的 API 有时有点笨重。 Core Image
的 API 是弱类型的 - 它经过键值对 (key-value) 设置图像滤镜。这样在设置参数的类型和名字时很容易失误,会致使运行时错误。新的 API 将会十分的安全和模块化,经过使用类型而不是键值对来规避这样的运行时错误。
咱们的目标是构建一个 API ,让咱们能够简单安全的组装自定义滤镜。举个例子,在文章的结尾,咱们能够这样写:
let myFilter = blur(blurRadius) >|> colorOverlay(overlayColor) let result = myFilter(image)
上面构建了一个自定义的滤镜,先模糊图像,而后再添加一个颜色蒙版。为了达到这个目标,咱们将充分利用 Swift 函数是一等公民这一特性。项目源码能够在 Github 上的这个示例项目中下载。
CIFilter
是 Core Image
中的一个核心类,用来建立图像滤镜。当实例化一个 CIFilter
对象以后,你 (几乎) 老是经过kCIInputImageKey
来输入图像,而后经过 kCIOutputImageKey
获取返回的图像,返回的结果能够做为下一个滤镜的参数输入。
在咱们即将开发的 API 里,咱们会把这些键值对 (key-value) 对应的真实内容抽离出来,为用户提供一个安全的强类型 API。咱们定义了本身的滤镜类型 Filter
,它是一个能够传入图片做为参数的函数,而且返回一个新的图片。
typealias Filter = CIImage -> CIImage
这里咱们用 typealias
关键字,为 CIImage -> CIImage
类型定义了咱们本身的名字,这个类型是一个函数,它的参数是一个CIImage
,返回值也是 CIImage
。这是咱们后面开发须要的基础类型。
若是你不太熟悉函数式编程,你可能对于把一个函数类型命名为 Filter
感受有点奇怪,一般来讲,咱们会用这样的命名来定义一个类。若是咱们很想以某种方式来表现这个类型的函数式的特性,咱们能够把它命名成 FilterFunction
或者一些其余的相似的名字。可是,咱们有意识的选择了 Filter
这个名字,由于在函数式编程的核心哲学里,函数就是值,函数和结构体、整数、多元组、或者类,并无任何区别。一开始我也不是很适应,不过一段时间以后发现,这样作确实颇有意义。
如今咱们已经定义了 Filter
类型,接下来能够定义函数来构建特定的滤镜了。这些函数须要参数来设置特定的滤镜,而且返回一个类型为 Filter
的值。这些函数大概是这个样子:
func myFilter(/* parameters */) -> Filter
注意返回的值 Filter
自己就是一个函数,在后面有利于咱们将多个滤镜组合起来,以达到理想的处理效果。
为了让后面的开发更轻松一点,咱们扩展了 CIFilter
类,添加了一个 convenience 的初始化方法,以及一个用来获取输出图像的计算属性:
typealias Parameters = Dictionary<String, AnyObject> extension CIFilter { convenience init(name: String, parameters: Parameters) { self.init(name: name) setDefaults() for (key, value : AnyObject) in parameters { setValue(value, forKey: key) } } var outputImage: CIImage { return self.valueForKey(kCIOutputImageKey) as CIImage } }
这个 convenience 初始化方法有两个参数,第一个参数是滤镜的名字,第二个参数是一个字典。字典中的键值对将会被设置成新滤镜的参数。咱们 convenience 初始化方法先调用了指定的初始化方法,这符合 Swift 的开发规范。
计算属性 outputImage
能够方便地从滤镜对象中获取到输出的图像。它查找 kCIOutputImageKey
对应的值而且将其转换成一个CIImage
对象。经过提供这个属性, API 的用户再也不须要对返回的结果手动进行类型转换了。
有了这些东西,如今咱们就能够定义属于本身的简单滤镜了。高斯模糊滤镜只须要一个模糊半径做为参数,咱们能够很是容易的完成一个模糊滤镜:
func blur(radius: Double) -> Filter { return { image in let parameters : Parameters = [kCIInputRadiusKey: radius, kCIInputImageKey: image] let filter = CIFilter(name:"CIGaussianBlur", parameters:parameters) return filter.outputImage } }
就是这么简单,这个模糊函数返回了一个函数,新的函数的参数是一个类型为 CIImage
的图片,返回值 (filter.outputImage
) 是一个新的图片 。这个模糊函数的格式是 CIImage -> CIImage
,知足咱们前面定义的 Filter
类型的格式。
这个例子只是对 Core Image
中已有滤镜的一个简单的封装,咱们能够屡次重复一样的模式,建立属于咱们本身的滤镜函数。
如今让咱们定义一个颜色滤镜,能够在现有的图片上面加上一层颜色蒙版。 Core Image
默认没有提供这个滤镜,不过咱们能够经过已有的滤镜组装一个。
咱们使用两个模块来完成这个工做,一个是颜色生成滤镜 (CIConstantColorGenerator
),另外一个是资源合成滤镜 (CISourceOverCompositing
)。让咱们先定义一个生成一个常量颜色面板的滤镜:
func colorGenerator(color: UIColor) -> Filter { return { _ in let filter = CIFilter(name:"CIConstantColorGenerator", parameters: [kCIInputColorKey: color]) return filter.outputImage } }
这段代码看起来和前面的模糊滤镜差很少,不过有一个较为明显的差别:颜色生成滤镜不会检测输入的图片。因此在函数里咱们不须要给传入的图片参数命名,咱们使用了一个匿名参数 _
来强调这个 filter 的图片参数是被忽略的。
接下来,咱们来定义合成滤镜:
func compositeSourceOver(overlay: CIImage) -> Filter { return { image in let parameters : Parameters = [ kCIInputBackgroundImageKey: image, kCIInputImageKey: overlay ] let filter = CIFilter(name:"CISourceOverCompositing", parameters: parameters) return filter.outputImage.imageByCroppingToRect(image.extent()) } }
在这里咱们将输出图像裁剪到和输入大小同样。这并非严格须要的,要取决于咱们想让滤镜如何工做。不过,在后面咱们的例子中咱们能够看出来这是一个明智之举。
func colorOverlay(color: UIColor) -> Filter { return { image in let overlay = colorGenerator(color)(image) return compositeSourceOver(overlay)(image) } }
咱们再一次返回了一个参数为图片的函数,colorOverlay
在一开始先调用了 colorGenerator
滤镜。colorGenerator
滤镜须要一个颜色做为参数,而且返回一个滤镜。所以 colorGenerator(color)
是 Filter
类型的。可是 Filter
类型自己是一个CIImage
向 CIImage
转换的函数,咱们能够在 colorGenerator(color)
后面加上一个类型为 CIImage
的参数,这样能够获得一个类型为 CIImage
的蒙版图片。这就是在定义 overlay
的时候发生的事情:咱们用 colorGenerator
函数建立了一个滤镜,而后把图片做为一个参数传给了这个滤镜,从而获得了一张新的图片。返回值 compositeSourceOver(overlay)(image)
和这个基本类似,它由一个滤镜 compositeSourceOver(overlay)
和一个图片参数 image
组成。
如今咱们已经定义了一个模糊滤镜和一个颜色滤镜,咱们在使用的时候能够把它们组合在一块儿:咱们先将图片作模糊处理,而后再在上面放一个红色的蒙层。让咱们先加载一张图片:
let url = NSURL(string: "http://tinyurl.com/m74sldb"); let image = CIImage(contentsOfURL: url)
如今咱们能够把滤镜组合起来,同时应用到一张图片上:
let blurRadius = 5.0 let overlayColor = UIColor.redColor().colorWithAlphaComponent(0.2) let blurredImage = blur(blurRadius)(image) let overlaidImage = colorOverlay(overlayColor)(blurredImage)
咱们又一次的经过滤镜组装了图片。好比在倒数第二行,咱们先获得了模糊滤镜 blur(blurRadius)
,而后再把这个滤镜应用到图片上。
不过,咱们能够作的比上面的更好。咱们能够简单的把两行滤镜的调用组合在一块儿变成一行,这是我脑海中想到的第一个能改进的地方:
let result = colorOverlay(overlayColor)(blur(blurRadius)(image))
不过,这些圆括号让这行代码彻底不具备可读性,更好的方式是定义一个函数来完成这项任务:
func composeFilters(filter1: Filter, filter2: Filter) -> Filter { return { img in filter2(filter1(img)) } }
composeFilters
函数的两个参数都是 Filter ,而且返回了一个新的 Filter 滤镜。组装后的滤镜须要一个 CIImage
类型的参数,而且会把这个参数分别传给 filter1
和 filter2
。如今咱们能够用 composeFilters
来定义咱们本身的组合滤镜:
let myFilter = composeFilters(blur(blurRadius), colorOverlay(overlayColor)) let result = myFilter(image)
咱们还能够更进一步的定义一个滤镜运算符,让代码更具备可读性,
infix operator >|> { associativity left } func >|> (filter1: Filter, filter2: Filter) -> Filter { return { img in filter2(filter1(img)) } }
运算符经过 infix
关键字定义,代表运算符具备 左
和 右
两个参数。associativity left
代表这个运算知足左结合律,即:f1 >|> f2 >|> f3 等价于 (f1 >|> f2) >|> f3。经过使这个运算知足左结合律,再加上运算内先应用了左侧的滤镜,因此在使用的时候滤镜顺序是从左往右的,就像 Unix 管道同样。
剩余的部分是一个函数,内容和 composeFilters
基本相同,只不过函数名变成了 >|>
。
接下来咱们把这个组合滤镜运算器应用到前面的例子中:
let myFilter = blur(blurRadius) >|> colorOverlay(overlayColor) let result = myFilter(image)
运算符让代码变得更易于阅读和理解滤镜使用的顺序,调用滤镜的时候也更加的方便。就比如是 1 + 2 + 3 + 4
要比add(add(add(1, 2), 3), 4)
更加清晰,更加容易理解。
不少 Objective-C 的开发者对于自定义运算符持有怀疑态度。在 Swift 刚发布的时候,这是一个并无很受欢迎的特性。不少人在 C++ 中遭遇过自定义运算符过分使用 (甚至滥用) 的状况,有些是我的经历过的,有些是听到别人谈起的。
你可能对于前面定义的运算符 >|>
持有一样的怀疑态度,毕竟若是每一个人都定义本身的运算符,那代码岂不是很难理解了?值得庆幸的是在函数式编程里有不少的操做,为这些操做定义一个运算符并非一件很罕见的事情。
咱们定义的滤镜组合运算符是一个函数组合的例子,这是一个在函数式编程中普遍使用的概念。在数学里,两个函数 f
和 g
的组合有时候写作 f ∘ g
,这样定义了一种全新的函数,将输入的 x
映射到 f(g(x))
上。这刚好就是咱们的 >|>
所作的工做 (除了函数的逆向调用)。
仔细想一想,其实咱们并无必要去定义一个用来专门组装滤镜的运算符,咱们能够用一个泛型的运算符来组装函数。目前咱们的 >|>
是这样的:
func >|> (filter1: Filter, filter2: Filter) -> Filter
这样定义以后,咱们传入的参数只能是 Filter
类型的滤镜。
可是,咱们能够利用 Swift 的通用特性来定义一个泛型的函数组合运算符:
func >|> <A, B, C>(lhs: A -> B, rhs: B -> C) -> A -> C { return { x in rhs(lhs(x)) } }
这个一开始可能很难理解 -- 至少对我来讲是这样。可是分开的看了各个部分以后,一切都变得清晰起来。
首先,咱们来看一下函数名后面的尖括号。尖括号定义了这个函数适用的泛型类型。在这个例子里咱们定义了三个类型:A、B 和 C。由于咱们并无指定这些类型,因此它们能够表明任何东西。
接下来让咱们来看看函数的参数:第一个参数:lhs (left-hand side 的缩写),是一个类型为 A -> B 的函数。这表明一个函数的参数为 A,返回值的类型为 B。第二个参数:rhs (right-hand side 的缩写),是一个类型为 B -> C 的函数。参数命名为 lhs 和 rhs,由于它们分别对应操做符左边和右边的值。
重写了没有 Filter
的滤镜组合运算符以后,咱们很快就发现其实前面实现的组合运算符只是泛型函数中的一个特殊状况:
func >|> (filter1: CIImage -> CIImage, filter2: CIImage -> CIImage) -> CIImage -> CIImage
把咱们脑海中的泛型类型 A、B、C 都换成 CIImage
,这样能够清晰的理解用通用运算符的来替换滤镜组合运算符是多么的有用。
至此,咱们成功的用函数式 API 封装了 Core Image
。但愿这个例子可以很好的说明,对于 Objective-C 的开发者来讲,在咱们所熟知的 API 的设计模式以外有一片彻底不一样的世界。有了 Swift,咱们如今能够动手探索那些全新的领域,而且将它们充分地利用起来。