Map,FlatMap,Filter,Reduce

在swift中给Array提供了很多函数式的操作,这些操作其实在Objective - C的ReactiveCocoa开源库中也有对应的操作。这些操作大大减少了代码量,同时也让很多同学非常难以理解,学习嘛,不仅要知其然,还要知其所以然。

Map

引子:我们通常会遇到这样一个问题,需要对数组中的所有元素做一个操作,比如+1。如果不知道map操作或者不使用毛,通常我们会这么做:

let numbers = [1, 2, 3, 4]
var result = []()
for i in numbers {
  result.append(i + 1)
}
print(result)
// [2, 4, 6, 8]

使用map以后,简直so easy,一行代码搞定

let numbers = [1, 2, 3, 4]
let result = numbers.map{$0 + 1}

我们再看看API文档:

    /// Returns an array containing the results of mapping the given closure
    /// over the sequence's elements.
    ///
    /// In this example, `map` is used first to convert the names in the array
    /// to lowercase strings and then to count their characters.
    ///
    ///     let cast = ["Vivien", "Marlon", "Kim", "Karl"]
    ///     let lowercaseNames = cast.map { $0.lowercaseString }
    ///     // 'lowercaseNames' == ["vivien", "marlon", "kim", "karl"]
    ///     let letterCounts = cast.map { $0.characters.count }
    ///     // 'letterCounts' == [6, 6, 3, 4]
    ///
    /// - Parameter transform: A mapping closure. `transform` accepts an
    ///   element of this sequence as its parameter and returns a transformed
    ///   value of the same or of a different type.
    /// - Returns: An array containing the transformed elements of this
    ///   sequence.
    public func map(_ transform: @noescape Element throws -> T) rethrows -> [T]

通过文档的解释,我们可以知道,其实map函数可以对数组里面的元素进行遍历,并且你可以在遍历的同时加上自己的转换规则(transform),返回的结果是转换后的新元素组成的新数组;

关于转换这里,同学们 要注意以下,这里被传入的参数是一个非逃逸闭包(@noescape),也就是说这个闭包只能在map函数里面执行,所以在里面做一些操作的时候注意作用域问题,笔者之前被坑过!!!

通过API文档我们可以猜测一下,那么大致实现如下:

func map(array: [T], f: T -> U) -> [U] {
  var result = [U]()
  for i in numbers {
    result.append(i)
  } 
  return result
}

FlatMap

FlatMap其实很多人觉得他们俩是一样的,在很多操作上看起来确实是一样的

let numbers = [1, 2, 3, 4]
let result = numbers.flatMap{$0 + 1}

// result -> [2, 4, 6, 8]

这样看起来是一样的,那么到底哪里不同呢?咱们先从API文档里面找找看:

    /// Returns an array containing the non-`nil` results of calling the given
    /// transformation with each element of this sequence.
    ///
    /// Use this method to receive an array of nonoptional values when your
    /// transformation produces an optional value.
    ///
    /// In this example, note the difference in the result of using `map` and
    /// `flatMap` with a transformation that returns an optional `Int` value.
    ///
    ///     let possibleNumbers = ["1", "2", "three", "///4///", "5"]
    /// 
    ///     let mapped: [Int?] = numbers.map { str in Int(str) }
    ///     // [1, 2, nil, nil, 5]
    /// 
    ///     let flatMapped: [Int] = numbers.flatMap { str in Int(str) }
    ///     // [1, 2, 5]
    ///
    /// - Parameter transform: A closure that accepts an element of this
    ///   sequence as its argument and returns an optional value.
    /// - Returns: An array of the non-`nil` results of calling `transform`
    ///   with each element of the sequence.
    ///
    /// - Complexity: O(*m* + *n*), where *m* is the length of this sequence
    ///   and *n* is the length of the result.
    public func flatMap(_ transform: @noescape Element throws -> ElementOfResult?) rethrows -> [ElementOfResult]

文档里面写的很清楚,flatMap会滤掉nil的结果,换句话说他返回的是[T],而不是[T?]

let possibleNumbers = ["1", "2", "three", "///4///", "5"]
let mapped: [Int?] = numbers.map{str in Int(str)}
// [1, 2, nil, nil, 5]
let flatMapped: [Int] = numbers.flatMap{str in Int(str)}
// [1, 2, 5]

