<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>MapReduce | 酷 壳 - CoolShell</title>
	<atom:link href="https://coolshell.cn/tag/mapreduce/feed" rel="self" type="application/rss+xml" />
	<link>https://coolshell.cn</link>
	<description>享受编程和技术所带来的快乐 - Coding Your Ambition</description>
	<lastBuildDate>Sat, 04 Sep 2021 05:44:02 +0000</lastBuildDate>
	<language>zh-CN</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.2</generator>
	<item>
		<title>Go编程模式 ： 泛型编程</title>
		<link>https://coolshell.cn/articles/21615.html</link>
					<comments>https://coolshell.cn/articles/21615.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Sat, 04 Sep 2021 05:44:02 +0000</pubDate>
				<category><![CDATA[Go 语言]]></category>
		<category><![CDATA[编程语言]]></category>
		<category><![CDATA[generics]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[golang]]></category>
		<category><![CDATA[MapReduce]]></category>
		<category><![CDATA[泛型]]></category>
		<guid isPermaLink="false">https://coolshell.cn/?p=21615</guid>

					<description><![CDATA[<p>Go语言的1.17版本发布了，其中开始正式支持泛型了。虽然还有一些限制（比如，不能把泛型函数export），但是，可以体验了。我的这个《Go编程模式》的系列终于...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/21615.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/21615.html">Go编程模式 ： 泛型编程</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><img decoding="async" loading="lazy" class="alignright wp-image-21627 " src="https://coolshell.cn/wp-content/uploads/2021/09/go-generics-1024x512.png" alt="" width="406" height="203" srcset="https://coolshell.cn/wp-content/uploads/2021/09/go-generics-1024x512.png 1024w, https://coolshell.cn/wp-content/uploads/2021/09/go-generics-300x150.png 300w, https://coolshell.cn/wp-content/uploads/2021/09/go-generics-768x384.png 768w, https://coolshell.cn/wp-content/uploads/2021/09/go-generics-540x270.png 540w, https://coolshell.cn/wp-content/uploads/2021/09/go-generics.png 1200w" sizes="(max-width: 406px) 100vw, 406px" />Go语言的1.17版本发布了，其中开始正式支持泛型了。虽然还有一些限制（比如，不能把泛型函数export），但是，可以体验了。我的这个《Go编程模式》的系列终于有了真正的泛型编程了，再也不需要使用反射或是go generation这些难用的技术了。周末的时候，我把Go 1.17下载下来，然后，体验了一下泛型编程，还是很不错的。下面，就让我们来看一下Go的泛型编程。（注：不过，如果你对泛型编程的重要性还不是很了解的话，你可以先看一下之前的这篇文章《<a title="Go 编程模式：Go Generation" href="https://coolshell.cn/articles/21179.html" target="_blank" rel="noopener">Go编程模式：Go Generation</a>》，然后再读一下《<a title="Go编程模式：Map-Reduce" href="https://coolshell.cn/articles/21164.html" target="_blank" rel="noopener">Go编程模式：MapReduce</a>》）</p>
<section class="post-series"><h3 class="post-series-title">本文是全系列中第10 / 10篇：<a href="https://coolshell.cn/articles/series/go%e7%bc%96%e7%a8%8b%e6%a8%a1%e5%bc%8f">Go编程模式</a></h3><ul class="post-series-list"><li class="post-series-item"><span class="post-series-item-title"><a href="https://coolshell.cn/articles/21128.html">Go编程模式：切片，接口，时间和性能</a></span></li><li class="post-series-item"><span class="post-series-item-title"><a href="https://coolshell.cn/articles/21140.html">Go 编程模式：错误处理</a></span></li><li class="post-series-item"><span class="post-series-item-title"><a href="https://coolshell.cn/articles/21146.html">Go 编程模式：Functional Options</a></span></li><li class="post-series-item"><span class="post-series-item-title"><a href="https://coolshell.cn/articles/21214.html">Go编程模式：委托和反转控制</a></span></li><li class="post-series-item"><span class="post-series-item-title"><a href="https://coolshell.cn/articles/21164.html">Go编程模式：Map-Reduce</a></span></li><li class="post-series-item"><span class="post-series-item-title"><a href="https://coolshell.cn/articles/21179.html">Go 编程模式：Go Generation</a></span></li><li class="post-series-item"><span class="post-series-item-title"><a href="https://coolshell.cn/articles/17929.html">Go编程模式：修饰器</a></span></li><li class="post-series-item"><span class="post-series-item-title"><a href="https://coolshell.cn/articles/21228.html">Go编程模式：Pipeline</a></span></li><li class="post-series-item"><span class="post-series-item-title"><a href="https://coolshell.cn/articles/21263.html">Go 编程模式：k8s Visitor 模式</a></span></li><li class="post-series-item-current post-series-item"><span class="post-series-item-title">Go编程模式 ： 泛型编程</span></li></ul><nav class="post-series-nav"><span class="post-series-nav-prev">&laquo; <a href="https://coolshell.cn/articles/21263.html" rel="prev" title="Go 编程模式：k8s Visitor 模式">上一篇文章</a></span></nav></section>
<h4>初探</h4>
<p>我们先来看一个简单的示例：</p>
<p><span id="more-21615"></span></p>
<pre class="EnlighterJSRAW" data-enlighter-language="golang">package main

import "fmt"

func print[T any] (arr []T) {
  for _, v := range arr {
    fmt.Print(v)
    fmt.Print(" ")
  }
  fmt.Println("")
}

func main() {
  strs := []string{"Hello", "World",  "Generics"}
  decs := []float64{3.14, 1.14, 1.618, 2.718 }
  nums := []int{2,4,6,8}

  print(strs)
  print(decs)
  print(nums)
}</pre>
<p>上面这个例子中，有一个 <code>print()</code> 函数，这个函数就是想输出数组的值，如果没有泛型的话，这个函数需要写出 <code>int</code> 版，<code>float</code>版，<code>string</code> 版，以及我们的自定义类型（<code>struct</code>）的版本。现在好了，有了泛型的支持后，我们可以使用 <code>[T any]</code> 这样的方式来声明一个泛型类型（有点像C++的 <code>typename T</code>），然后面都使用 <code>T</code> 来声明变量就好。</p>
<p>上面这个示例中，我们泛型的 <code>print()</code> 支持了三种类型的适配—— <code>int</code>型，<code>float64</code>型，和 <code>string</code>型。要让这段程序跑起来需要在编译行上加上 <code>-gcflags=-G=3</code>编译参数（这个编译参数会在1.18版上成为默认参数），如下所示：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="shell">$ go run -gcflags=-G=3 ./main.go</pre>
<p>有了个操作以后，我们就可以写一些标准的算法了，比如，一个查找的算法</p>
<pre class="EnlighterJSRAW" data-enlighter-language="golang">func find[T comparable] (arr []T, elem T) int {
  for i, v := range arr {
    if  v == elem {
      return i
    }
  }
  return -1
}</pre>
<p>我们注意到，我们没有使用 <code>[T any]</code>的形式，而是使用 <code>[T comparable]</code>的形式，<code>comparable</code>是一个接口类型，其约束了我们的类型需要支持 <code>==</code> 的操作， 不然就会有类型不对的编译错误。上面的这个 <code>find()</code> 函数同样可以使用于 <code>int</code>, <code>float64</code>或是<code>string</code>类型。</p>
<p>从上面的这两个小程序来看，Go语言的泛型已基本可用了，只不过，还有三个问题：</p>
<ul>
<li>一个是 <code>fmt.Printf()</code>中的泛型类型是 <code>%v</code> 还不够好，不能像c++ <code>iostream</code>重载 <code>&gt;&gt;</code> 来获得程序自定义的输出。</li>
<li>另外一个是，go不支持操作符重载，所以，你也很难在泛型算法中使用“泛型操作符”如：<code>==</code> 等</li>
<li>最后一个是，上面的 <code>find()</code> 算法依赖于“数组”，对于hash-table、tree、graph、link等数据结构还要重写。也就是说，没有一个像C++ STL那样的一个泛型迭代器（这其中的一部分工作当然也需要通过重载操作符（如：<code>++</code> 来实现）</li>
</ul>
<p>不过，这个已经很好了，让我们来看一下，可以干哪些事了。</p>
<h4>数据结构</h4>
<h5>Stack 栈</h5>
<p>编程支持泛型最大的优势就是可以实现类型无关的数据结构了。下面，我们用Slices这个结构体来实现一个Stack的数结构。</p>
<p>首先，我们可以定义一个泛型的Stack</p>
<pre class="EnlighterJSRAW" data-enlighter-language="golang">type stack [T any] []T</pre>
<p>看上去很简单，还是 <code>[T any]</code> ，然后 <code>[]T</code> 就是一个数组，接下来就是实现这个数据结构的各种方法了。下面的代码实现了 <code>push()</code> ，<code>pop()</code>，<code>top()</code>，<code>len()</code>，<code>print()</code>这几个方法，这几个方法和 C++的STL中的 Stack很类似。（注：目前Go的泛型函数不支持 export，所以只能使用第一个字符是小写的函数名）</p>
<pre class="EnlighterJSRAW" data-enlighter-language="golang">func (s *stack[T]) push(elem T) {
  *s = append(*s, elem)
}

func (s *stack[T]) pop() {
  if len(*s) &gt; 0 {
    *s = (*s)[:len(*s)-1]
  } 
}
func (s *stack[T]) top() *T{
  if len(*s) &gt; 0 {
    return &amp;(*s)[len(*s)-1]
  } 
  return nil
}

func (s *stack[T]) len() int{
  return len(*s)
}

func (s *stack[T]) print() {
  for _, elem := range *s {
    fmt.Print(elem)
    fmt.Print(" ")
  }
  fmt.Println("")
}</pre>
<p>上面的这个例子还是比较简单的，不过在实现的过程中，对于一个如果栈为空，那么 <code>top()</code>要么返回<code>error</code>要么返回空值，在这个地方卡了一下。因为，之前，我们返回的“空”值，要么是 int 的<code>0</code>，要么是 string 的 <code>“”</code>，然而在泛型的<code>T</code>下，这个值就不容易搞了。也就是说，除了类型泛型后，还需要有一些“值的泛型”（注：在C++中，如果你要用一个空栈进行 <code>top()</code> 操作，你会得到一个 segmentation fault），所以，这里我们返回的是一个指针，这样可以判断一下指针是否为空。</p>
<p>下面是如何使用这个stack的代码。</p>
<pre class="EnlighterJSRAW" data-enlighter-language="golang">func main() {

  ss := stack[string]{}
  ss.push("Hello")
  ss.push("Hao")
  ss.push("Chen")
  ss.print()
  fmt.Printf("stack top is - %v\n", *(ss.top()))
  ss.pop()
  ss.pop()
  ss.print()

  
  ns := stack[int]{}
  ns.push(10)
  ns.push(20)
  ns.print()
  ns.pop()
  ns.print()
  *ns.top() += 1
  ns.print()
  ns.pop()
  fmt.Printf("stack top is - %v\n", ns.top())

}</pre>
<p>&nbsp;</p>
<h5>LinkList 双向链表</h5>
<p>下面我们再来看一个双向链表的实现。下面这个实现中实现了 这几个方法：</p>
<ul>
<li><code>add()</code> &#8211; 从头插入一个数据结点</li>
<li><code>push()</code> &#8211; 从尾插入一个数据结点</li>
<li><code>del()</code> &#8211; 删除一个结点（因为需要比较，所以使用了 <code>compareable</code> 的泛型）</li>
<li><code>print()</code> &#8211; 从头遍历一个链表，并输出值。</li>
</ul>
<pre class="EnlighterJSRAW" data-enlighter-language="golang">type node[T comparable] struct {
  data T
  prev *node[T]
  next *node[T]
}

type list[T comparable] struct {
  head, tail *node[T]
  len int
}

func (l *list[T]) isEmpty() bool {
  return l.head == nil &amp;&amp; l.tail == nil
}

func (l *list[T]) add(data T) {
  n := &amp;node[T] {
    data : data,
    prev : nil,
    next : l.head,
  }
  if l.isEmpty() {
    l.head = n
    l.tail = n
  }
  l.head.prev = n
  l.head = n
}

func (l *list[T]) push(data T) { 
  n := &amp;node[T] {
    data : data,
    prev : l.tail,
    next : nil,
  }
  if l.isEmpty() {
    l.head = n
    l.tail = n
  }
  l.tail.next = n
  l.tail = n
}

func (l *list[T]) del(data T) { 
  for p := l.head; p != nil; p = p.next {
    if data == p.data {
      
      if p == l.head {
        l.head = p.next
      }
      if p == l.tail {
        l.tail = p.prev
      }
      if p.prev != nil {
        p.prev.next = p.next
      }
      if p.next != nil {
        p.next.prev = p.prev
      }
      return 
    }
  } 
}

func (l *list[T]) print() {
  if l.isEmpty() {
    fmt.Println("the link list is empty.")
    return 
  }
  for p := l.head; p != nil; p = p.next {
    fmt.Printf("[%v] -&gt; ", p.data)
  }
  fmt.Println("nil")
}</pre>
<p>上面这个代码都是一些比较常规的链表操作，学过链表数据结构的同学应该都不陌生，使用的代码也不难，如下所示，都很简单，看代码就好了。</p>
<pre class="EnlighterJSRAW" data-enlighter-language="golang">func main(){
  var l = list[int]{}
  l.add(1)
  l.add(2)
  l.push(3)
  l.push(4)
  l.add(5)
  l.print() //[5] -&gt; [2] -&gt; [1] -&gt; [3] -&gt; [4] -&gt; nil
  l.del(5)
  l.del(1)
  l.del(4)
  l.print() //[2] -&gt; [3] -&gt; nil
  
}</pre>
<h4>函数式范型</h4>
<p>接下来，我们就要来看一下我们函数式编程的三大件 <code>map()</code> 、 <code>reduce()</code> 和 <code>filter()</code> 在之前的《<a title="Go编程模式：Map-Reduce" href="https://coolshell.cn/articles/21164.html" target="_blank" rel="noopener">Go编程模式：Map-Reduce</a>》文章中，我们可以看到要实现这样的泛型，需要用到反射，代码复杂到完全读不懂。下面来看一下真正的泛型版本。</p>
<h5>泛型Map</h5>
<pre class="EnlighterJSRAW" data-enlighter-language="golang">func gMap[T1 any, T2 any] (arr []T1, f func(T1) T2) []T2 {
  result := make([]T2, len(arr))
  for i, elem := range arr {
    result[i] = f(elem)
  }
  return result
}</pre>
<p>在上面的这个 map函数中我使用了两个类型 &#8211; <code>T1</code> 和 <code>T2</code> ，</p>
<ul>
<li><code>T1</code> &#8211; 是需要处理数据的类型</li>
<li><code>T2</code> &#8211; 是处理后的数据类型</li>
</ul>
<p><code>T1</code> 和 <code>T2</code> 可以一样，也可以不一样。</p>
<p>我们还有一个函数参数 &#8211;  <code>func(T1) T2</code> 意味着，进入的是 <code>T1</code> 类型的，出来的是 <code>T2</code> 类型的。</p>
<p>然后，整个函数返回的是一个 <code>[]T2</code></p>
<p>好的，我们来看一下怎么使用这个map函数：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="golang">nums := []int {0,1,2,3,4,5,6,7,8,9}
squares := gMap(nums, func (elem int) int {
  return elem * elem
})
print(squares)  //0 1 4 9 16 25 36 49 64 81 

strs := []string{"Hao", "Chen", "MegaEase"}
upstrs := gMap(strs, func(s string) string  {
  return strings.ToUpper(s)
})
print(upstrs) // HAO CHEN MEGAEASE 


dict := []string{"零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖"}
strs =  gMap(nums, func (elem int) string  {
  return  dict[elem]
})
print(strs) // 零 壹 贰 叁 肆 伍 陆 柒 捌 玖</pre>
<h5>泛型 Reduce</h5>
<p>接下来，我们再来看一下我们的Reduce函数，reduce函数是把一堆数据合成一个。</p>
<pre class="EnlighterJSRAW" data-enlighter-language="golang">func gReduce[T1 any, T2 any] (arr []T1, init T2, f func(T2, T1) T2) T2 {
  result := init
  for _, elem := range arr {
    result = f(result, elem)
  }
  return result
}</pre>
<p>函数实现起来很简单，但是感觉不是很优雅。</p>
<ul>
<li>也是有两个类型 <code>T1</code> 和 <code>T2</code>，前者是输出数据的类型，后者是佃出数据的类型。</li>
<li>因为要合成一个数据，所以需要有这个数据的初始值 <code>init</code>，是 <code>T2</code> 类型</li>
<li>而自定义函数 <code>func(T2, T1) T2</code>，会把这个init值传给用户，然后用户处理完后再返回出来。</li>
</ul>
<p>下面是一个使用上的示例——求一个数组的和</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">nums := []int {0,1,2,3,4,5,6,7,8,9}
sum := gReduce(nums, 0, func (result, elem int) int  {
    return result + elem
})
fmt.Printf("Sum = %d \n", sum)</pre>
<h5>泛型 filter</h5>
<p>filter函数主要是用来做过滤的，把数据中一些符合条件（filter in）或是不符合条件（filter out）的数据过滤出来，下面是相关的代码示例</p>
<pre class="EnlighterJSRAW" data-enlighter-language="golang">func gFilter[T any] (arr []T, in bool, f func(T) bool) []T {
  result := []T{}
  for _, elem := range arr {
    choose := f(elem)
    if (in &amp;&amp; choose) || (!in &amp;&amp; !choose) {
      result = append(result, elem)
    }
  }
  return result
}

func gFilterIn[T any] (arr []T, f func(T) bool) []T {
  return gFilter(arr, true, f)
}

func gFilterOut[T any] (arr []T, f func(T) bool) []T {
  return gFilter(arr, false, f)
}</pre>
<p>其中，用户需要提从一个 <code>bool</code> 的函数，我们会把数据传给用户，然后用户只需要告诉我行还是不行，于是我们就会返回一个过滤好的数组给用户。</p>
<p>比如，我们想把数组中所有的奇数过滤出来</p>
<pre class="EnlighterJSRAW" data-enlighter-language="golang">nums := []int {0,1,2,3,4,5,6,7,8,9}
odds := gFilterIn(nums, func (elem int) bool  {
    return elem % 2 == 1
})
print(odds)</pre>
<h4>业务示例</h4>
<p>正如《<a title="Go编程模式：Map-Reduce" href="https://coolshell.cn/articles/21164.html" target="_blank" rel="noopener">Go编程模式：Map-Reduce</a>》中的那个业务示例，我们在这里再做一遍。</p>
<p>首先，我们先声明一个员工对象和相关的数据</p>
<pre class="EnlighterJSRAW" data-enlighter-language="golang">type Employee struct {
  Name     string
  Age      int
  Vacation int
  Salary   float32
}

var employees = []Employee{
  {"Hao", 44, 0, 8000.5},
  {"Bob", 34, 10, 5000.5},
  {"Alice", 23, 5, 9000.0},
  {"Jack", 26, 0, 4000.0},
  {"Tom", 48, 9, 7500.75},
  {"Marry", 29, 0, 6000.0},
  {"Mike", 32, 8, 4000.3},
}</pre>
<p>然后，我们想统一下所有员工的薪水，我们就可以使用前面的reduce函数</p>
<pre class="EnlighterJSRAW" data-enlighter-language="golang">total_pay := gReduce(employees, 0.0, func(result float32, e Employee) float32 {
  return result + e.Salary
})
fmt.Printf("Total Salary: %0.2f\n", total_pay) // Total Salary: 43502.05</pre>
<p>我们函数这个 <code>gReduce</code> 函数有点啰嗦，还需要传一个初始值，在用户自己的函数中，还要关心 <code>result</code> 我们还是来定义一个更好的版本。</p>
<p>一般来说，我们用 reduce 函数大多时候基本上是统计求和或是数个数，所以，是不是我们可以定义的更为直接一些？比如下面的这个 <code>CountIf()</code>，就比上面的 Reduce 干净了很多。</p>
<pre class="EnlighterJSRAW" data-enlighter-language="golang">func gCountIf[T any](arr []T, f func(T) bool) int {
  cnt := 0
  for _, elem := range arr {
    if f(elem) {
      cnt += 1
    }
  }
  return cnt;
}</pre>
<p>我们做求和，我们也可以写一个Sum的泛型。</p>
<ul>
<li>处理 <code>T</code> 类型的数据，返回 <code>U</code>类型的结果</li>
<li>然后，用户只需要给我一个需要统计的 <code>T</code> 的 <code>U</code> 类型的数据就可以了。</li>
</ul>
<p>代码如下所示：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="golang">type Sumable interface {
  type int, int8, int16, int32, int64,
        uint, uint8, uint16, uint32, uint64,
        float32, float64
}

func gSum[T any, U Sumable](arr []T, f func(T) U) U {
  var sum U
  for _, elem := range arr {
    sum += f(elem)
  }
  return sum
}</pre>
<p>上面的代码我们动用了一个叫 Sumable 的接口，其限定了 U 类型，只能是 Sumable里的那些类型，也就是整型或浮点型，这个支持可以让我们的泛型代码更健壮一些。</p>
<p>于是，我们就可以完成下面的事了。</p>
<p><strong>1）统计年龄大于40岁的员工数</strong></p>
<pre class="EnlighterJSRAW" data-enlighter-language="golang">old := gCountIf(employees, func (e Employee) bool  {
    return e.Age &gt; 40
})
fmt.Printf("old people(&gt;40): %d\n", old) 
// ld people(&gt;40): 2</pre>
<p><strong>2）统计薪水超过 6000元的员工数</strong></p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">high_pay := gCountIf(employees, func(e Employee) bool {
  return e.Salary &gt;= 6000
})
fmt.Printf("High Salary people(&gt;6k): %d\n", high_pay) 
//High Salary people(&gt;6k): 4</pre>
<p><strong>3）统计年龄小于30岁的员工的薪水</strong></p>
<pre class="EnlighterJSRAW" data-enlighter-language="golang">younger_pay := gSum(employees, func(e Employee) float32 {
  if e.Age &lt; 30 {
      return e.Salary
  } 
  return 0
})
fmt.Printf("Total Salary of Young People: %0.2f\n", younger_pay)
//Total Salary of Young People: 19000.00</pre>
<p><strong>4）统计全员的休假天数</strong></p>
<pre class="EnlighterJSRAW" data-enlighter-language="golang">total_vacation := gSum(employees, func(e Employee) int {
  return e.Vacation
})
fmt.Printf("Total Vacation: %d day(s)\n", total_vacation)
//Total Vacation: 32 day(s)</pre>
<p><strong>5）把没有休假的员工过滤出来</strong></p>
<pre class="EnlighterJSRAW" data-enlighter-language="golang">no_vacation := gFilterIn(employees, func(e Employee) bool {
  return e.Vacation == 0
})
print(no_vacation)
//{Hao 44 0 8000.5} {Jack 26 0 4000} {Marry 29 0 6000}</pre>
<p>怎么样，你大概了解了泛型编程的意义了吧。</p>
<p>（全文完）<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" id="wp_rp_first"><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/21164.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2020/12/go.map_.reduce-150x150.png" alt="Go编程模式：Map-Reduce" width="150" height="150" /></a><a href="https://coolshell.cn/articles/21164.html" class="wp_rp_title">Go编程模式：Map-Reduce</a></li><li ><a href="https://coolshell.cn/articles/21263.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2020/12/go.k8s-150x150.png" alt="Go 编程模式：k8s Visitor 模式" width="150" height="150" /></a><a href="https://coolshell.cn/articles/21263.html" class="wp_rp_title">Go 编程模式：k8s Visitor 模式</a></li><li ><a href="https://coolshell.cn/articles/21228.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2020/12/go.line_.-150x150.png" alt="Go编程模式：Pipeline" width="150" height="150" /></a><a href="https://coolshell.cn/articles/21228.html" class="wp_rp_title">Go编程模式：Pipeline</a></li><li ><a href="https://coolshell.cn/articles/21214.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2020/12/go.pair_-150x150.png" alt="Go编程模式：委托和反转控制" width="150" height="150" /></a><a href="https://coolshell.cn/articles/21214.html" class="wp_rp_title">Go编程模式：委托和反转控制</a></li><li ><a href="https://coolshell.cn/articles/21179.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2020/12/go.generate-150x150.png" alt="Go 编程模式：Go Generation" width="150" height="150" /></a><a href="https://coolshell.cn/articles/21179.html" class="wp_rp_title">Go 编程模式：Go Generation</a></li><li ><a href="https://coolshell.cn/articles/21146.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2020/12/go.options-150x150.png" alt="Go 编程模式：Functional Options" width="150" height="150" /></a><a href="https://coolshell.cn/articles/21146.html" class="wp_rp_title">Go 编程模式：Functional Options</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/21615.html">Go编程模式 ： 泛型编程</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/21615.html/feed</wfw:commentRss>
			<slash:comments>14</slash:comments>
		
		
			</item>
		<item>
		<title>Go编程模式：Map-Reduce</title>
		<link>https://coolshell.cn/articles/21164.html</link>
					<comments>https://coolshell.cn/articles/21164.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Thu, 24 Dec 2020 07:13:52 +0000</pubDate>
				<category><![CDATA[Go 语言]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[编程语言]]></category>
		<category><![CDATA[functional]]></category>
		<category><![CDATA[functional-programming]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[golang]]></category>
		<category><![CDATA[MapReduce]]></category>
		<category><![CDATA[函数式]]></category>
		<category><![CDATA[函数式编程]]></category>
		<guid isPermaLink="false">https://coolshell.cn/?p=21164</guid>

					<description><![CDATA[<p>在本篇文章中，我们学习一下函数式编程的中非常重要的Map、Reduce、Filter的三种操作，这三种操作可以让我们非常方便灵活地进行一些数据处理——我们的程序...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/21164.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/21164.html">Go编程模式：Map-Reduce</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><img decoding="async" loading="lazy" class="alignright size-medium wp-image-21251" src="https://coolshell.cn/wp-content/uploads/2020/12/go.map_.reduce-300x192.png" alt="" width="300" height="192" srcset="https://coolshell.cn/wp-content/uploads/2020/12/go.map_.reduce-300x192.png 300w, https://coolshell.cn/wp-content/uploads/2020/12/go.map_.reduce-768x491.png 768w, https://coolshell.cn/wp-content/uploads/2020/12/go.map_.reduce-422x270.png 422w, https://coolshell.cn/wp-content/uploads/2020/12/go.map_.reduce.png 992w" sizes="(max-width: 300px) 100vw, 300px" />在本篇文章中，我们学习一下函数式编程的中非常重要的Map、Reduce、Filter的三种操作，这三种操作可以让我们非常方便灵活地进行一些数据处理——我们的程序中大多数情况下都是在到倒腾数据，尤其对于一些需要统计的业务场景，Map/Reduce/Filter是非常通用的玩法。下面先来看几个例子：</p>
<section class="post-series"><h3 class="post-series-title">本文是全系列中第5 / 10篇：<a href="https://coolshell.cn/articles/series/go%e7%bc%96%e7%a8%8b%e6%a8%a1%e5%bc%8f">Go编程模式</a></h3><ul class="post-series-list"><li class="post-series-item"><span class="post-series-item-title"><a href="https://coolshell.cn/articles/21128.html">Go编程模式：切片，接口，时间和性能</a></span></li><li class="post-series-item"><span class="post-series-item-title"><a href="https://coolshell.cn/articles/21140.html">Go 编程模式：错误处理</a></span></li><li class="post-series-item"><span class="post-series-item-title"><a href="https://coolshell.cn/articles/21146.html">Go 编程模式：Functional Options</a></span></li><li class="post-series-item"><span class="post-series-item-title"><a href="https://coolshell.cn/articles/21214.html">Go编程模式：委托和反转控制</a></span></li><li class="post-series-item-current post-series-item"><span class="post-series-item-title">Go编程模式：Map-Reduce</span></li><li class="post-series-item"><span class="post-series-item-title"><a href="https://coolshell.cn/articles/21179.html">Go 编程模式：Go Generation</a></span></li><li class="post-series-item"><span class="post-series-item-title"><a href="https://coolshell.cn/articles/17929.html">Go编程模式：修饰器</a></span></li><li class="post-series-item"><span class="post-series-item-title"><a href="https://coolshell.cn/articles/21228.html">Go编程模式：Pipeline</a></span></li><li class="post-series-item"><span class="post-series-item-title"><a href="https://coolshell.cn/articles/21263.html">Go 编程模式：k8s Visitor 模式</a></span></li><li class="post-series-item"><span class="post-series-item-title"><a href="https://coolshell.cn/articles/21615.html">Go编程模式 ： 泛型编程</a></span></li></ul><nav class="post-series-nav"><span class="post-series-nav-prev">&laquo; <a href="https://coolshell.cn/articles/21214.html" rel="prev" title="Go编程模式：委托和反转控制">上一篇文章</a></span><span class="post-series-nav-next"><a href="https://coolshell.cn/articles/21179.html" rel="next" title="Go 编程模式：Go Generation">下一篇文章</a> &raquo;</span></nav></section>
<h4>基本示例</h4>
<h5>Map示例</h5>
<p>下面的程序代码中，我们写了两个Map函数，这两个函数需要两个参数，</p>
<ul>
<li>一个是字符串数组 <code>[]string</code>，说明需要处理的数据一个字符串</li>
<li>另一个是一个函数<code>func(s string) string</code> 或 <code>func(s string) int</code></li>
</ul>
<pre class="EnlighterJSRAW" data-enlighter-language="golang">func MapStrToStr(arr []string, fn func(s string) string) []string {
    var newArray = []string{}
    for _, it := range arr {
        newArray = append(newArray, fn(it))
    }
    return newArray
}

func MapStrToInt(arr []string, fn func(s string) int) []int {
    var newArray = []int{}
    for _, it := range arr {
        newArray = append(newArray, fn(it))
    }
    return newArray
}</pre>
<p>整个Map函数运行逻辑都很相似，函数体都是在遍历第一个参数的数组，然后，调用第二个参数的函数，然后把其值组合成另一个数组返回。</p>
<p><span id="more-21164"></span></p>
<p>于是我们就可以这样使用这两个函数：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="golang">var list = []string{"Hao", "Chen", "MegaEase"}

x := MapStrToStr(list, func(s string) string {
    return strings.ToUpper(s)
})
fmt.Printf("%v\n", x)
//["HAO", "CHEN", "MEGAEASE"]

y := MapStrToInt(list, func(s string) int {
    return len(s)
})
fmt.Printf("%v\n", y)
//[3, 4, 8]</pre>
<p>我们可以看到，我们给第一个 <code>MapStrToStr()</code> 传了函数做的是 转大写，于是出来的数组就成了全大写的，给<code>MapStrToInt()</code> 传的是算其长度，所以出来的数组是每个字符串的长度。</p>
<p>我们再来看一下Reduce和Filter的函数是什么样的。</p>
<h5><strong>Reduce 示例</strong></h5>
<pre class="EnlighterJSRAW" data-enlighter-language="golang">func Reduce(arr []string, fn func(s string) int) int {
    sum := 0
    for _, it := range arr {
        sum += fn(it)
    }
    return sum
}

var list = []string{"Hao", "Chen", "MegaEase"}

x := Reduce(list, func(s string) int {
    return len(s)
})
fmt.Printf("%v\n", x)
// 15
</pre>
<h5><strong>Filter示例</strong></h5>
<pre class="EnlighterJSRAW" data-enlighter-language="golang">func Filter(arr []int, fn func(n int) bool) []int {
    var newArray = []int{}
    for _, it := range arr {
        if fn(it) {
            newArray = append(newArray, it)
        }
    }
    return newArray
}

var intset = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
out := Filter(intset, func(n int) bool {
   return n%2 == 1
})
fmt.Printf("%v\n", out)

out = Filter(intset, func(n int) bool {
    return n &gt; 5
})
fmt.Printf("%v\n", out)
</pre>
<p>下图是一个比喻，其非常形象地说明了Map-Reduce是的业务语义，其在数据处理中非常有用。</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-21169" src="https://coolshell.cn/wp-content/uploads/2020/12/map-reduce.png" alt="" width="794" height="442" srcset="https://coolshell.cn/wp-content/uploads/2020/12/map-reduce.png 794w, https://coolshell.cn/wp-content/uploads/2020/12/map-reduce-300x167.png 300w, https://coolshell.cn/wp-content/uploads/2020/12/map-reduce-768x428.png 768w, https://coolshell.cn/wp-content/uploads/2020/12/map-reduce-360x200.png 360w, https://coolshell.cn/wp-content/uploads/2020/12/map-reduce-485x270.png 485w" sizes="(max-width: 794px) 100vw, 794px" /></p>
<h4>业务示例</h4>
<p>通过上面的一些示例，你可能有一些明白，Map/Reduce/Filter只是一种控制逻辑，真正的业务逻辑是在传给他们的数据和那个函数来定义的。是的，这是一个很经典的“业务逻辑”和“控制逻辑”分离解耦的编程模式。下面我们来看一个有业务意义的代码，来让大家强化理解一下什么叫“控制逻辑”与业务逻辑分离。</p>
<h5>员工信息</h5>
<p>首先，我们一个员工对象，以及一些数据</p>
<pre class="EnlighterJSRAW" data-enlighter-language="golang">type Employee struct {
    Name     string
    Age      int
    Vacation int
    Salary   int
}

var list = []Employee{
    {"Hao", 44, 0, 8000},
    {"Bob", 34, 10, 5000},
    {"Alice", 23, 5, 9000},
    {"Jack", 26, 0, 4000},
    {"Tom", 48, 9, 7500},
    {"Marry", 29, 0, 6000},
    {"Mike", 32, 8, 4000},
}</pre>
<h5>相关的Reduce/Fitler函数</h5>
<p>然后，我们有如下的几个函数：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">func EmployeeCountIf(list []Employee, fn func(e *Employee) bool) int {
    count := 0
    for i, _ := range list {
        if fn(&amp;list[i]) {
            count += 1
        }
    }
    return count
}

func EmployeeFilterIn(list []Employee, fn func(e *Employee) bool) []Employee {
    var newList []Employee
    for i, _ := range list {
        if fn(&amp;list[i]) {
            newList = append(newList, list[i])
        }
    }
    return newList
}

func EmployeeSumIf(list []Employee, fn func(e *Employee) int) int {
    var sum = 0
    for i, _ := range list {
        sum += fn(&amp;list[i])
    }
    return sum
}</pre>
<p>简单说明一下：</p>
<ul>
<li><code>EmployeeConutIf</code> 和 <code>EmployeeSumIf</code> 分别用于统满足某个条件的个数或总数。它们都是Filter + Reduce的语义。</li>
<li><code>EmployeeFilterIn</code> 就是按某种条件过虑。就是Fitler的语义。</li>
</ul>
<h5>各种自定义的统计示例</h5>
<p>于是我们就可以有如下的代码。</p>
<p><strong>1）统计有多少员工大于40岁</strong></p>
<pre class="EnlighterJSRAW" data-enlighter-language="golang">old := EmployeeCountIf(list, func(e *Employee) bool {
    return e.Age &gt; 40
})
fmt.Printf("old people: %d\n", old)
//old people: 2
</pre>
<p><strong>2）统计有多少员工薪水大于6000</strong></p>
<pre class="EnlighterJSRAW" data-enlighter-language="golang">high_pay := EmployeeCountIf(list, func(e *Employee) bool {
    return e.Salary &gt;= 6000
})
fmt.Printf("High Salary people: %d\n", high_pay)
//High Salary people: 4
</pre>
<p><strong>3）列出有没有休假的员工</strong></p>
<pre class="EnlighterJSRAW" data-enlighter-language="golang">no_vacation := EmployeeFilterIn(list, func(e *Employee) bool {
    return e.Vacation == 0
})
fmt.Printf("People no vacation: %v\n", no_vacation)
//People no vacation: [{Hao 44 0 8000} {Jack 26 0 4000} {Marry 29 0 6000}]
</pre>
<p><strong>4）统计所有员工的薪资总和</strong></p>
<pre class="EnlighterJSRAW" data-enlighter-language="golang">total_pay := EmployeeSumIf(list, func(e *Employee) int {
    return e.Salary
})

fmt.Printf("Total Salary: %d\n", total_pay)
//Total Salary: 43500
</pre>
<p><strong>5）统计30岁以下员工的薪资总和</strong></p>
<pre class="EnlighterJSRAW" data-enlighter-language="golang">younger_pay := EmployeeSumIf(list, func(e *Employee) int {
    if e.Age &lt; 30 {
        return e.Salary
    } 
    return 0
})</pre>
<h4>泛型Map-Reduce</h4>
<p>我们可以看到，上面的Map-Reduce都因为要处理数据的类型不同而需要写出不同版本的Map-Reduce，虽然他们的代码看上去是很类似的。所以，这里就要带出来泛型编程了，Go语言在本文写作的时候还不支持泛型（注：Go开发团队技术负责人Russ Cox在2012年11月21golang-dev上的mail确认了Go泛型(type parameter)将在Go 1.18版本落地，即2022.2月份）。</p>
<h5>简单版 Generic Map</h5>
<p>所以，目前的Go语言的泛型只能用 <code>interface{}</code> + <code>reflect</code>来完成，<code>interface{}</code> 可以理解为C中的 <code>void*</code>，Java中的 <code>Object</code> ，<code>reflect</code>是Go的反射机制包，用于在运行时检查类型。</p>
<p>下面我们来看一下一个非常简单不作任何类型检查的泛型的Map函数怎么写。</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">func Map(data interface{}, fn interface{}) []interface{} {
    vfn := reflect.ValueOf(fn)
    vdata := reflect.ValueOf(data)
    result := make([]interface{}, vdata.Len())

    for i := 0; i &lt; vdata.Len(); i++ {
        result[i] = vfn.Call([]reflect.Value{vdata.Index(i)})[0].Interface()
    }
    return result
}</pre>
<p>上面的代码中，</p>
<ul>
<li>通过 <code>reflect.ValueOf()</code> 来获得 <code>interface{}</code> 的值，其中一个是数据 <code>vdata</code>，另一个是函数 <code>vfn</code>，</li>
<li>然后通过 <code>vfn.Call()</code> 方法来调用函数，通过 <code>[]refelct.Value{vdata.Index(i)}</code>来获得数据。</li>
</ul>
<p>Go语言中的反射的语法还是有点令人费解的，但是简单看一下手册还是能够读懂的。我这篇文章不讲反射，所以相关的基础知识还请大家自行Google相关的教程。</p>
<p>于是，我们就可以有下面的代码——不同类型的数据可以使用相同逻辑的<code>Map()</code>代码。</p>
<pre class="EnlighterJSRAW" data-enlighter-language="golang">square := func(x int) int {
  return x * x
}
nums := []int{1, 2, 3, 4}

