2019-07-24 23:04:35 kyl282889543 阅读数 149

swift常用高阶函数

swift中比较常用的高阶函数有:map、flatMap、filter、reduce
更多swift相关教程可以参考极客学院文章

1. map

map可以对数组中的每一个元素做一次处理

  1. 先通过下面实例代码了解一下map的用法
// 计算字符串的长度
let stringArray = ["Objective-C", "Swift", "HTML", "CSS", "JavaScript"]
func stringCount(string: String) -> Int {
    return string.characters.count
}
stringArray.map(stringCount)

stringArray.map({string -> Int in
    return string.characters.count
})

// $0代表数组中的每一个元素
stringArray.map{
    return $0.characters.count
}
  1. 我们来查看一下map在swift中的定义
    我们看到它可以用在 Optionals 和 SequenceType 上(如:数组、词典等)。
    代码:
public enum Optional<Wrapped> : _Reflectable, NilLiteralConvertible {
    /// If `self == nil`, returns `nil`.  Otherwise, returns `f(self!)`.
    @warn_unused_result
    public func map<U>(@noescape f: (Wrapped) throws -> U) rethrows -> U?
}

extension CollectionType {
    /// Returns an `Array` containing the results of mapping `transform`
    /// over `self`.
    ///
    /// - Complexity: O(N).
    @warn_unused_result
    public func map<T>(@noescape transform: (Self.Generator.Element) throws -> T) rethrows -> [T]
}

源码中的一些关键字在这里介绍一下:
@warn_unused_result:表示如果没有检查或者使用该方法的返回值,编译器就会报警告。
@noescape:表示transform这个闭包是非逃逸闭包,它只能在当前函数map中执行,不能脱离当前函数执行。这使得编译器可以明确的知道运行时的上下文环境(因此,在非逃逸闭包中可以不用写self),进而进行一些优化

  1. 对 Optionals进行map操作
    简要的说就是,如果这个可选值有值,那就解包,调用这个函数,之后返回一个可选值,需要注意的是,返回的可选值类型可以与原可选值类型不一致:
///原来类型: Int?,返回值类型:String?
var value:Int? = 1
var result = value.map { String("result = \($0)") }
/// "Optional("result = 1")"
print(result)


var value:Int? = nil
var result = value.map { String("result = \($0)") }
/// "nil"
print(result)
  1. 对SequenceType进行map操作

我们可以使用map方法遍历数组中的所有元素,并对这些元素一一进行一样的操作(函数方法)。map方法返回完成操作后的数组。

在这里插入图片描述
我们可以对比一下用传统的For-in操作和map操作:

1. 传统写法:
var values = [1,3,5,7]
var results = [Int]()
for var value in values {
    value *= 2
    results.append(value)
}
//"[2, 6, 10, 14]"
print(results)
//传统的For-in实现起来代码很多,不简介,而且效率没有高阶函数高。

2. 使用map高阶函数
let results = values.map ({ (element) -> Int in
    return element * 2
})
//"[2, 6, 10, 14]"

此外还有更加精简的写法:
let results = values.map { $0 * 2 }
//"[2, 6, 10, 14]"
3. 

通过上面的代码对比,我们可以看出高阶函数精简写法是多么的优雅,就像写诗一样。下面我们来探究一下怎么就精简这么短小了呢,连return语句都不需要了

  • 第一步
    由于闭包的函数体很短,所以我们将其改写成一行:
let results = values.map ({ (element) -> Int in return element * 2 })
//"[2, 6, 10, 14]"

  • 第二步
    由于我们的闭包是作为map的参数传入的,系统可以推断出其参数与返回值,因为其参数必须是(Element) -> Int类型的函数。因此,返回值类型,->及围绕在参数周围的括号都可以被忽略:
let results = values.map ({ element  in return element * 2 })
//"[2, 6, 10, 14]"

  • 第三步
    单行表达式闭包可以通过省略return来隐式返回闭包的结果:
    由于闭包函数体只含有element * 2这单一的表达式,该表达式返回Int类型,与我们例子中map所需的闭包的返回值类型一致(其实是泛型),所以,可以省略return。
let results = values.map ({ element  in element * 2 })
//"[2, 6, 10, 14]"

  • 第四步
    参数名称缩写(Shorthand Argument Names),由于Swift自动为内联闭包提供了参数缩写功能,你可以直接使用$0,$1,$2…依次获取闭包的第1,2,3…个参数。
    如果您在闭包表达式中使用参数名称缩写,您可以在闭包参数列表中省略对其的定义,并且对应参数名称缩写的类型会通过函数类型进行推断。in关键字也同样可以被省略:
