本条话题老生长谈了,在面试中必然考核的能力中,笔者个人觉得化解难题能力是排第二人的,比上学能力优先级更高。解决难题的力量既能看出程序员的思维能力,应变能力,探索能力等,又足以见到她的经验。若是化解难题能力不好是无力回天透过面试的。

   
在上一节我们谈论了Monoid的结合性和恒等值的意义以及Monoid怎么着与串类成分折叠算法相匹配。然则大家只示范了一下基础项目(primitive
type)Monoid实例的使用,所以上一节的座谈目标是理论多于实践。在这一节大家将把主要放在一些实用综合项目(composite
type)Monoid实例及Monoid的架空表明及函数组合能力。

这里举个例子,假诺我执行了二个PHP的本子,如php
test.php,预期是能够再次来到叁个字符串。但实施后不曾其余消息输出,那时候通过哪些方法能明白程序错在何地?这里可以将一挥而就难题能力分为七个阶段,越到末端的意味能力越强。

   
Monoid的二元操作函数有所整合天性(associativity),与恒等值(identity)共同使用可以随心所欲接纳左折叠或右折叠算法处理串类成分(List
element)而博得平等结果。所以利用Monoid
op大家能够得出左折叠等于右折叠的结论:

Lv0 查看PHP错误音讯

先后没有直达预期效应,证东魏码出错了,看PHP的错误音讯是首先步。如若一向忽略错误新闻,申明此人不合乎充当专业的程序员岗位。有些景况下php.ini配置中关闭了错误呈现,必要修改php.ini打开错误消息,只怕错误新闻被导出到了日记文件,那种意况能够直接tailf
php_error.log来看错误新闻。

获得错误消息后一直定位到程序代码难题,恐怕到谷歌(Google)/百度搜索,即可化解难点。

注:打开错误呈现的形式是

  • php.ini中display_errors / display_startup_errors 设置为On
  • php.ini中error_reporting 设置为E_ALL
  • PHP代码中安装error_reporting(E_ALL)

左折叠:op(op(op(a,b),c),d)

Lv1 存在多个本子的php或php-cli与php-fpm加载分裂的配备

留存多个本子的php,明白通过which
php来看是哪些PHP,恐怕加相对路线制定php版本。表示此PHPer通过了此层级的5/10考验。

除此以外一个处境正是php-cli与php-fpm得到的推市价况不雷同,如在web浏览器中履行是对的,cli下执行是错的。这时候可能是3个条件加载的php.ini分歧所致。cli下通过php
-i |grep
php.ini获得加载了哪个php.ini。而fpm下通过phpinfo()函数能够获取php.ini的相对路径。

 

右折叠:op(a,op(b,op(c,d)))

Lv2 var_dump/die打印变量值音信单步调节和测试

那是惯用的程序调试手段,也是最简便阴毒有效的消除难点方法。高级一点的一手是使用PHP的Trace类/日志类,花哨一点的能够凭借phpstorm+xdebug在IDE工具里进行Debug。

Trace工具还足以分析脚本的耗费时间,实行PHP程序的质量优化。

那三个考验全部通过,评释此程序员已经怀有了专业PHP程序员应该有的消除难题能力了。PHP程序员只要过了那一个阶段,就足以应多大学一年级些情形,在中型小型型网站中毫无压力。

 

可是,假设能够用并行算法的话正是:

Lv3 用到strace工具跟踪程序执行

strace可以用来查阅系统调用的施行,使用strace php test.php,只怕strace -p
进度ID。strace就可以扶持你通过现象看本质,驾驭程序执行的进程。这么些手法是在巨型网站,大集团里最常用的。固然没理解strace,那里不得不说对不起了,我们不接受不会strace的PHPer。

strace其实也是对程序员基础的考验,假若不懂操作操作系统,完全不懂底层,肯定也达不到会用strace的水准。当然strace对于PHP代码里的死循环是消除不了的。比如您发现2个php-fpm进度CPU百分百了,strace或者是消除不了的。因为strace是看系统调用,一般都以IO类操作,既然是IO密集,那CPU一定不容许是百分百。

 