squared_arr := Map(nums,square)
fmt.Println(squared_arr)
//[1 4 9 16]



upcase := func(s string) string {
  return strings.ToUpper(s)
}
strs := []string{"Hao", "Chen", "MegaEase"}
upstrs := Map(strs, upcase);
fmt.Println(upstrs)
//[HAO CHEN MEGAEASE]</pre>
<p>但是因为反射是运行时的事，所以，如果类型什么出问题的话，就会有运行时的错误。比如：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="golang">x := Map(5, 5)
fmt.Println(x)</pre>
<p>上面的代码可以很轻松的编译通过，但是在运行时就出问题了，还是panic错误……</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">panic: reflect: call of reflect.Value.Len on int Value

goroutine 1 [running]:
reflect.Value.Len(0x10b5240, 0x10eeb58, 0x82, 0x10716bc)
        /usr/local/Cellar/go/1.15.3/libexec/src/reflect/value.go:1162 +0x185
main.Map(0x10b5240, 0x10eeb58, 0x10b5240, 0x10eeb60, 0x1, 0x14, 0x0)
        /Users/chenhao/.../map.go:12 +0x16b
main.main()
        /Users/chenhao/.../map.go:42 +0x465
exit status 2</pre>
<h5>健壮版的Generic Map</h5>
<p>所以，如果要写一个健壮的程序，对于这种用<code>interface{}</code> 的“过度泛型”，就需要我们自己来做类型检查。下面是一个有类型检查的Map代码：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="golang">func Transform(slice, function interface{}) interface{} {
  return transform(slice, function, false)
}