let results = values.map ({ $0 * 2 })//$0即代表闭包中的第一个参数。
//"[2, 6, 10, 14]"

  • 第五步
    尾随闭包,由于我们的闭包是作为最后一个参数传递给map函数的,所以我们可以将闭包表达式尾随:
let results = values.map (){ $0 * 2 }
//"[2, 6, 10, 14]"

如果函数只需要闭包表达式一个参数,当您使用尾随闭包时,您甚至可以把()省略掉:

let results = values.map { $0 * 2 }
//"[2, 6, 10, 14]"

2. flatMap

  1. flatMap返回后的数组中不存在nil,同时它会把Optional解包
let array = ["Apple", "Orange", "Puple", ""]

let arr1 = array.map { a -> Int? in
    let length = a.characters.count
    guard length > 0 else { return nil }
    return length  
}
arr1 // [{some 5}, {some 6}, {some 5}, nil]

let arr2 = array.flatMap { a-> Int? in
    let length = a.characters.count
    guard length > 0 else { return nil}
    return length    
}    
arr2 // [5, 6, 5]
  1. flatMap还能把数组中存有数组的数组(二维数组、N维数组)一同打开变成一个新的数组
let array = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

let arr1 = array.map{ $0 }
arr1 // [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

let arr2 = array.flatMap{ $0 }
arr2 // [1, 2, 3, 4, 5, 6, 7, 8, 9]
  1. flatMap也能把两个不同的数组合并成一个数组,这个合并的数组元素个数是前面两个数组元素个数的乘积
let fruits = ["Apple", "Orange", "Puple"]
let counts = [2, 3, 5]

let array = counts.flatMap { count in
    fruits.map ({ fruit in
         return fruit + "  \(count)"            
    })   
}
array // ["Apple 2", "Orange 2", "Puple 2", "Apple 3", "Orange 3", "Puple 3", "Apple 5", "Orange 5", "Puple 5"]

3. filter

filer:过滤,可以对数组中的元素按照某种规则进行一次过滤

// 筛选出字符串的长度小于10的字符串
let stringArray = ["Objective-C", "Swift", "HTML", "CSS", "JavaScript"]
func stringCountLess10(string: String) -> Bool {
    return string.characters.count < 10
}
stringArray.filter(stringCountLess10)

stringArray.filter({string -> Bool in
    return string.characters.count < 10
})

// $0表示数组中的每一个元素
stringArray.filter{
    return $0.characters.count < 10
}

4. reduce

reduce:计算,可以对数组的元素进行计算

// 将数组中的每个字符串用‘、’拼接
let stringArray = ["Objective-C", "Swift", "HTML", "CSS", "JavaScript"]

func appendString(string1: String, string2: String) -> String {
    return string1 == "" ? string2 : string1 + "、" + string2
}
// reduce方法中的第一个参数是初始值
stringArray.reduce("", appendString)

stringArray.reduce("", {(string1, string2) -> String in
    return string1 == "" ? string2 : string1 + "、" + string2
})

// $0表示计算后的结果, $1表示数组中的每一个元素
stringArray.reduce("", {
    return $0 == "" ? $1 : $0 + "、" + $1
})

参考博客,教程:
http://wiki.jikexueyuan.com/project/swift/chapter2/07_Closures.html

2019-04-17 08:50:46 kyl282889543 阅读数 68

swift 4 新特性

1. 关于compactMap函数

苹果在Swift 4.1中新增compactMap函数,用来代替flatMap函数。

在Swift标准库中compactMap定义如下

public func compactMap(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult]
1
在Swift标准库中flatMap定义如下

public func flatMap(_ transform: (Element) throws -> String?) rethrows -> [String]
1
从定义可以看出,经过flatMap后一定变成字符串数组,而compactMap是任意类型的数组。从而compactMap使用更加灵活。
compactMap函数的应用

1.过滤 nil

let nums = [1, nil, 3, nil, 5]
let result = nums.compactMap { (item) -> Int? in
return item
}
print(result) // [1, 3, 5]

简洁语法,可这样使用

let result = nums.compactMap { return $0 }
print(result) // [1, 3, 5]

2.类型转换

let nums = [1, 2, 3, 4, 5]
let result = nums.compactMap { (item) -> String? in

return "\(item)"

}
print(result) // [“1”, “2”, “3”, “4”, “5”]

简洁语法,可这样使用