并行算法:op(op(a,b),op(c,d))
大家得以同时运算 op(a,b), op(c,d)

Lv4 采取tcpdump工具分析互连网通讯进程

tcpdump能够抓到网卡的数据通讯进程,甚至数据内容也能够抓到。使用tcpdump能够观望互连网通讯进度是怎么的,如曾几何时发起了TCP
SYN三回握手,哪天发送FIN包,哪一天发送奔驰G级ST包。那是2个基础,假如不懂tcpdump,表明不具有网络难点一蹴即至能力。

 

假如大家得以应用并行算法的话,肯定能升级总括功用。试想假使我们对一个重特大文件举行理文件字数总计恐怕搜索最大值什么的,大家能够把那个大文件分为若干小文件然后还要计算后再协商将节约成千成万乘除时间。在当代大数额时期,数据文件不但大还若是遍布在不少服务器上的。那么Monoid的卓殊定律就能够使数码处理相互运算,尤其同盟大数目处理方式。

Lv5 总计函数调用的耗费时间和成功率

应用xhporf/xdebug导出PHP请求的调用进度,然后分析种种函数调用的经过和耗费时间。能够分析PHP程序的属性瓶颈,找出能够优化的点。

除此以外三个对此网络服务的调用,如mysql查询,curl,别的API调用等,通过记录伊始和终止时microtime,重返的是或不是false,能够赢得调用是还是不是成功,耗费时间多少。借使得以集中数据,整理出调用的成功率,退步率,平均延时,注脚此程序员对接口质量敏感,有重型网站项目经验。

 

咱俩看看能还是不能够落成像折叠算法似的并行算法:

Lv6 gdb使用

gdb是C/C++调节和测试程序的利器,须要具有一定C/C++功底的程序员才会能运用自如使用gdb。上边说的strace不可能跟踪php程序CPU百分百,而gdb是足以跟踪的。其余gdb也足以缓解php程序core
dump的题材。

透过gdb -p 进度ID,再协作php-src的.gdbinit
zbacktrace等工具,能够很有益地跟踪PHP程序的实践。像上面的CPU百分之百屡屡是PHP程序中爆发死循环了,gdb举行数次查看,就大致能够收获死循环的岗位。具备gdb化解难点能力的PHP程序员少之又少。假设能接纳gdb解决PHP难点,这么些PHPer百分之百能够经过面试,并且能够得到较高的技能评级。

 

1   def foldMapV[A,B](iseq: IndexedSeq[A])(m: Monoid[B])(f: A => B): B = {
2       if (iseq.isEmpty) m.zero
3       else if (iseq.length == 1) f(iseq(0))
4       else {
5           val (l, r) = iseq.splitAt(iseq.length / 2)
6           m.op(foldMapV(l)(m)(f), foldMapV(r)(m)(f))
7       }
8   }                                               //> foldMapV: [A, B](iseq: IndexedSeq[A])(m: ch10.ex2.Monoid[B])(f: A => B)B

Lv7 查看PHP内核和扩大源码

如若能熟谙PHP内核和增加的源码,境遇PHP程序中最复杂的内部存款和储蓄器错误,也足以有缓解的能力。那类PHP程序员就是凤毛麟角了。协作gdb工具和对PHP源码的熟谙,能够查看opcode的音讯,execute_data的内部存款和储蓄器,全局变量的图景等。

转载大神博文,地址:http://rango.swoole.com/archives/340 

嗬,那几个foldMapV从表面,在品种款式上跟foldMap相同,不一样的是内部具体落到实处方式;foldMapV循环把对象串实行分半。

构成前边对并行运算的探讨,大家用以下方法应该可以兑现互相之间运算吧:

1   def foldMapV[A,B](iseq: IndexedSeq[A])(m: Monoid[B])(f: A => B): B = {
2       if (iseq.isEmpty) m.zero
3       else if (iseq.length == 1) f(iseq(0))
4       else {
5           val (l, r) = iseq.splitAt(iseq.length / 2)
6           m.op(async(foldMapV(l)(m)(f)), async(foldMapV(r)(m)(f)))
7       }
8   }                                               //> foldMapV: [A, B](iseq: IndexedSeq[A])(m: ch10.ex2.Monoid[B])(f: A => B)B

好,我们下边找个例子来演示高阶类型Monoid实例和互相运算应用:用Monoid来落到实处对字串(List[String])的文字数计算。由于大家打算动用并行总计,对字串进行分半时会容许会出现一字分成两半的意况,所以供给自定义复杂一点的数据类型:

 1   trait WC 
 2   case class Stub(chars: String) extends WC  //chars 记录了未完整文字的字符
 3   case class Part(lStub: String, words: Int, rStub: String) extends WC   //lStub=左边文字结尾, words=完整字数,rStub=右边文字开头
 4   
 5   def wcMonoid: Monoid[WC] = new Monoid[WC] {
 6       def op(wc1: WC, wc2: WC): WC = {
 7           (wc1, wc2) match {
 8               case (Stub(l),Stub(r)) => Stub(l + r)
 9               case (Stub(lb),Part(le,w,rb)) => Part(lb+le,w,rb)
10               case (Part(le,w,rb),Stub(re)) => Part(le,w,rb+re)
11               case (Part(le,w,rb),Part(lb,w2,re)) => Part(le, w + (if((rb+lb).isEmpty) 0 else 1) + w2, re)
12           }
13       }
14       val zero = Stub("")
15   }

 Monoid[WC]统计,是个WC类型的Monoid实例,op(wc1,wc2)=wc3则把两个WC值拼凑起来变成另多个WC值。下边让我们用wcMonoid来兑现这几个文字总计函数:

 1   def wordCount(ws: String): Int = {
 2     def wc(c: Char): WC = {
 3         if (c.isWhitespace) Part("",0,"")
 4         else Stub(c.toString)
 5     }
 6     def unstub(s: String) = s.length min 1
 7     
 8     foldMapV(ws.toIndexedSeq)(wcMonoid)(wc) match {
 9         case Stub(c) => 0
10         case Part(l,w,r) => unstub(l) + w + unstub(r)
11     }
12   }                                               //> wordCount: (ws: String)Int

 

用它来数个字数:

1   val words = "the brown fox     is running quickly."  //故意留点空格,标点符号什么的
2                                                   //> words  : String = the brown fox     is running quickly.
3   wordCount(words)                                //> res0: Int = 6 

没错!

再来1个例子:检查一串数字是还是不是是有序的:

 1   def seqIsOrdered(iseq: IndexedSeq[Int]): Boolean = {
 2       val stateMonoid = new Monoid[Option[(Int, Int, Boolean)]] {    //状态:Option[(最小值,最大值,是排序)]
 3          def op(s1: Option[(Int,Int,Boolean)], s2: Option[(Int,Int,Boolean)]): Option[(Int,Int,Boolean)] = {
 4            (s1,s2) match {
 5               case (None, b) => b
 6               case (b, None) => b
 7               case (Some((x1,y1,b1)),Some((x2,y2,b2))) => Some(x1 min x2,y1 max y2, b1 && b2 && x2 >= y1)
 8            }
 9          }
10          val zero = None
11       }
12       foldMapV(iseq)(stateMonoid)(i => Some(i,i,true)) map (_._3) getOrElse true
13   }

1   seqIsOrdered(List(1,2,5,9,33).toIndexedSeq)     //> res0: Boolean = true
2   seqIsOrdered(List(1,2,5,0    ,33).toIndexedSeq)    //> res1: Boolean = false

在这一个例子里我们用Option[min,max,ordered]作为当前气象并用stateMonoid来处理那些场所。foldMapV参数(i
=> Some(i,i,true))正是正规的 A =>
B。还记得呢,大家扩充foldMap这一个函数是的目标是一旦成分A没有Monoid实例,那么我们得以用Monoid[B]下一场用A
=>B函数把A转成B才能使用Monoid[B]。那里大家把
i转成Some(Int,Int,Boolean)。