func TransformInPlace(slice, function interface{}) interface{} {
  return transform(slice, function, true)
}

func transform(slice, function interface{}, inPlace bool) interface{} {
 
  //check the <code data-enlighter-language="raw" class="EnlighterJSRAW">slice</code> type is Slice
  sliceInType := reflect.ValueOf(slice)
  if sliceInType.Kind() != reflect.Slice {
    panic("transform: not slice")
  }

  //check the function signature
  fn := reflect.ValueOf(function)
  elemType := sliceInType.Type().Elem()
  if !verifyFuncSignature(fn, elemType, nil) {
    panic("trasform: function must be of type func(" + sliceInType.Type().Elem().String() + ") outputElemType")
  }

  sliceOutType := sliceInType
  if !inPlace {
    sliceOutType = reflect.MakeSlice(reflect.SliceOf(fn.Type().Out(0)), sliceInType.Len(), sliceInType.Len())
  }
  for i := 0; i &lt; sliceInType.Len(); i++ {
    sliceOutType.Index(i).Set(fn.Call([]reflect.Value{sliceInType.Index(i)})[0])
  }
  return sliceOutType.Interface()

}

func verifyFuncSignature(fn reflect.Value, types ...reflect.Type) bool {

  //Check it is a funciton
  if fn.Kind() != reflect.Func {
    return false
  }
  // NumIn() - returns a function type's input parameter count.
  // NumOut() - returns a function type's output parameter count.
  if (fn.Type().NumIn() != len(types)-1) || (fn.Type().NumOut() != 1) {
    return false
  }
  // In() - returns the type of a function type's i'th input parameter.
  for i := 0; i &lt; len(types)-1; i++ {
    if fn.Type().In(i) != types[i] {
      return false
    }
  }
  // Out() - returns the type of a function type's i'th output parameter.
  outType := types[len(types)-1]
  if outType != nil &amp;&amp; fn.Type().Out(0) != outType {
    return false
  }
  return true
}
</pre>
<p>上面的代码一下子就复杂起来了，可见，复杂的代码都是在处理异常的地方。我不打算Walk through 所有的代码，别看代码多，但是还是可以读懂的，下面列几个代码中的要点：</p>
<ul>
<li>代码中没有使用Map函数，因为和数据结构和关键有含义冲突的问题，所以使用<code>Transform</code>，这个来源于 C++ STL库中的命名。</li>
<li>有两个版本的函数，一个是返回一个全新的数组 &#8211; <code>Transform()</code>，一个是“就地完成” &#8211; <code>TransformInPlace()</code></li>
<li>在主函数中，用 <code>Kind()</code> 方法检查了数据类型是不是 Slice，函数类型是不是Func</li>
<li>检查函数的参数和返回类型是通过 <code>verifyFuncSignature()</code> 来完成的，其中：
<ul>
<li><code>NumIn()</code> &#8211; 用来检查函数的“入参”</li>
<li> <code>NumOut()</code> 用来检查函数的“返回值”</li>
</ul>
</li>
<li>如果需要新生成一个Slice，会使用 <code>reflect.MakeSlice()</code> 来完成。</li>
</ul>
<p>好了，有了上面的这段代码，我们的代码就很可以很开心的使用了：</p>
<p>可以用于字符串数组</p>
<pre class="EnlighterJSRAW" data-enlighter-language="golang">list := []string{"1", "2", "3", "4", "5", "6"}
result := Transform(list, func(a string) string{
    return a +a +a
})
//{"111","222","333","444","555","666"}
</pre>
<p>可以用于整形数组</p>
<pre class="EnlighterJSRAW" data-enlighter-language="golang">list := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
TransformInPlace(list, func (a int) int {
  return a*3
})
//{3, 6, 9, 12, 15, 18, 21, 24, 27}
</pre>
<p>可以用于结构体</p>
<pre class="EnlighterJSRAW" data-enlighter-language="golang">var list = []Employee{
    {"Hao", 44, 0, 8000},
    {"Bob", 34, 10, 5000},
    {"Alice", 23, 5, 9000},
    {"Jack", 26, 0, 4000},
    {"Tom", 48, 9, 7500},
}