而且,FlatMap会对结果进行自动的flatten操作,也就是说flatMap(transform)其实相当于map(transform).flatten()

let numbers = [1, 2, 3, 4]
let mapped = numbers.map { $0 * 2 }
// [[1], [2, 2], [3, 3, 3], [4, 4, 4, 4]]
let flatMapped = numbers.flatMap { $0.map{ $0 * 2 } }
// [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]

// 或者可以换一种易于理解的写法
let flatMappedAnother = numbers.flatMap { array in
  array.map { element in 
    element * 2
  }
}
// [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]

Filter

在开发过程中过滤数据是再常见不过的操作了,比如从订单中筛选出金额大于xxx的订单来,如果使用for的方法,代码或许或是这样的:

let orders = [10, 30 ,45, 99]
 // 筛选出大于30的结果
 var result = [Int]()
 for payment in orders {
  if payment > 30 {
    result.append(payment)
  }
 }
 
 print(result)
 // [45, 99]

使用filter后的代码:

let filterOrder = orders.filter{ $0 > 30}
print(filterOrder)
// [45, 99]

非常容易理解,那么我们再看看API文档是如何描述的:

    /// Returns an array containing, in order, the elements of the sequence
    /// that satisfy the given predicate.
    ///
    /// In this example, `filter` is used to include only names shorter than
    /// five characters.
    ///
    ///     let cast = ["Vivien", "Marlon", "Kim", "Karl"]
    ///     let shortNames = cast.filter { $0.characters.count < 5 }
    ///     print(shortNames)
    ///     // Prints "["Kim", "Karl"]"
    ///
    /// - Parameter includeElement: A closure that takes an element of the
    ///   sequence as its argument and returns a Boolean value indicating
    ///   whether the element should be included in the returned array.
    /// - Returns: An array of the elements that `includeElement` allowed.
    public func filter(_ includeElement: @noescape Element throws -> Bool) rethrows -> [Element]

再好理解不过了,传入对应的转换条件就可以了,返回的是转换后的新元素数组

Reduce

reduce其实是对map,flatmap,filter的扩展,他可以实现其他三个能实现的任何功能,下面我们使用reduce来实现上面的几个类似的功能看看reduce的强大

// 对numbers数组中的数进行累加
let result2 = numbers.reduce([Int]()){a, x in
  var result = a
  result.append(x + 2)
  return result
}

// 对nil进行过滤
let numbsers: [Int?] = [1, 2, nil, 4]

// reduce
numbsers.reduce([Int]()) { (a, x) in
  var result = a
  if let temp = x {
    result.append(temp)
  }
  return result
}

// 下面filter的操作大致相同

那么我们现在可以大致猜测以下,reduce内部到底做了什么?我们先使用最笨的方法,看看reduce里面做了什么?

print([1, 2, 3, 4].reduce([Int]()){a, x in
  var result = a
  print("x -> \(x)")
  print("a -> \(a)")
  result.append(x + 2)
  return result
})

// Result
x -> 1
a -> []
x -> 2
a -> [3]
x -> 3
a -> [3, 4]
x -> 4
a -> [3, 4, 5]
[3, 4, 5, 6]

从结果来看,a会拼接每次得到的结果,也就是每次结束的时候result又被赋值给了a,而x是每次遍历时数组中对应的元素。那么我可以猜测reduce的实现可能是这样的:

func reduce(arr: [A], initialValue: R, combine: (R, A) -> R) -> R {
    var result = initialValue 
    for i in arr {
        result = combine(result, i) // result又被赋值回去了
    }
    return result 
}

接下来,我们可以使用reduce来重新实现map和filter函数

func mapReduceVersion(rls: [T], f: T -> U) -> [U] {
  return reduce(rls, []){result, x in result + [f(x)]}
}

func filterReduceVersion(rls: [T], f: T -> Bool) -> [T] {
  return reduce(rls, []) {result, x in
    return f(x) ? result + [x] : result
  }
}

这几个函数的使用是次要的,主要是要理解其中的原理,多多去探索,坑太多了...

如有错误之处,欢迎指出讨论

参考资料:
objc.io
swift.gg

生命不息,折腾不止...
I'm not a real coder,but i love it so much!

你可能感兴趣的:(Map,FlatMap,Filter,Reduce)