filter、map、reduce

介绍

  对于数据,不同的语言都有异曲同工的处理方式,比较直观的就是使用控制流和循环语句,将有同一处理需求的数据按照一定的规则进行处理,从而得到一定规则的数据。但这对于常常需要进行数据操作的人来说,一些常用的数据处理方式往往需要重复使用,于是便出现了像filtermapreduce这种灵活通用的数据处理方法,并且可以发现,这三个处理,在多种语言中都有提供,如Swift、Python、Javascript等等。

  那么,这里将以Swift为例阐述filtermapreduce这三种处理。

filter

  从名字来看,filter就是过滤的意思。定义如下:

public func filter(_ isIncluded: (Element) throws -> Bool) rethrows -> [Element]

  可以看到,参数是一个closure,其返回值为Bool类型,实际上很好理解,当一个Array对象调用filter方法后,需要传入一个过滤条件,Array对象内的Element符合这个过滤条件(即返回值为true)时,那么这个Element就通过了过滤,被放到[Element]这个返回值中,重复遍历完整个Array对象后,组成了完整的[Element]返回值对象,此时,filter执行完毕。

举个栗子,如下:

let array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

// filter1 = [5, 6, 7, 8, 9]
let filter1 = array.filter { (data) -> Bool in
    if data > 4 {
        return true
    } else {
        return false
    }
}

// filter2 = [5, 6, 7, 8, 9]
let filter2 = array.filter{$0 > 4}

// filter3 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
let filter3 = array.filter{_ in true}

  从栗子中的filter1中,基本已经可以看出filter处理的方式了,而filter2filter3则是非常方便的closure表达式简写了过滤条件。

map

  从名字来看,map就是映射的意思。定义如下:

public func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]

  可以看到,参数也是一个closure,其返回值是一个泛型,可以自定义,要理解也不难,这种处理方式,就是把Array对象内的Element按照一定规则映射成另一个[T]类型的对象。

举个栗子,如下:

let array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

//	map1 = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
let map1 = array.map { (data) -> String in
    return "(data)"
}

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

  从栗子中的map1中,可以看出map处理的方式,closure中的返回值被定义为String了,并在closure进行了Element类型的转换,所以在这里可以理解为把数字类型的数据转换成字符类型的数据。

reduce

  单从名字来看,这个处理方法有点难理解,reduce其实是把Array内的Element个数减少、降低的意思。定义如下:

public func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, Element) throws -> Result) rethrows -> Result

  可以看到,参数有2个,并且这里的Result也是泛型,用来指定初始化和最终返回值的类型,而对于返回值,更可以看出返回值不一定是Array,而第二个参数也是一个closure,所做的处理便是以Result为返回值容器,把Element按照一定规则处理后放到Result容器中,返回Result值为reduce的返回值。

举个栗子,如下:

let array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

//	reduce1 = "0123456789"
let reduce1 = array.reduce(String()) { (result: String, data: Int) -> String in
    return result + "(data)"
}

//	reduce2 = "0123456789"
let reduce2 = array.reduce(String()) {$0 + "($1)"}

  从栗子中的reduce1中,可以想象reduce的处理方式,String()Result泛型定义为String类型,之后Element定义为名为dataInt参数,在closure中通过反复转换类型并把其值传入闭包返回值Result中,最终的返回值就是最终的Result返回值。

总结

  通过上面的几个栗子,应能明白这三种数据处理方式,而对于这些处理数据的返回值,亦可互相组合使用,从而产生更灵活的处理,在一些语言中,也是有更高级的处理方式,例如Swift,还会有类似如下的用法:

public func flatMap<SegmentOfResult : Sequence>(_ transform: (Element) throws -> SegmentOfResult) rethrows -> [SegmentOfResult.Iterator.Element]
public func flatMap<ElementOfResult>(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult]

  灵活处理数据,可以有效地提升开发效率,而这三种处理方式也是多语言支持的,所以不妨放下数不尽的相同的循环语句,尝试使用这些处理方式。但当然,不能纯粹为了使用而使用,好好思考使用条件与处理机制,处理后的数据才会成为真正需要的。