result := TransformInPlace(list, func(e Employee) Employee {
    e.Salary += 1000
    e.Age += 1
    return e
})</pre>
<h5>健壮版的 Generic Reduce</h5>
<p>同样，泛型版的 Reduce 代码如下：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="golang">func Reduce(slice, pairFunc, zero interface{}) interface{} {
  sliceInType := reflect.ValueOf(slice)
  if sliceInType.Kind() != reflect.Slice {
    panic("reduce: wrong type, not slice")
  }

  len := sliceInType.Len()
  if len == 0 {
    return zero
  } else if len == 1 {
    return sliceInType.Index(0)
  }

  elemType := sliceInType.Type().Elem()
  fn := reflect.ValueOf(pairFunc)
  if !verifyFuncSignature(fn, elemType, elemType, elemType) {
    t := elemType.String()
    panic("reduce: function must be of type func(" + t + ", " + t + ") " + t)
  }

  var ins [2]reflect.Value
  ins[0] = sliceInType.Index(0)
  ins[1] = sliceInType.Index(1)
  out := fn.Call(ins[:])[0]

  for i := 2; i &lt; len; i++ {
    ins[0] = out
    ins[1] = sliceInType.Index(i)
    out = fn.Call(ins[:])[0]
  }
  return out.Interface()
}</pre>
<h5>健壮版的 Generic Filter</h5>
<p>同样，泛型版的 Filter 代码如下（同样分是否“就地计算”的两个版本）：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">func Filter(slice, function interface{}) interface{} {
  result, _ := filter(slice, function, false)
  return result
}