let nums = [1, 2, 3, 4, 5]
let result = nums.compactMap { return “($0)” }
print(result) // [“1”, “2”, “3”, “4”, “5”]

3.筛选数据 - 能被4整除的数

let nums = [12, 55, 733, 77, 44]
let result = nums.compactMap { (item) -> Int? in

if item%4 == 0 {
    return item
}
return nil

}
print(result) // [12, 44]

2. map : 可以对数组中的每一个元素做一次处理

通过map实现元素的映射,好处是我们可以非常清楚的表示两个元素列表作了何种转换,实现起来更简单,却有更大的信噪比。减轻我们理解代码的难度。

func map(transform: (T) -> U) -> U[]
它接受一个函数叫做 transform ,然后依次对原数组里的每一个元素调用该函数,函数返回值组成另一个数组:

[ x1, x2, … , xn].map(f) -> [f(x1), f(x2), … , f(xn)]

// foreach 表示
var newArray : Array = []
for item in oldArray {
newArray += f(item)
}

  • 实例1:
    map是一个高级函数,并不仅仅对数组有意义。它可以在任何类型和方法中实现,包括一种或多种映射方式,一个或多个映射关系。
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!"
}
  • 实例2:
// 计算字符串的长度
let stringArray = ["Objective-C", "Swift", "HTML", "CSS", "JavaScript"]
func stringCount(string: String) -> Int {
    return string.characters.count
}
stringArray.map(stringCount)
 
stringArray.map({string -> Int in
    return string.characters.count
})
 
// $0代表数组中的每一个元素
stringArray.map{
    return $0.characters.count
}
  • 实例3: 使用map操作Optional
    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。 

let nilNumber: Int? = .None
  
let transformedNilNumber = nilNumber.map { $0 * 2 }.map { $0 % 2 == 0 }
// transformedNilNumber: None
  • 实例4:用自己写的类型实现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)
        }
    }
}

  • 实例5:

3. flatMap与map的区别

flatMap 比map更上一层楼

var oldArray = [10,20,45,32]
var newArray = oldArray.flatMap{
    ["¥\($0)","$\($0 )"]
}
println(newArray) // [¥10, $10,¥20, $20, ¥45, $45, ¥32, $32]

2.1、 可传入n个处理方法,处理后得到n组数据,并组合到同一个数组中  
相对的,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))
    }
}

(1) flatMap返回后的数组中不存在nil, 同事它会把Optional解包

let array = ["Apple", "Orange", "Puple", ""]
 
let arr1 = array.map { a -> Int? in
    let length = a.characters.count
    guard length > 0 else { return nil }
    return length  
}
arr1 // [{some 5}, {some 6}, {some 5}, nil]
 
let arr2 = array.flatMap { a-> Int? in
    let length = a.characters.count
    guard length > 0 else { return nil}
    return length    
}    
arr2 // [5, 6, 5]


(2)flatMap还能把多维数组变成一维数组:
let array = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
 
let arr1 = array.map{ $0 }
arr1 // [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
 
let arr2 = array.flatMap{ $0 }
arr2 // [1, 2, 3, 4, 5, 6, 7, 8, 9]


(3)flatMap也能把两个不同的数组合并成一个数组,这个合并的数组元素个数是前面两个数组元素个数的乘积:

let fruits = ["Apple", "Orange", "Puple"]
let counts = [2, 3, 5]
 
let array = counts.flatMap { count in
    fruits.map ({ fruit in
         return fruit + "  \(count)"            
    })   
}
array // ["Apple 2", "Orange 2", "Puple 2", "Apple 3", "Orange 3", "Puple 3", "Apple 5", "Orange 5", "Puple 5"]


compactMap: 当后面的闭包中返回Optional的时候,在Swift 4.1中应该使用compactMap代替map。

4. filer:过滤,可以对数组中的元素按照某种规则进行一次过滤:

filter就是筛选的功能,参数是一个用来判断是否筛除的筛选闭包
func filter(includeElement: (T) -> Bool) -> [T]

  • 实例1:
// 筛选出字符串的长度小于10的字符串
let stringArray = ["Objective-C", "Swift", "HTML", "CSS", "JavaScript"]
func stringCountLess10(string: String) -> Bool {
    return string.characters.count < 10
}
stringArray.filter(stringCountLess10)
 
stringArray.filter({string -> Bool in
    return string.characters.count < 10
})
 
// $0表示数组中的每一个元素
stringArray.filter{
    return $0.characters.count < 10
}


  • 实例2:
// 传统的 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]

5. reduce:计算,可以对数组的元素进行计算:

reduce 函数解决了把数组中的值整合到某个独立对象的问题

func reduce(initial: U, combine: (U, T) -> U) -> U

  • 实例1:

要把数组中的值都加起来放到 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})

  • 实例2:
// 将数组中的每个字符串用‘、’拼接
let stringArray = ["Objective-C", "Swift", "HTML", "CSS", "JavaScript"]
 
func appendString(string1: String, string2: String) -> String {
    return string1 == "" ? string2 : string1 + "、" + string2
}
// reduce方法中的第一个参数是初始值
stringArray.reduce("", appendString)
 
stringArray.reduce("", {(string1, string2) -> String in
    return string1 == "" ? string2 : string1 + "、" + string2
})
 
// $0表示计算后的结果, $1表示数组中的每一个元素
stringArray.reduce("", {
    return $0 == "" ? $1 : $0 + "、" + $1
})


6. Swift 的函数式 API

在过去的时间里,人们对于设计 API 总结了很多通用的模式和最佳实践方案。一般情况下,我们总是可以从苹果的 Foundation、Cocoa、Cocoa Touch 和很多其他框架中总结出一些开发中的范例。毫无疑问,对于“特定情境下的 API 应该如何设计”这个问题,不同的人总是有着不同的意见,对于这个问题有很大的讨论空间。不过对于很多 Objective-C 的开发者来说,对于那些常用的模式早已习以为常。

随着 Swift 的出现,设计 API 引起了更多的问题。绝大多数情况下,我们只能继续做着手头的工作,然后把现有的方法翻译成 Swift 版本。不过,这对于 Swift 来说并不公平,因为和 Objective-C 相比,Swift 添加了很多新的特性。引用 Swift 创始人 Chris Lattner的一段话:

Swift 引入了泛型和函数式编程的思想,极大地扩展了设计的空间。

Core Image 是一个功能强大的图像处理框架,但是它的 API 有时有点笨重。 Core Image 的 API 是弱类型的 - 它通过键值对 (key-value) 设置图像滤镜。这样在设置参数的类型和名字时很容易失误,会导致运行时错误。新的 API 将会十分的安全和模块化,通过使用类型而不是键值对来规避这样的运行时错误。

6.1、 目标
我们的目标是构建一个 API ,让我们可以简单安全的组装自定义滤镜。举个例子,在文章的结尾,我们可以这样写:

let myFilter = blur(blurRadius) >|> colorOverlay(overlayColor)
let result = myFilter(image)
上面构建了一个自定义的滤镜,先模糊图像,然后再添加一个颜色蒙版。为了达到这个目标,我们将充分利用 Swift 函数是一等公民这一特性。项目源码可以在 Github 上的这个示例项目中下载。

6.2、 Filter 类型
CIFilter 是 Core Image 中的一个核心类,用来创建图像滤镜。当实例化一个 CIFilter 对象之后,你 (几乎) 总是通过kCIInputImageKey 来输入图像,然后通过 kCIOutputImageKey 获取返回的图像,返回的结果可以作为下一个滤镜的参数输入。

在我们即将开发的 API 里,我们会把这些键值对 (key-value) 对应的真实内容抽离出来,为用户提供一个安全的强类型 API。我们定义了自己的滤镜类型 Filter,它是一个可以传入图片作为参数的函数,并且返回一个新的图片。

typealias Filter = CIImage -> CIImage
这里我们用 typealias 关键字,为 CIImage -> CIImage类型定义了我们自己的名字,这个类型是一个函数,它的参数是一个CIImage ,返回值也是 CIImage 。这是我们后面开发需要的基础类型。

如果你不太熟悉函数式编程,你可能对于把一个函数类型命名为 Filter 感觉有点奇怪,通常来说,我们会用这样的命名来定义一个类。如果我们很想以某种方式来表现这个类型的函数式的特性,我们可以把它命名成 FilterFunction 或者一些其他的类似的名字。但是,我们有意识的选择了 Filter 这个名字,因为在函数式编程的核心哲学里,函数就是值,函数和结构体、整数、多元组、或者类,并没有任何区别。一开始我也不是很适应,不过一段时间之后发现,这样做确实很有意义。

6.3、 构建滤镜
现在我们已经定义了 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 的用户不再需要对返回的结果手动进行类型转换了。

