Swift 中函数式编程的应用

作者: 诸葛俊伟 | 来源:发表于2016-07-22 06:15 被阅读132次

    最近停下了 iOS 的学习,一心刷题。以前挺反感这种中国高考刷数学试卷式的应试方式,但是因为美国找工作只考算法,所以。。。不过,现在感觉刷过之后基础确实扎实了很多。我没有看过喵神的函数式 Swift,我下面所讲的只是函数式编程的冰山一角,有时间还是看看喵神的书为好。

    刷题过程中用到最多的语句应该就是 for 循环吧?用到最多的结构应该就是数组(Array),链表(Linked List)和字典(Dictionary,在其它语言中应该是 hash map)。将 for 循环和 Array 或者 Dictionary 结合起来,就是 Swift 中的 Map, Filter 和 Reduce,还有 FlatMap,当然基础就是 Closure。我之前写过一篇非常详细完整的 Closure 文章,对 Closure 不太熟悉的童鞋可以点击传送门

    Map

    Map 就是遍历整个集合,对每个元素执行同样的操作,返回一个包含了对每个元素操作之后的数组

    对上图中的操作,我们可以用 for 循环语言:

    let val = [1,2,3,4]
    var result = [Int]()
    for i in val {
        result.append(i*i)
    }
    print("\(result)")
    

    上面的代码看上去很平常,别的语言中几乎都这么写,但是在 Swift 中有更 Swifty 的方式,就是用 Map:

    let val = [1,2,3,4]
    let doubleVal = val.map{ $0 * $0 }
    

    这里的 { $0 * $0 } 就是典型的 Closure,是最简化的版本,最初的是下面这样的。如何化简的这里就不多说了。

    let doubleVal = val.map({
        (val: Int) -> Int in
        return val * val
    })
    

    结果中的数据类型并不一定等于原数据的数据类型,比如:

    let scores = [0,28,124]
    let words = scores.map { NSNumberFormatter.localizedStringFromNumber($0,
        numberStyle: .SpellOutStyle) }
    // ["zero", "twenty-eight", "one hundred twenty-four"]
    

    Map 操作还可以应用到 Dictionary 或者 set 上,但是输出的结果一定是 Array。比如:

    let milesToPoint = ["point1":120.0,"point2":50.0,"point3":70.0]
    let kmToPoint = milesToPoint.map { name,miles in miles * 1.6093 }
    // [80.465, 112.651, 193.116]
    

    Filter

    Filter 的功能和字面意思一样,就是过滤的作用,返回一个满足条件语句的数组

    Filter 也是用 Closure 来实现对原集合中各元素的判断,图中的示例代码如下:

    let digits = [1,4,15,20]
    let even = digits.filter { $0 & 0b1 == 0 }
    // [4, 20]
    

    Reduce

    Reduce 就是对集合中的每个元素执行某种操作,附加(combine)到一个初始数值(initial value)上。

    Reduce 中有两个变量,一个是初始数值,一个是 combine 的 closure。比如将数组中的所有数都乘到一个初始数值(这里为 1)上:

    let digits = [1,2,3,4]
    let even = digits.reduce(1, combine: *)
    // 24
    

    Swift 中的 String 是可以用 + 运算符的。所以还可以这样:

    let hello = ["Love","U"]
    let hi = hello.reduce("I", combine: +)
    // "ILoveU"
    

    根据尾部闭包(Trailing Closures)的定义,如果闭包是函数的最后一个参数,可以把闭包体写在圆括号的外面。所以在这里我们可以将 reduce(initial: T, combine: (T, String) throws -> T) 中的 combine closure 移到外面,比如:

    let hello = ["Love","U"]
    let hi = hello.reduce("I"){ "\($0) \($1)" }
    // "I Love U",这样更好看吧
    

    FlatMap

    FlatMap 的一个简单应用就是将二维数组转换成一维数组:

    let hello = [[1,2,3],[4,5,6]]
    let hi = hello.flatMap{ $0 }
    [1, 2, 3, 4, 5, 6]
    

    还可以用来去除 optional nil,

    let hello: [Int?] = [1,2,nil]
    let hi = hello.flatMap{ $0 }
    // [1, 2]
    

    FlatMap 真正强大之处是可以很方便地对二维数组执行以前用两个 for 循环才能完成的操作。注意 flatMap 的 closure 中的变量是二维数组的第二维数组,所以我们可以将 filter,map,reduce 这些操作写在 flatmap 中 。比如:

    let collections = [[1,2,3],[4,5,6]]
    let onlyEven = collections.flatMap { $0.filter{ $0 & 0b1 == 0 } }
    // [2, 4, 6]
    

    Chaining

    我们可以用链式的方式执行以上的那些操作。比如我们要将一个数组中大于0的数乘以2,我们可以先选出大于0的数 (Filter),然后再乘以2 (Map).

    let digits = [1,2,-3,-4,5,0]
    let result = digits.filter{ $0 > 0 }.map{ $0 * 2 }
    // [2, 4, 10]
    

    LeetCode 实战

    最后我再分享一个实战的代码,是 LeetCode 中的 169. Majority Element,题目不难,方法可以有很多,在这个示例中我用的是 Bit Manipulation 方法,具体我就不详细解释了,有兴趣的可以去做一做。

    func majorityElement(nums: [Int]) -> Int {
        let numBits = sizeof(Int) * 8
        let bitCounts = (0 ..< numBits).map { i in
            nums.reduce(0) { $0 + ($1 >> i) & 1 }
        }
        let major = (0 ..< numBits).reduce(0) {
            $0 | (bitCounts[$1] > nums.count/2 ? 1 << $1 : 0)
        }
        return major
    }
    

    欢迎转载,转载请注明出处。)

    相关文章

      网友评论

        本文标题:Swift 中函数式编程的应用

        本文链接:https://www.haomeiwen.com/subject/jnfijttx.html