值得注意的是上述三个例证foldMapV历遍无论怎么着是不会中途退出的。那几个特点把foldMapV的运用局限在必得消耗整个数据源的计量应用,如求和、最大值等等。对于其余一些渴求,如:A
=>
Boolean那种必要,尽管第三个值就早已获得答案也务必走完整串数据。

咱俩在前边的章节里已经探究了一部分数据结构如List,Stream,Tree等。当我们需求处理那一个组织中封装的因素时一般使用部分算法如折叠算法。那种算法能保留数据结构。而且它们有共通性:都足以运用折叠算法。既然有共性,肯定就会有深度抽象的长空,我们得以把它们抽象表完成2个Foldable[F[_]]:List,Stream,Tree等数据结构类型正是F[_];3个数据结构中封装了部分要素。这几个Foldable类型能够涵盖种种历遍算法:

 1  def endoComposeMonoid[A] = new Monoid[A => A] {
 2       def op(f: A => A, g: A => A): A => A = f compose g
 3       val zero = (a: A) => a
 4   }
 5   def dual[A](m: Monoid[A]): Monoid[A] = new Monoid[A] {
 6       def op(a1: A, a2: A) = m.op(a2,a1)
 7       val zero = m.zero
 8   }
 9   trait Foldable[F[_]] {
10      def foldRight[A, B](as: F[A])(z: B)(f: (A, B) => B): B = {
11            foldMap(as)(endoComposeMonoid[B])(a => b => f(a,b))(z)
12      }
13      def foldLeft[A, B](as: F[A])(z: B)(f: (B, A) => B): B = {
14            foldMap(as)(dual(endoComposeMonoid[B]))(a => b => f(b,a))(z)
15      }
16      def foldMap[A, B](as: F[A])(mb: Monoid[B])(f: A => B): B = {
17            foldLeft(as)(mb.zero)((b,a) => mb.op(f(a),b))
18      }
19      def concatenate[A](as: F[A])(m: Monoid[A]): A = {
20         foldLeft(as)(m.zero)(m.op)
21      }
22   }

大家今后一度赢得了个Foldable抽象数据结构,它包括了三种折叠历遍算法。我们能够试着成立一些Foldable实例看看:

 1   object listFoldable extends Foldable[List] {
 2      override def foldRight[A, B](as: List[A])(z: B)(f: (A, B) => B): B = {
 3            as.foldRight(z)(f)
 4      }
 5      override def foldLeft[A, B](as: List[A])(z: B)(f: (B, A) => B): B = {
 6            as.foldLeft(z)(f)
 7      }
 8      override def foldMap[A, B](as: List[A])(mb: Monoid[B])(f: A => B): B = {
 9            as.foldLeft(mb.zero)((b,a) => mb.op(f(a),b))
10      }
11      override def concatenate[A](as: List[A])(m: Monoid[A]): A = {
12         as.foldLeft(m.zero)(m.op)
13      }
14   }
15   object indexedSeqFoldable extends Foldable[IndexedSeq] {
16      override def foldRight[A, B](as: IndexedSeq[A])(z: B)(f: (A, B) => B): B = {
17            as.foldRight(z)(f)
18      }
19      override def foldLeft[A, B](as: IndexedSeq[A])(z: B)(f: (B, A) => B): B = {
20            as.foldLeft(z)(f)
21      }
22      override def foldMap[A, B](as: IndexedSeq[A])(mb: Monoid[B])(f: A => B): B = {
23            as.foldLeft(mb.zero)((b,a) => mb.op(f(a),b))
24      }
25      override def concatenate[A](as: IndexedSeq[A])(m: Monoid[A]): A = {
26         as.foldLeft(m.zero)(m.op)
27      }
28   }
29   object StreamFoldable extends Foldable[Stream] {
30      override def foldRight[A, B](as: Stream[A])(z: B)(f: (A, B) => B): B = {
31            as.foldRight(z)(f)
32      }
33      override def foldLeft[A, B](as: Stream[A])(z: B)(f: (B, A) => B): B = {
34            as.foldLeft(z)(f)
35      }
36   }