6.4、 模糊
有了这些东西,现在我们就可以定义属于自己的简单滤镜了。高斯模糊滤镜只需要一个模糊半径作为参数,我们可以非常容易的完成一个模糊滤镜:

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 中已有滤镜的一个简单的封装,我们可以多次重复同样的模式,创建属于我们自己的滤镜函数。

6.5、 颜色蒙版
现在让我们定义一个颜色滤镜,可以在现有的图片上面加上一层颜色蒙版。 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 组成。

6.6、 组合滤镜
现在我们已经定义了一个模糊滤镜和一个颜色滤镜,我们在使用的时候可以把它们组合在一起:我们先将图片做模糊处理,然后再在上面放一个红色的蒙层。让我们先加载一张图片:

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) ,然后再把这个滤镜应用到图片上。

6.7、 函数组装
不过,我们可以做的比上面的更好。我们可以简单的把两行滤镜的调用组合在一起变成一行,这是我脑海中想到的第一个能改进的地方:

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) 更加清晰,更加容易理解。

6.8、 自定义运算符
很多 Objective-C 的开发者对于自定义运算符持有怀疑态度。在 Swift 刚发布的时候,这是一个并没有很受欢迎的特性。很多人在 C++ 中遭遇过自定义运算符过度使用 (甚至滥用) 的情况,有些是个人经历过的,有些是听到别人谈起的。

你可能对于前面定义的运算符 >|> 持有同样的怀疑态度,毕竟如果每个人都定义自己的运算符,那代码岂不是很难理解了?值得庆幸的是在函数式编程里有很多的操作,为这些操作定义一个运算符并不是一件很罕见的事情。

我们定义的滤镜组合运算符是一个函数组合的例子,这是一个在函数式编程中广泛使用的概念。在数学里,两个函数 f 和 g 的组合有时候写做 f ∘ g,这样定义了一种全新的函数,将输入的 x 映射到 f(g(x)) 上。这恰好就是我们的 >|> 所做的工作 (除了函数的逆向调用)。

6.9、 泛型
仔细想想,其实我们并没有必要去定义一个用来专门组装滤镜的运算符,我们可以用一个泛型的运算符来组装函数。目前我们的 >|>是这样的:

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,这样可以清晰的理解用通用运算符的来替换滤镜组合运算符是多么的有用。

6.10、 结论
至此,我们成功的用函数式 API 封装了 Core Image。希望这个例子能够很好的说明,对于 Objective-C 的开发者来说,在我们所熟知的 API 的设计模式之外有一片完全不同的世界。有了 Swift,我们现在可以动手探索那些全新的领域,并且将它们充分地利用起来。


2018-08-02 16:09:58 super_niuxinhuai 阅读数 165

原文链接

Swift是支持一门函数式编程的语言,拥有Map,FlatMap,Filter,Reduce针对集合类型的操作.本文主要根据官方文档举例了解Swift中的Map,FlatMap,Filter,Reduce

Map

首先我们来看一下mapSwift中的的定义,我们看到它可以用在 Optionals 和 SequenceType 上(如:数组、词典等)

public enum Optional<Wrapped> : _Reflectable, NilLiteralConvertible {
    /// If `self == nil`, returns `nil`.  Otherwise, returns `f(self!)`.
    @warn_unused_result
    public func map<U>(@noescape f: (Wrapped) throws -> U) rethrows -> U?
}

extension CollectionType {
    /// Returns an `Array` containing the results of mapping `transform`
    /// over `self`.
    ///
    /// - Complexity: O(N).
    @warn_unused_result
    public func map<T>(@noescape transform: (Self.Generator.Element) throws -> T) rethrows -> [T]
}

1869329-e17438ddf8171bbf.png

我们可以用For-in完成类似的操作

var values = [1,3,5,7]
var results = [Int]()
for var value in values {
    value *= 2
    results.append(value)
}
//"[2, 6, 10, 14]"
print(results)

Map操作

var values = [1,3,5,7]
let results = values.map (){ $0 * 2 }
//"[2, 6, 10, 14]"
相比较而言,代码精简了很多。可以理解为 map是直接对当前的数组进行处理,返回的还是当前的数组样式

FlatMap

与map一样,它可以用在 Optionals和 SequenceType 上(如:数组、词典等)。

public enum Optional<Wrapped> : _Reflectable, NilLiteralConvertible {
    /// Returns `nil` if `self` is `nil`, `f(self!)` otherwise.
    @warn_unused_result
    public func flatMap<U>(@noescape f: (Wrapped) throws -> U?) rethrows -> U?
}