func FilterInPlace(slicePtr, function interface{}) {
  in := reflect.ValueOf(slicePtr)
  if in.Kind() != reflect.Ptr {
    panic("FilterInPlace: wrong type, " +
      "not a pointer to slice")
  }
  _, n := filter(in.Elem().Interface(), function, true)
  in.Elem().SetLen(n)
}

var boolType = reflect.ValueOf(true).Type()

func filter(slice, function interface{}, inPlace bool) (interface{}, int) {

  sliceInType := reflect.ValueOf(slice)
  if sliceInType.Kind() != reflect.Slice {
    panic("filter: wrong type, not a slice")
  }

  fn := reflect.ValueOf(function)
  elemType := sliceInType.Type().Elem()
  if !verifyFuncSignature(fn, elemType, boolType) {
    panic("filter: function must be of type func(" + elemType.String() + ") bool")
  }

  var which []int
  for i := 0; i &lt; sliceInType.Len(); i++ {
    if fn.Call([]reflect.Value{sliceInType.Index(i)})[0].Bool() {
      which = append(which, i)
    }
  }

  out := sliceInType

  if !inPlace {
    out = reflect.MakeSlice(sliceInType.Type(), len(which), len(which))
  }
  for i := range which {
    out.Index(i).Set(sliceInType.Index(which[i]))
  }

  return out.Interface(), len(which)
}</pre>
<h4>后记</h4>
<p>还有几个未尽事宜：</p>
<p>1）使用反射来做这些东西，会有一个问题，<strong>那就是代码的性能会很差。所以，上面的代码不能用于你需要高性能的地方</strong>。怎么解决这个问题，我们会在本系列文章的下一篇文章中讨论。</p>
<p>2）上面的代码大量的参考了 Rob Pike的版本，他的代码在 <a href="https://github.com/robpike/filter" target="_blank" rel="noopener">https://github.com/robpike/filter</a></p>
<p>3）其实，在全世界范围内，有大量的程序员都在问Go语言官方什么时候在标准库中支持 Map/Reduce，Rob Pike说，这种东西难写吗？还要我们官方来帮你们写么？这种代码我多少年前就写过了，但是，我从来一次都没有用过，我还是喜欢用“For循环”，我觉得你最好也跟我一起用 “For循环”。</p>
<p>我个人觉得，Map/Reduce在数据处理的时候还是很有用的，Rob Pike可能平时也不怎么写“业务逻辑”的代码，所以，对他来说可能也不太了解业务的变化有多么的频繁……</p>
<p>当然，好还是不好，由你来判断，但多学一些编程模式是对自己的帮助也是很有帮助的。</p>
<p>（全文完）<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/21146.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2020/12/go.options-150x150.png" alt="Go 编程模式：Functional Options" width="150" height="150" /></a><a href="https://coolshell.cn/articles/21146.html" class="wp_rp_title">Go 编程模式：Functional Options</a></li><li ><a href="https://coolshell.cn/articles/17929.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2017/06/go-hardhat-150x150.png" alt="Go编程模式：修饰器" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17929.html" class="wp_rp_title">Go编程模式：修饰器</a></li><li ><a href="https://coolshell.cn/articles/21615.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2021/09/go-generics-150x150.png" alt="Go编程模式 ： 泛型编程" width="150" height="150" /></a><a href="https://coolshell.cn/articles/21615.html" class="wp_rp_title">Go编程模式 ： 泛型编程</a></li><li ><a href="https://coolshell.cn/articles/21263.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2020/12/go.k8s-150x150.png" alt="Go 编程模式：k8s Visitor 模式" width="150" height="150" /></a><a href="https://coolshell.cn/articles/21263.html" class="wp_rp_title">Go 编程模式：k8s Visitor 模式</a></li><li ><a href="https://coolshell.cn/articles/21228.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2020/12/go.line_.-150x150.png" alt="Go编程模式：Pipeline" width="150" height="150" /></a><a href="https://coolshell.cn/articles/21228.html" class="wp_rp_title">Go编程模式：Pipeline</a></li><li ><a href="https://coolshell.cn/articles/21214.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2020/12/go.pair_-150x150.png" alt="Go编程模式：委托和反转控制" width="150" height="150" /></a><a href="https://coolshell.cn/articles/21214.html" class="wp_rp_title">Go编程模式：委托和反转控制</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/21164.html">Go编程模式：Map-Reduce</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/21164.html/feed</wfw:commentRss>
			<slash:comments>19</slash:comments>
		
		
			</item>
		<item>
		<title>Jeff Dean的Stanford演讲</title>
		<link>https://coolshell.cn/articles/3301.html</link>
					<comments>https://coolshell.cn/articles/3301.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Mon, 22 Nov 2010 01:07:36 +0000</pubDate>
				<category><![CDATA[杂项资源]]></category>
		<category><![CDATA[Google]]></category>
		<category><![CDATA[Jeff Dean]]></category>
		<category><![CDATA[MapReduce]]></category>
		<category><![CDATA[Performance]]></category>
		<category><![CDATA[Stanford]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=3301</guid>

					<description><![CDATA[<p>Google 公司的 Jeff Dean 在Stanford大学做了一个非常 精彩的演讲（视频未墙）。我觉得我们每一个人都应该去看一看这个视频，当然，没有字幕，...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/3301.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/3301.html">Jeff Dean的Stanford演讲</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><img decoding="async" loading="lazy" class="alignright size-full wp-image-3305" title="Jeff Dean" src="https://coolshell.cn/wp-content/uploads/2010/11/jeff.jpg" alt="" width="135" height="175" />Google 公司的 <a href="http://research.google.com/people/jeff/" target="_blank"><strong>Jeff Dean</strong></a> 在Stanford大学做了一个非常 <a href="http://stanford-online.stanford.edu/courses/ee380/101110-ee380-300.asx" target="_blank"><strong>精彩的演讲</strong></a>（视频未墙）。我觉得我们每一个人都应该去看一看这个视频，当然，没有字幕，需要不错的听力，当然，我不可能全部翻译出来，因为我也不是完全能听懂，下面是一些相关的Notes，供你参夸，并欢迎牛人指证。</p>
<ul>
<li>比较了从1999年到2010年十年来的搜索量的变化。搜索量增加了 1000 倍，而搜索速度快了5 倍。1999年，一个网页的更新最多需要一个月到两个月，而今天，只需要几秒钟，足足加快了5w倍。</li>
<li>一开始，这些大量的查询产生了大约30GB的I/O量。2004年，他们考虑过全部重写infrastructure。</li>
<li>讨论了一些关于变量长度字节对齐的东西。</li>
<li>今天的MapReduce 有400万个作业，处理将近1000PB的数据，130PB的中间数据，还有45PB的输出数据。（1PB =1024TB）关于 MapReduce （Google云计算的精髓） 的一些统计，见下图：</li>
<li><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-3302" title="Mapreduce Stats" src="https://coolshell.cn/wp-content/uploads/2010/11/mapreducestats.jpg" alt="" width="575" height="426" srcset="https://coolshell.cn/wp-content/uploads/2010/11/mapreducestats.jpg 575w, https://coolshell.cn/wp-content/uploads/2010/11/mapreducestats-300x222.jpg 300w" sizes="(max-width: 575px) 100vw, 575px" /></li>
</ul>
<p><span id="more-3301"></span></p>
<ul>
<li>现在Jeff正在做一个叫Spanner的项目，这是一个跨多个数据中心的项目。在后来的Q&amp;A中，Jeff解释了现在的数据基本上都在各个数据中心中，数据在不同数据中心间的交换几乎不可能。所以，他们需要提供一些手动的方式或是一些工作或任务来达到数据共享。这其中还需要有一些策略配置，共同的namespace，事务处理，数据一致性等等工作。</li>
</ul>
<ul>
<li>最后一个段落应该是最精彩的，Jeff讲了很多很有意思的东西，绝对让你受用一生：
<ul>
<li>一个大型的系统需要分解成N多的小services.（这和Amazon的很相似，一个页面的调用可能要经过几百个后台的services）</li>
<li>代码的性能将会是想当的重要。Jeff给了一张叫“Numbers Everyone Should Know” 的slide，如下所示，我觉得太经典了，其中的东西，如果你看过我的那篇“<a href="https://coolshell.cn/articles/3236.html" target="_blank"><strong>给老婆普及计算机知识</strong></a>”，我想我不需要多解释了。（注：1 ns = 十亿分之一秒）</li>
<li><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-3303" title="每一个程序员都应该知道的数字" src="https://coolshell.cn/wp-content/uploads/2010/11/numberseveryoneshouldknow.png" alt="" width="570" height="425" srcset="https://coolshell.cn/wp-content/uploads/2010/11/numberseveryoneshouldknow.png 570w, https://coolshell.cn/wp-content/uploads/2010/11/numberseveryoneshouldknow-300x223.png 300w" sizes="(max-width: 570px) 100vw, 570px" /></li>
<li>把相同的东西抽出来去建立一个系统，而不是把所有的事情交给所有的人。他说： “最后的那个功能可能会导致你怎么个系统超出了原有的复杂度”。</li>
<li>不要无限制地设计可扩展性。5倍到50倍的扩展性设计足够了。如果你要达到100倍的，那应该是re-arch了。</li>
<li>Jeff很喜欢有中心主结点的架构体系，他并不喜欢分布式系统。当然，中心主结点主要是用来做控制的，而不是做数据或是计算服务的。</li>
<li>J在一些小机器上运行多个小服务，而不在一个大机器上运行一个mongo作业。越小的单元就越容易处理，修复，负载均衡和扩展。（化繁为简）</li>
<li>…… ……</li>
</ul>
</li>
</ul>
<p>这是一个非常不错的演讲，很让人开阔眼界。</p>
<p>最后，我想说说英文，很多程序员都很不喜欢英文，哎……怎么说呢？如果你今天对英文还很害怕的话，这只能怪我们的教育制度的失败。但如果你以此为借口的话，那只能怪你自己了。没有英文的能力，你的技术和认知仅限于中文圈中，而中文圈中基本上都是产商的文化。有人说，“功夫网”让我们的internet成为了局域网，而我想说，让我们成为局域网的不是那个墙，而是我们自己的世界观和英文能力。<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/5224.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2011/09/image008-150x150.jpg" alt="一些文章和各种资源" width="150" height="150" /></a><a href="https://coolshell.cn/articles/5224.html" class="wp_rp_title">一些文章和各种资源</a></li><li ><a href="https://coolshell.cn/articles/22398.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2023/02/chatgpt-150x150.jpg" alt="我看ChatGPT: 为啥谷歌掉了千亿美金" width="150" height="150" /></a><a href="https://coolshell.cn/articles/22398.html" class="wp_rp_title">我看ChatGPT: 为啥谷歌掉了千亿美金</a></li><li ><a href="https://coolshell.cn/articles/22242.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2022/05/etcd-150x150.png" alt="ETCD的内存问题" width="150" height="150" /></a><a href="https://coolshell.cn/articles/22242.html" class="wp_rp_title">ETCD的内存问题</a></li><li ><a href="https://coolshell.cn/articles/21615.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2021/09/go-generics-150x150.png" alt="Go编程模式 ： 泛型编程" width="150" height="150" /></a><a href="https://coolshell.cn/articles/21615.html" class="wp_rp_title">Go编程模式 ： 泛型编程</a></li><li ><a href="https://coolshell.cn/articles/21164.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2020/12/go.map_.reduce-150x150.png" alt="Go编程模式：Map-Reduce" width="150" height="150" /></a><a href="https://coolshell.cn/articles/21164.html" class="wp_rp_title">Go编程模式：Map-Reduce</a></li><li ><a href="https://coolshell.cn/articles/17381.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2016/07/PerfTest-150x150.png" alt="性能测试应该怎么做？" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17381.html" class="wp_rp_title">性能测试应该怎么做？</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/3301.html">Jeff Dean的Stanford演讲</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/3301.html/feed</wfw:commentRss>
			<slash:comments>16</slash:comments>
		
		<enclosure url="http://stanford-online.stanford.edu/courses/ee380/101110-ee380-300.asx" length="127" type="video/asf" />

			</item>
	</channel>
</rss>