再看看Tree foldable
实例:

 1  trait Tree[+A] 
 2   case class Leaf[A](value: A) extends Tree[A]
 3   case class Branch[A](left: Tree[A], right: Tree[A]) extends Tree[A]
 4   object TreeFoldable extends Foldable[Tree] {
 5       override def foldMap[A, B](as: Tree[A])(mb: Monoid[B])(f: A => B): B = {
 6           as match {
 7               case Leaf(a) => f(a)
 8               case Branch(l,r) => mb.op(foldMap(l)(mb)(f),foldMap(r)(mb)(f))
 9           }
10       }
11       override def foldRight[A, B](as: Tree[A])(z: B)(f: (A, B) => B): B = {
12           as match {
13               case Leaf(a) => f(a,z)     //恒等值定律 
14               case Branch(l,r) => foldRight(l)(foldRight(r)(z)(f))(f)
15           }
16       }
17       override def foldLeft[A, B](as: Tree[A])(z: B)(f: (B,A) => B): B = {
18           as match {
19               case Leaf(a) => f(z,b)
20               case Branch(l,r) => foldLeft(r)(foldLeft(l)(z)(f))(f)
21           }
22       }
23   }

能够看出TreeFoldable的落到实处情势与List,Stream等串类数据类型有所分裂。那是因为Tree类型没有现成的折叠算法。再不怕Tree类型没有空值(只有Leaf,
Branch)。这几个特点暗示着有个别项指标Monoid是从未恒等值的。我们统称那些项目为semigroup。

 Option的foldable与TreeFoldable很像:

 1   object OptionFoldable extends Foldable[Option] {
 2        override def foldMap[A, B](opt: Option[A])(mb: Monoid[B])(f: A => B): B = {
 3            opt match {
 4                case None => mb.zero
 5                case Some(a) => f(a)
 6            }
 7        }
 8     override def foldRight[A, B](opt: Option[A])(z: B)(f: (A, B) => B): B = {
 9      opt match {
10          case None => z
11          case Some(a) => f(a,z)
12      }
13     }
14     override def foldLeft[A, B](opt: Option[A])(z: B)(f: (B,A) => B): B = {
15         opt match {
16             case None => z
17             case Some(a) => f(z,a)
18         }
19     }
20   }

 

事实上任何可折叠的数据类型都足以被转换来List类型,因为我们能够用折叠算法重组List:

 1  trait Foldable[F[_]] {
 2      def foldRight[A, B](as: F[A])(z: B)(f: (A, B) => B): B = {
 3            foldMap(as)(endoComposeMonoid[B])(a => b => f(a,b))(z)
 4      }
 5      def foldLeft[A, B](as: F[A])(z: B)(f: (B, A) => B): B = {
 6            foldMap(as)(dual(endoComposeMonoid[B]))(a => b => f(b,a))(z)
 7      }
 8      def foldMap[A, B](as: F[A])(mb: Monoid[B])(f: A => B): B = {
 9            foldLeft(as)(mb.zero)((b,a) => mb.op(f(a),b))
10      }
11      def concatenate[A](as: F[A])(m: Monoid[A]): A = {
12         foldLeft(as)(m.zero)(m.op)
13      }
14      def toList[A](as: F[A]): List[A] = {
15          foldRight(as)(Nil: List[A]){_ :: _}
16      }
17   }

Monoid的函数组合能力也挺有趣:假设大家有Monoid[A],
Monoid[B],那大家就能够把它们组合成Monoid[(A,B)]:

1   def productMonoid[A,B](ma: Monoid[A], mb: Monoid[B]): Monoid[(A,B)] = new Monoid[(A,B)] {
2       def op(x: (A,B), y: (A,B)) = (ma.op(x._1, y._1), mb.op(x._2,y._2))
3       val zero = (ma.zero, mb.zero)
4   }                                               //> productMonoid: [A, B](ma: ch10.ex2.Monoid[A], mb: ch10.ex2.Monoid[B])ch10.e
5                                                   //| x2.Monoid[(A, B)]

 大家得以用那些组成的Monoid在历遍一个List时同时总计List长度及和:

1  val pm = productMonoid(intAdditionMonoid,intAdditionMonoid)
2                                                   //> pm  : ch10.ex2.Monoid[(Int, Int)] = ch10.ex2$$anonfun$main$1$$anon$6@614c55
3                                                   //| 15
4   listFoldable.foldMap(List(1,2,3,4))(pm)(a => (1, a))
5                                                   //> res0: (Int, Int) = (4,10)

在历遍进程中大家把List每一个节点元素值转成一对值
a => (1, a),然后分别对各种成员执行intAdditionMonoid的op操作。

上边剩下的日子大家再谈谈一些较复杂的Monoid:

设若2个函数的结果是Monoid,我们得以兑现那些函数的Monoid实例:

1   def functionMonoid[A,B](mb: Monoid[B]): Monoid[A => B] = new Monoid[A => B] {
2       def op(f: A => B, g: A => B): A => B = a => mb.op(f(a),g(a))
3       val zero: A => B = a => mb.zero 
4   }

兑现这些Monoid实例的应有尽可能从类型匹配动手:函数A
=> B的结果是B;咱们有Monoid[B],Monoid[B].op(b,b)=>b。

再来三个合并key-value
Map的Monoid实例:要是我们有value类型的Monoid实例就能够实现:

1   def mapMergeMonoid[K,V](mv: Monoid[V]): Monoid[Map[K,V]] = new Monoid[Map[K,V]] {
2        val zero = Map()
3       def op(ma: Map[K,V], mb: Map[K,V]): Map[K,V] = {
4           ma.map {
5               case (k,v) => (k, mv.op(v, mb get(k) getOrElse mv.zero ))
6           }
7       }
8   }

有了那么些Monoid实例,大家就足以处理Map的内置表明式了:

 1   val M: Monoid[Map[String, Map[String, Int]]] = mapMergeMonoid(mapMergeMonoid(intAdditionMonoid))
 2   M: Monoid[Map[String, Map[String, Int]]] = $anon$1@21dfac82
 3   
 4  val m1 = Map("o1" -> Map("i1" -> 1, "i2" -> 2))
 5  m1: Map[String,Map[String,Int]] = Map(o1 -> Map(i1 -> 1, i2 -> 2))
 6  
 7  val m2 = Map("o1" -> Map("i2" -> 3))
 8  m2: Map[String,Map[String,Int]] = Map(o1 -> Map(i2 -> 3))
 9  
10  val m3 = M.op(m1, m2)
11  m3: Map[String,Map[String,Int]] = Map(o1 -> Map(i1 -> 1, i2 -> 5))

最后,我们试着用mapMergeMonoid实例来落成frequencyMap:总括输入List里的文字发现次数。假如用二个例证来证实的话,看看下边那个一串文字转成key-value
Map:

Vector(“a rose”, “is
a”, “rose is”, “a rose”)
>>> Map(a -> 3, rose -> 3, is -> 2)

那不正是寻觅引擎中的索引比重算法吗?大家用foldMapV和mapMergeMonoid能够并行运算整理索引,那究竟Monoid的其实使用之一。

笔者们看看实际完毕:

1 def frequencyMap[A](as: IndexedSeq[A]): Map[A, Int] =
2   foldMapV(as, mapMergeMonoid[A, Int](intAddition))((a: A) => Map(a -> 1))

1 frequencyMap(Vector("a rose", "is a", "rose is", "a rose"))
2 res0: Map[String,Int] = Map(a -> 3, rose -> 3, is -> 2)