extension SequenceType {
    /// 返回一个将变换结果连接起来的数组
    /// `transform` over `self`.
    ///     s.flatMap(transform)
    /// is equivalent to
    ///     Array(s.map(transform).flatten())
    @warn_unused_result
    public func flatMap<S : SequenceType>(transform: (Self.Generator.Element) throws -> S) rethrows -> [S.Generator.Element]
}

extension SequenceType {
    /// 返回一个包含非空值的映射变换结果
    @warn_unused_result
    public func flatMap<T>(@noescape transform: (Self.Generator.Element) throws -> T?) rethrows -> [T]
}

一:空值过滤


var values:[Int?] = [1,3,5,7,9,nil]
let flattenResult = values.flatMap{ $0 }
/// [1, 3, 5, 7, 9]

二:压平,(降维)

var values = [[1,3,5,7],[9]]
let flattenResult = values.flatMap{ $0 }
/// [1, 3, 5, 7, 9]
FlatMap可以理解为强制空值过滤,剔除空值。强制压平\强制降维。

Filter

同样,我先来看看Swift中的定义:

extension SequenceType {
    /// 返回包含原数组中符合条件的元素的数组
    /// Returns an `Array` containing the elements of `self`,
    /// in order, that satisfy the predicate `includeElement`.
    @warn_unused_result
    public func filter(@noescape includeElement: (Self.Generator.Element) throws -> Bool) rethrows -> [Self.Generator.Element]
}

eg: 我们向flatMap传入了一个闭包,筛选出了能被3整除的数据

var values = [1,3,5,7,9]
let flattenResults = values.filter{ $0 % 3 == 0}
//[3, 9]
Filter 可以理解为条件过滤,返回包含原数组中符合条件的元素的数组

Reduce

我们先来看下Swift中的定义:

extension SequenceType {
    /// Returns the result of repeatedly calling `combine` with an
    /// accumulated value initialized to `initial` and each element of
    /// `self`, in turn, i.e. return
    /// `combine(combine(...combine(combine(initial, self[0]),
    /// self[1]),...self[count-2]), self[count-1])`.
    @warn_unused_result
    public func reduce<T>(initial: T, @noescape combine: (T, Self.Generator.Element) throws -> T) rethrows -> T
}

eg

let fiveArray = [1,2,3,4,5]
let sum = fiveArray.reduce(0) { $0 + $1 } //求数组元素的和
let sum1 = fiveArray.reduce(0, +) // 简写
let sum2 = fiveArray.filter{ $0 > 2 }.reduce(0, +)  // 数组中元素大于2 的数据求和
print(sum,sum1,sum2)
// 15 15 12
2016-06-29 19:44:31 fish_yan_ 阅读数 3616

map 和 flatMap 是 Swift 中两个常用的函数,它们体现了 Swift 中很多的特性。对于简单的使用来说,它们的接口并不复杂,但它们内部的机制还是非常值得研究的,能够帮助我们够好的理解 Swift 语言。

map 简介

首先,咱们说说 map 函数如何使用。

let numbers = [1,2,3,4]
let result = numbers.map { $0 + 2 }
print(result) // [3,4,5,6]

map 方法接受一个闭包作为参数, 然后它会遍历整个 numbers 数组,并对数组中每一个元素执行闭包中定义的操作。 相当于对数组中的所有元素做了一个映射。 比如咱们这个例子里面的闭包是讲所有元素都加 2 。 这样它产生的结果数据就是 [3,4,5,6]。

初步了解之后,我们来看一下 map 的定义:

func map
(@noescape transform: (Self.Generator.Element) throws -> T) rethrows -> [T]

咱们抛开一些和关键逻辑无关的修饰符 @noescape,throws 这些,在整理一下就是这样:

func map
(transform: (Self.Generator.Element) -> T) rethrows -> [T]
 ```

map 函数接受一个闭包, 这个闭包的定义是这样的:

(Self.Generator.Element) -> T
“`

它接受 Self.Generator.Element 类型的参数, 这个类型代表数组中当前元素的类型。 而这个闭包的返回值,是可以和传递进来的值不同的。 比如我们可以这样:

let stringResult = numbers.map { "No. \($0)" }
// ["No. 1", "No. 2", "No. 3", "No. 4"]

这次我们在闭包装把传递进来的数字拼接到一个字符串中, 然后返回一个组数, 这个数组中包含的数据类型,就是我们拼接好的字符串。

这就是关于 map 的初步了解, 我们继续来看 flatMap。

flatMap

map 可以对一个集合类型的所有元素做一个映射操作。 那么 flatMap 呢?

