几个语言map/reduce的简单用法

几个语言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

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注