几个语言map/reduce的简单用法
filter/map/reduce 是较为简单、基建的一个工具函数。他们太常用了,特别是在对数据处理时候,一些较为繁琐的逻辑,可以通过他们做管道式拼接调用,组合出简约的表达。
本文用一个数组1-9 做以下简单操作:
1 过滤出奇数;
2 把上一步计算结果分别做平方处理;
3 把上一步的结果进行求和。
先假设上面三条是相互独立的,互不感知的一个业务需求,不以上帝目光把它们作为一个整体而封装在一个函数内,而是把他们看成业务需求独立的编排组合。它们随时都有交换顺序、修改逻辑、添加/删除逻辑条目等需求调整。通过map/filter/reduce 组合可使得代码调整随这些业务变化而可更灵活。
下面为各个语言版本demo。
php 版本
先说全球最好的语言php,php语言库已经提供了支持。它们配合匿名函数使用,代码挺精美。
<?php
$nums = [1,2,3,4,5,6,7,8,9];
$odds = array_filter($nums, function($n) { return $n % 2 == 1;});
$odds_square = array_map(function($n) { return $n * $n;}, $odds);
$result = array_reduce($odds_square, function($carry, $n) { return $carry + $n;}, 0);
print_r($result); // 165
python版本
这里是python3 的代码。
python 的 map 和 filter 语言库已经自带,他们的返回值分别是个 map 和 filter 对象,这两对象是个惰性序列的Iterator,如果需要结果为list,则通过list()函数来生成就可。
使用 reduce 只要 import from functools 就可。
它们配合 或者匿名函数或者lambda来用,会把逻辑写的非常简约好读。
#!/usr/bin/python
from functools import reduce
nums = [1,2,3,4,5,6,7,8,9]
odds = filter(lambda n:n % 2 == 1, nums)
odds_square = map(lambda n:n*n, odds)
result = reduce(lambda carry,n:carry+n, odds_square,0)
print(result) # 165
GO 版本
golang 不像脚本语言的灵活,需要自己定义map/filter/reduce 函数。
package main
import "fmt"
func MapInt(nums []int, fn func(int) int) []int {
newNums := make([]int, 0, len(nums))
for _, it := range nums {
newNums = append(newNums, fn(it))
}
return newNums
}
func FilterInt(nums []int, fn func(int) bool) []int {
newNums := make([]int, 0)
for _, it := range nums {
if fn(it) {
newNums = append(newNums, it)
}
}
return newNums
}
func ReduceInt(nums []int, fn func(int, interface{}) interface{}, carry interface{}) interface{} {
for _, it := range nums {
carry = fn(it, carry)
}
return carry
}
func main() {
nums := []int{1,2,3,4,5,6,7,8,9}
odds := FilterInt(nums, func(n int) bool {return n % 2 == 1 })
oddsSquare := MapInt(odds, func(n int) int {return n * n })
sumFun := func(n int, carry interface{}) interface{} {
// val, ok := carry.(int)
return n + carry.(int)
}
result := ReduceInt(oddsSquare, sumFun, 0)
fmt.Println(result) // 165
}
上面写了int 版本,对int64 版本不通用,需要做不少“猥琐”的处理才可做成通用的模版。但那样会使代码变得难读难用了。
参考耗子的文章:https://coolshell.cn/articles/21164.html
Java 版本
Java 8 API添加了一个新的抽象称为流Stream。它将要处理的元素集合看作一种流,把流在管道中传输执行链式处理。代码结构上,比其他语言更加流畅。
import java.util.stream.*;
import java.util.*;
public class MapReduce {
public static void main(String []args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
Integer result = numbers.stream().filter(n -> n % 2 == 1)
.map(n -> n * n)
.reduce(0,(carry,n) -> carry+n);
System.out.println(result);
}
}
C++ 版本
引用 algorithm 和 numeric 库就可使用 std::transform(对应 map)、std::copy_if(对应 filter)、std::accumulate(对应 reduce),他们用起来不像前面的语言那么短小,但凑合用。
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
using namespace std;
int main()
{
std::vector<int> nums{ 1, 2, 3, 4, 5, 6, 7, 8, 9 };
std::vector<int> cache (nums.size());
// filter
auto it = std::copy_if (nums.begin(),
nums.end(),
cache.begin(),
[](int n){return n % 2 == 1;});
// shrink container to new size
cache.resize(std::distance(cache.begin(),it));
// map
std::transform(cache.begin(),
cache.end(),
cache.begin(),
[](int n) -> int {return n * n; });
auto result = std::accumulate(cache.begin(),
cache.end(),
0,
[] (int carry, int n){ return carry + n;});
std::cout << result << std::endl;
return 0;
}
这几种语言对比,整体上来看动态语言用的相对溜点,使用得很轻盈。但单独来看 java 管道式流处理的最美,而且其 stream 还有其他很丰富的函数,可以组合出较多的逻辑。
本文仅是用简单例子说明了下几个语言的map/reduce 用法,并没有体现出来它们的好处,可能还真不如一个for 加个if 整个函数再来些逻辑简便。在复杂点的数据倒腾,用上map/reduce ,简约性才好体现出来,这些在实践久了就能体会得到。
(全文完)
(欢迎转载本站文章,但请注明作者和出处 云域 – Yuccn )