让我们来看一个 flatMap 的例子:

result = numbers.flatMap { $0 + 2 }
// [3,4,5,6]

我们对同样的数组使用 flatMap 进行处理, 得到了同样的结果。 那 flatMap 和 map 到底有什么区别呢?

咱们再来看另一个例子:

let numbersCompound = [[1,2,3],[4,5,6]];
var res = numbersCompound.map { $0.map{ $0 + 2 } }
// [[3, 4, 5], [6, 7, 8]]
var flatRes = numbersCompound.flatMap { $0.map{ $0 + 2 } }
// [3, 4, 5, 6, 7, 8]

这里就看出差别了。 对于二维数组, map 和 flatMap 的结果就不同了。 我们先来看第一个调用:
`
var res = numbersCompound.map { $0.map{ $0 + 2 } }
// [[3, 4, 5], [6, 7, 8]]

numbersCompound.map { … } 这个调用实际上是遍历了这里两个数组元素 [1,2,3] 和 [4,5,6]。 因为这两个元素依然是数组,所以我们可以对他们再次调用 map 函数:$0.map{ $0 + 2 }。 这个内部的调用最终将数组中所有的元素加 2。

再来看看 flatMap 的调用:


var flatRes = numbersCompound.flatMap { $0.map{ $0 + 2 } }
// [3, 4, 5, 6, 7, 8]

flatMap 依然会遍历数组的元素,并对这些元素执行闭包中定义的操作。 但唯一不同的是,它对最终的结果进行了所谓的 “降维” 操作。 本来原始数组是一个二维的, 但经过 flatMap 之后,它变成一维的了。

flatMap 是如何做到的呢,它的原理是什么,为什么会存在这样一个函数呢? 相信此时你脑海中肯定会浮现出类似的问题。

下面咱们再来看一下 flatMap 的定义, 还是抛去 @noescape, rethrows 这些无关逻辑的关键字:


func flatMap(transform: (Self.Generator.Element) throws -> T?) -> [T]
func flatMap(transform: (Self.Generator.Element) -> S) -> [S.Generator.Element]

和 map 不同, flatMap 有两个重载。 参照我们刚才的示例, 我们调用的其实是第二个重载:


func flatMa
p(transform: (Self.Generator.Element) -> S) -> [S.Generator.Element]

flatMap 的闭包接受的是数组的元素,但返回的是一个 SequenceType 类型,也就是另外一个数组。 这从我们刚才这个调用中不难看出:


numbersCompound.flatMap { $0.map{ $0 + 2 } }

我们传入给 flatMap 一个闭包$0.map{ $0 + 2 } , 这个闭包中,又对 $0 调用了 map 方法, 从 map 方法的定义中我们能够知道,它返回的还是一个集合类型,也就是 SequenceType。 所以我们这个 flatMap 的调用对应的就是第二个重载形式。

那么为什么 flatMap 调用后会对数组降维呢? 我们可以从它的源码中窥探一二(Swift 不是开源了吗~)。

文件位置: swift/stdlib/public/core/SequenceAlgorithms.swift.gyb


extension Sequence {
//...
public func flatMap(
@noescape transform: (${GElement}) throws -> S
) rethrows -> [S.${GElement}] {
var result: [S.${GElement}] = []
for element in self {
result.append(contentsOf: try transform(element))
}
return result
}
//...
}

这就是 flatMap 的完整源码了, 它的源码也很简单, 对遍历的每一个元素调用 try transform(element)。 transform 函数就是我们传递进来的闭包。

然后将闭包的返回值通过 result.append(contentsOf:) 函数添加到 result 数组中。

那我们再来看一下 result.append(contentsOf:) 都做了什么, 它的文档定义是这样:

Append the elements of newElements to self.

简单说就是将一个集合中的所有元素,添加到另一个集合。 还以我们刚才这个二维数组为例:


let numbersCompound = [[1,2,3],[4,5,6]];
var flatRes = numbersCompound.flatMap { $0.map{ $0 + 2 } }
// [3, 4, 5, 6, 7, 8]

flatMap 首先会遍历这个数组的两个元素 [1,2,3] 和 [4,5,6], 因为这两个元素依然是数组, 所以我们可以对他们再进行 map 操作:$0.map{ $0 + 2 }

这样, 内部的$0.map{ $0 + 2 }调用返回值类型还是数组, 它会返回 [3,4,5] 和 [6,7,8]。

然后, flatMap 接收到内部闭包的这两个返回结果, 进而调用 result.append(contentsOf:) 将它们的数组中的内容添加到结果集中,而不是数组本身。

那么我们最终的调用结果理所当然就应该是 [3, 4, 5, 6, 7, 8] 了。

仔细想想是不是这样呢~

flatMap 的另一个重载

我们刚才分析了半天, 其实只分析到 flatMap 的一种重载情况, 那么另外一种重载又是怎么回事呢:

func flatMap
(transform: (Self.Generator.Element) -> T?) -> [T]

从定义中我们看出, 它的闭包接收的是 Self.Generator.Element 类型, 返回的是一个 T? 。 我们都知道,在 Swift 中类型后面跟随一个 ?, 代表的是 Optional 值。 也就是说这个重载中接收的闭包返回的是一个 Optional 值。 更进一步来说,就是闭包可以返回 nil。

我们来看一个例子:


let optionalArray: [String?] = ["AA", nil, "BB", "CC"];
var optionalResult = optionalArray.flatMap{ $0 }
// ["AA", "BB", "CC"]

这样竟然没有报错, 并且 flatMap 的返回结果中, 成功的将原数组中的 nil 值过滤掉了。 再仔细观察,你会发现更多。 使用 flatMap 调用之后, 数组中的所有元素都被解包了, 如果同样使用 print 函数输出原始数组的话, 大概会得到这样的结果:

[Optional("AA"), nil, Optional("BB"), Optional("CC")]

而使用 print 函数输出 flatMap 的结果集时,会得到这样的输出:

["AA", "BB", "CC"]

也就是说原始数组的类型是 [String?] 而 flatMap 调用后变成了 [String]。 这也是 flatMap 和 map 的一个重大区别。 如果同样的数组,我们使用 map 来调用, 得到的是这样的输出:

[Optional("AA"), nil, Optional("BB"), Optional("CC")]

这就和原始数组一样了。 这两者的区别就是这样。 map 函数值对元素进行变换操作。 但不会对数组的结构造成影响。 而 flatMap 会影响数组的结构。再进一步分析之前,我们暂且这样理解。

flatMap 的这种机制,而已帮助我们方便的对数据进行验证,比如我们有一组图片文件名, 我们可以使用 flatMap 将无效的图片过滤掉:

var imageNames = ["test.png", "aa.png", "icon.png"];
imageNames.flatMap{ UIImage(named: $0) }

那么 flatMap 是如何实现过滤掉 nil 值的呢? 我们还是来看一下源码:

extension Sequence {
// ...
public func flatMap(
@noescape transform: (${GElement}) throws -> T?
) rethrows -> [T] {
var result: [T] = []
for element in self {
if let newElement = try transform(element) {
result.append(newElement)
}
}
return result
}
// ...
}

依然是遍历所有元素,并应用 try transform(element) 闭包的调用, 但关键一点是,这里面用到了 if let 语句, 对那些只有解包成功的元素,才会添加到结果集中:

if let newElement = try transform(element) {
result.append(newElement)
}

这样, 就实现了我们刚才看到的自动去掉 nil 值的效果了。

关于 Optional 和 if let 语句可以参看: 浅谈 Swift 中的 Optionals

结尾

关于 Swift 中的 map 和 flatMap, 看完这篇内容是不会会对你有所启发呢。 当然, 关于这两个函数我们这里并没有完全讨论完。 它们背后还有着更多的思想。 关于本篇文章的代码,大家还可以来 Github 上面参看

2018-03-26 16:12:09 liumiaomiao1991 阅读数 57

我在寻找最佳解决方案时,也考虑过flatMap。但是老实说,我并不是很了解flatMap,也不知道如何使用。一位同事告诉我一种解决方案,需要用到两个flatMap,看起来十分复杂。

之后,在博文的评论Twitter上发生了激烈讨论,我发现其实使用flatMap可以轻松地解决问题:

let minionImagesFlattened = (1...7).flatMap { UIImage(named: "minionIcon-\($0)") }

下面我会按照自己的理解介绍flatMap。别忘了我也是刚学的,所以我肯定不是flatMap专家!

简单的例子

我对flatMap的理解十分基础,这是我最初的想法:

let nestedArray = [[1,2,3], [4,5,6]]
let flattenedArray = nestedArray.flatMap { $0 }
flattenedArray // [1, 2, 3, 4, 5, 6]

展开阅读原文

flatMap与Monad(Swift)

阅读数 620

swift的map和flatMap

阅读数 1

没有更多推荐了,返回首页