上节我们讨论了Zipper-串形不可变集合(immutable sequential collection)游标,在串形集合中左右游走及元素维护操作。这篇我们谈谈Tree。在电子商务应用中对于xml,json等格式文件的处理要求非常之普遍,scalaz提供了Tree数据类型及相关的游览及操作函数能更方便高效的处理xml,json文件及系统目录这些树形结构数据的相关编程。scalaz Tree的定义非常简单:scalaz/Tree.scala

  1. * A multi-way tree, also known as a rose tree. Also known as Cofree[Stream, A].
  2. */
  3. sealed abstract class Tree[A] {
  4.  
  5. import Tree._
  6.  
  7. /** The label at the root of this tree. */
  8. def rootLabel: A
  9.  
  10. /** The child nodes of this tree. */
  11. def subForest: Stream[Tree[A]]
  12. ...

Tree是由一个A值rootLabel及一个流中子树Stream[Tree[A]]组成。Tree可以只由一个A类型值rootLabel组成,这时流中子树subForest就是空的Stream.empty。只有rootLabel的Tree俗称叶(leaf),有subForest的称为节(node)。scalaz为任何类型提供了leaf和node的构建注入方法:syntax/TreeOps.scala

  1. final class TreeOps[A](self: A) {
  2. def node(subForest: Tree[A]*): Tree[A] = Tree.node(self, subForest.toStream)
  3.  
  4. def leaf: Tree[A] = Tree.leaf(self)
  5. }
  6.  
  7. trait ToTreeOps {
  8. implicit def ToTreeOps[A](a: A) = new TreeOps(a)
  9. }

实际上注入方法调用了Tree里的构建函数:

  1. trait TreeFunctions {
  2. /** Construct a new Tree node. */
  3. def node[A](root: => A, forest: => Stream[Tree[A]]): Tree[A] = new Tree[A] {
  4. lazy val rootLabel = root
  5. lazy val subForest = forest
  6.  
  7. override def toString = "<tree>"
  8. }
  9.  
  10. /** Construct a tree node with no children. */
  11. def leaf[A](root: => A): Tree[A] = node(root, Stream.empty)

Tree提供了构建和模式拆分函数:

  1. object Tree extends TreeInstances with TreeFunctions {
  2. /** Construct a tree node with no children. */
  3. def apply[A](root: => A): Tree[A] = leaf(root)
  4.  
  5. object Node {
  6. def unapply[A](t: Tree[A]): Option[(A, Stream[Tree[A]])] = Some((t.rootLabel, t.subForest))
  7. }
  8. }

我们可以直接构建Tree:

  1. Tree("ALeaf") === "ALeaf".leaf //> res5: Boolean = true
  2. val tree: Tree[Int] =
  3. .node(
  4. .leaf,
  5. .node(
  6. .leaf),
  7. .node(
  8. .leaf,
  9. .leaf)
  10. ) //> tree : scalaz.Tree[Int] = <tree>
  11. tree.drawTree //> res6: String = "1
  12. //| |
  13. //| +- 11
  14. //| |
  15. //| +- 12
  16. //| | |
  17. //| | `- 121
  18. //| |
  19. //| `- 2
  20. //| |
  21. //| +- 21
  22. //| |
  23. //| `- 22
  24. //| "

Tree实现了下面众多的接口函数:

  1. sealed abstract class TreeInstances {
  2. implicit val treeInstance: Traverse1[Tree] with Monad[Tree] with Comonad[Tree] with Align[Tree] with Zip[Tree] = new Traverse1[Tree] with Monad[Tree] with Comonad[Tree] with Align[Tree] with Zip[Tree] {
  3. def point[A](a: => A): Tree[A] = Tree.leaf(a)
  4. def cobind[A, B](fa: Tree[A])(f: Tree[A] => B): Tree[B] = fa cobind f
  5. def copoint[A](p: Tree[A]): A = p.rootLabel
  6. override def map[A, B](fa: Tree[A])(f: A => B) = fa map f
  7. def bind[A, B](fa: Tree[A])(f: A => Tree[B]): Tree[B] = fa flatMap f
  8. def traverse1Impl[G[_]: Apply, A, B](fa: Tree[A])(f: A => G[B]): G[Tree[B]] = fa traverse1 f
  9. override def foldRight[A, B](fa: Tree[A], z: => B)(f: (A, => B) => B): B = fa.foldRight(z)(f)
  10. override def foldMapRight1[A, B](fa: Tree[A])(z: A => B)(f: (A, => B) => B) = (fa.flatten.reverse: @unchecked) match {
  11. case h #:: t => t.foldLeft(z(h))((b, a) => f(a, b))
  12. }
  13. override def foldLeft[A, B](fa: Tree[A], z: B)(f: (B, A) => B): B =
  14. fa.flatten.foldLeft(z)(f)
  15. override def foldMapLeft1[A, B](fa: Tree[A])(z: A => B)(f: (B, A) => B): B = fa.flatten match {
  16. case h #:: t => t.foldLeft(z(h))(f)
  17. }
  18. override def foldMap[A, B](fa: Tree[A])(f: A => B)(implicit F: Monoid[B]): B = fa foldMap f
  19. def alignWith[A, B, C](f: (\&/[A, B]) C) = {
  20. def align(ta: Tree[A], tb: Tree[B]): Tree[C] =
  21. Tree.node(f(\&/(ta.rootLabel, tb.rootLabel)), Align[Stream].alignWith[Tree[A], Tree[B], Tree[C]]({
  22. case \&/.This(sta) sta map {a f(\&/.This(a))}
  23. case \&/.That(stb) stb map {b f(\&/.That(b))}
  24. case \&/.Both(sta, stb) align(sta, stb)
  25. })(ta.subForest, tb.subForest))
  26. align _
  27. }
  28. def zip[A, B](aa: => Tree[A], bb: => Tree[B]) = {
  29. val a = aa
  30. val b = bb
  31. Tree.node(
  32. (a.rootLabel, b.rootLabel),
  33. Zip[Stream].zipWith(a.subForest, b.subForest)(zip(_, _))
  34. )
  35. }
  36. }
  37.  
  38. implicit def treeEqual[A](implicit A0: Equal[A]): Equal[Tree[A]] =
  39. new TreeEqual[A] { def A = A0 }
  40.  
  41. implicit def treeOrder[A](implicit A0: Order[A]): Order[Tree[A]] =
  42. new Order[Tree[A]] with TreeEqual[A] {
  43. def A = A0
  44. import std.stream._
  45. override def order(x: Tree[A], y: Tree[A]) =
  46. A.order(x.rootLabel, y.rootLabel) match {
  47. case Ordering.EQ =>
  48. Order[Stream[Tree[A]]].order(x.subForest, y.subForest)
  49. case x => x
  50. }
  51. }

那么Tree就是个Monad,也是Functor,Applicative,还是traversable,foldable。Tree也实现了Order,Equal实例,可以进行值的顺序比较。我们就用些例子来说明吧:

  1. // 是 Functor...
  2. (tree map { v: Int => v + }) ===
  3. .node(
  4. .leaf,
  5. .node(
  6. .leaf),
  7. .node(
  8. .leaf,
  9. .leaf)
  10. ) //> res7: Boolean = true
  11.  
  12. // ...是 Monad
  13. .point[Tree] === .leaf //> res8: Boolean = true
  14. val t2 = tree >>= (x => (x == ) ? x.leaf | x.node((-x).leaf))
  15. //> t2 : scalaz.Tree[Int] = <tree>
  16. t2 === .node((-).leaf, .leaf, .node((-).leaf, .node((-).leaf)))
  17. //> res9: Boolean = false
  18. t2.drawTree //> res10: String = "1
  19. //| |
  20. //| +- -1
  21. //| |
  22. //| +- 11
  23. //| | |
  24. //| | `- -11
  25. //| |
  26. //| +- 12
  27. //| | |
  28. //| | +- -12
  29. //| | |
  30. //| | `- 121
  31. //| | |
  32. //| | `- -121
  33. //| |
  34. //| `- 2
  35. //| |
  36. //| +- 21
  37. //| | |
  38. //| | `- -21
  39. //| |
  40. //| `- 22
  41. //| |
  42. //| `- -22
  43. //| "
  44. // ...是 Foldable
  45. tree.foldMap(_.toString) === "" //> res11: Boolean = true

说到构建Tree,偶然在网上发现了这么一个Tree构建函数:

  1. def pathTree[E](root: E, paths: Seq[Seq[E]]): Tree[E] = {
  2. root.node(paths groupBy (_.head) map {
  3. case (parent, subpaths) =>
  4. pathTree(parent, subpaths collect {
  5. case pp +: rest if rest.nonEmpty => rest
  6. })
  7. } toSeq: _*)
  8. }

据说这个pathTree函数能把List里的目录结构转化成Tree。先看看到底是不是具备如此功能:

  1. val paths = List(List("A","a1","a2"),List("B","b1"))
  2. //> paths : List[List[String]] = List(List(A, a1, a2), List(B, b1))
  3. pathTree("root",paths) drawTree //> res0: String = ""root"
  4. //| |
  5. //| +- "A"
  6. //| | |
  7. //| | `- "a1"
  8. //| | |
  9. //| | `- "a2"
  10. //| |
  11. //| `- "B"
  12. //| |
  13. //| `- "b1"
  14. //| "
  15. val paths = List(List("A","a1","a2"),List("B","b1"),List("B","b2","b3"))
  16. //> paths : List[List[String]] = List(List(A, a1, a2), List(B, b1), List(B, b2,
  17. //| b3))
  18. pathTree("root",paths) drawTree //> res0: String = ""root"
  19. //| |
  20. //| +- "A"
  21. //| | |
  22. //| | `- "a1"
  23. //| | |
  24. //| | `- "a2"
  25. //| |
  26. //| `- "B"
  27. //| |
  28. //| +- "b2"
  29. //| | |
  30. //| | `- "b3"
  31. //| |
  32. //| `- "b1"
  33. //| "

果然能行,而且还能把"B"节点合并汇集。这个函数的作者简直就是个神人,起码是个算法和FP语法运用大师。我虽然还无法达到大师的程度能写出这样的泛函程序,但好奇心是挡不住的,总想了解这个函数是怎么运作的。可以用一些测试数据来逐步跟踪一下:

  1. val paths = List(List("A")) //> paths : List[List[String]] = List(List(A))
  2. val gpPaths =paths.groupBy(_.head) //> gpPaths : scala.collection.immutable.Map[String,List[List[String]]] = Map(A-> List(List(A)))
  3. List(List("A")) collect { case pp +: rest if rest.nonEmpty => rest }
  4. //> res0: List[List[String]] = List()

通过上面的跟踪约化我们看到List(List(A))在pathTree里的执行过程。这里把复杂的groupBy和collect函数的用法和结果了解了。实际上整个过程相当于:

  1. "root".node(
  2. "A".node(List().toSeq: _*)
  3. ) drawTree //> res3: String = ""root"
  4. //| |
  5. //| `- "A"
  6. //| "

如果再增加一个点就相当于:

  1. "root".node(
  2. "A".node(List().toSeq: _*),
  3. "B".node(List().toSeq: _*)
  4. ) drawTree //> res4: String = ""root"
  5. //| |
  6. //| +- "A"
  7. //| |
  8. //| `- "B"
  9. //| "

加多一层:

  1. val paths = List(List("A","a1")) //> paths : List[List[String]] = List(List(A, a1))
  2. val gpPaths =paths.groupBy(_.head) //> gpPaths : scala.collection.immutable.Map[String,List[List[String]]] = Map(A
  3. //| -> List(List(A, a1)))
  4. List(List("A","a1")) collect { case pp +: rest if rest.nonEmpty => rest }
  5. //> res0: List[List[String]] = List(List(a1))
  6.  
  7. //化解成
  8. "root".node(
  9. "A".node(
  10. "a1".node(
  11. List().toSeq: _*)
  12. )
  13. ) drawTree //> res3: String = ""root"
  14. //| |
  15. //| `- "A"
  16. //| |
  17. //| `- "a1"
  18. //| "

合并目录:

  1. val paths = List(List("A","a1"),List("A","a2")) //> paths : List[List[String]] = List(List(A, a1), List(A, a2))
  2. val gpPaths =paths.groupBy(_.head) //> gpPaths : scala.collection.immutable.Map[String,List[List[String]]] = Map(A
  3. //| -> List(List(A, a1), List(A, a2)))
  4. List(List("A","a1"),List("A","a2")) collect { case pp +: rest if rest.nonEmpty => rest }
  5. //> res0: List[List[String]] = List(List(a1), List(a2))
  6.  
  7. //相当产生结果
  8. "root".node(
  9. "A".node(
  10. "a1".node(
  11. List().toSeq: _*)
  12. ,
  13. "a2".node(
  14. List().toSeq: _*)
  15. )
  16. ) drawTree //> res3: String = ""root"
  17. //| |
  18. //| `- "A"
  19. //| |
  20. //| +- "a1"
  21. //| |
  22. //| `- "a2"
  23. //| "

相信这些跟踪过程足够了解整个函数的工作原理了。
有了Tree构建方法后就需要Tree的游动和操作函数了。与串形集合的直线游动不同的是,树形集合游动方式是分岔的。所以Zipper不太适用于树形结构。scalaz特别提供了树形集合的定位游标TreeLoc,我们看看它的定义:scalaz/TreeLoc.scala

  1. final case class TreeLoc[A](tree: Tree[A], lefts: TreeForest[A],
  2. rights: TreeForest[A], parents: Parents[A]) {
  3. ...
  4. trait TreeLocFunctions {
  5. type TreeForest[A] =
  6. Stream[Tree[A]]
  7.  
  8. type Parent[A] =
  9. (TreeForest[A], A, TreeForest[A])
  10.  
  11. type Parents[A] =
  12. Stream[Parent[A]]

树形集合游标TreeLoc由当前节点tree、左子树lefts、右子树rights及父树parents组成。lefts,rights,parents都是在流中的树形Stream[Tree[A]]。
用Tree.loc可以直接对目标树生成TreeLoc:

  1. /** A TreeLoc zipper of this tree, focused on the root node. */
  2. def loc: TreeLoc[A] = TreeLoc.loc(this, Stream.Empty, Stream.Empty, Stream.Empty)
  3.  
  4. val tree: Tree[Int] =
  5. .node(
  6. .leaf,
  7. .node(
  8. .leaf),
  9. .node(
  10. .leaf,
  11. .leaf)
  12. ) //> tree : scalaz.Tree[Int] = <tree>
  13.  
  14. tree.loc //> res7: scalaz.TreeLoc[Int] = TreeLoc(<tree>,Stream(),Stream(),Stream())

TreeLoc的游动函数:

  1. def root: TreeLoc[A] =
  2. parent match {
  3. case Some(z) => z.root
  4. case None => this
  5. }
  6.  
  7. /** Select the left sibling of the current node. */
  8. def left: Option[TreeLoc[A]] = lefts match {
  9. case t #:: ts => Some(loc(t, ts, tree #:: rights, parents))
  10. case Stream.Empty => None
  11. }
  12.  
  13. /** Select the right sibling of the current node. */
  14. def right: Option[TreeLoc[A]] = rights match {
  15. case t #:: ts => Some(loc(t, tree #:: lefts, ts, parents))
  16. case Stream.Empty => None
  17. }
  18.  
  19. /** Select the leftmost child of the current node. */
  20. def firstChild: Option[TreeLoc[A]] = tree.subForest match {
  21. case t #:: ts => Some(loc(t, Stream.Empty, ts, downParents))
  22. case Stream.Empty => None
  23. }
  24.  
  25. /** Select the rightmost child of the current node. */
  26. def lastChild: Option[TreeLoc[A]] = tree.subForest.reverse match {
  27. case t #:: ts => Some(loc(t, ts, Stream.Empty, downParents))
  28. case Stream.Empty => None
  29. }
  30.  
  31. /** Select the nth child of the current node. */
  32. def getChild(n: Int): Option[TreeLoc[A]] =
  33. for {lr <- splitChildren(Stream.Empty, tree.subForest, n)
  34. ls = lr._1
  35. } yield loc(ls.head, ls.tail, lr._2, downParents)

我们试着用这些函数游动:

  1. val tree: Tree[Int] =
  2. .node(
  3. .leaf,
  4. .node(
  5. .leaf),
  6. .node(
  7. .leaf,
  8. .leaf)
  9. ) //> tree : scalaz.Tree[Int] = <tree>
  10. tree.loc //> res7: scalaz.TreeLoc[Int] = TreeLoc(<tree>,Stream(),Stream(),Stream())
  11. val l = for {
  12. l1 <- tree.loc.some
  13. l2 <- l1.firstChild
  14. l3 <- l1.lastChild
  15. l4 <- l3.firstChild
  16. } yield (l1,l2,l3,l4) //> l : Option[(scalaz.TreeLoc[Int], scalaz.TreeLoc[Int], scalaz.TreeLoc[Int],
  17. //| scalaz.TreeLoc[Int])] = Some((TreeLoc(<tree>,Stream(),Stream(),Stream()),T
  18. //| reeLoc(<tree>,Stream(),Stream(<tree>, <tree>),Stream((Stream(),1,Stream()),
  19. //| ?)),TreeLoc(<tree>,Stream(<tree>, <tree>),Stream(),Stream((Stream(),1,Stre
  20. //| am()), ?)),TreeLoc(<tree>,Stream(),Stream(<tree>, ?),Stream((Stream(<tree>,
  21. //| <tree>),2,Stream()), ?))))
  22.  
  23. l.get._1.getLabel //> res8: Int = 1
  24. l.get._2.getLabel //> res9: Int = 11
  25. l.get._3.getLabel //> res10: Int = 2
  26. l.get._4.getLabel //> res11: Int = 21

跳动函数:

  1. /** Select the nth child of the current node. */
  2. def getChild(n: Int): Option[TreeLoc[A]] =
  3. for {lr <- splitChildren(Stream.Empty, tree.subForest, n)
  4. ls = lr._1
  5. } yield loc(ls.head, ls.tail, lr._2, downParents)
  6.  
  7. /** Select the first immediate child of the current node that satisfies the given predicate. */
  8. def findChild(p: Tree[A] => Boolean): Option[TreeLoc[A]] = {
  9. @tailrec
  10. def split(acc: TreeForest[A], xs: TreeForest[A]): Option[(TreeForest[A], Tree[A], TreeForest[A])] =
  11. (acc, xs) match {
  12. case (acc, Stream.cons(x, xs)) => if (p(x)) Some((acc, x, xs)) else split(Stream.cons(x, acc), xs)
  13. case _ => None
  14. }
  15. for (ltr <- split(Stream.Empty, tree.subForest)) yield loc(ltr._2, ltr._1, ltr._3, downParents)
  16. }
  17.  
  18. /**Select the first descendant node of the current node that satisfies the given predicate. */
  19. def find(p: TreeLoc[A] => Boolean): Option[TreeLoc[A]] =
  20. Cobind[TreeLoc].cojoin(this).tree.flatten.find(p)

find用法示范:

  1. val tree: Tree[Int] =
  2. .node(
  3. .leaf,
  4. .node(
  5. .leaf),
  6. .node(
  7. .leaf,
  8. .leaf)
  9. ) //> tree : scalaz.Tree[Int] = <tree>
  10. tree.loc //> res7: scalaz.TreeLoc[Int] = TreeLoc(<tree>,Stream(),Stream(),Stream())
  11. val l = for {
  12. l1 <- tree.loc.some
  13. l2 <- l1.find{_.getLabel == }
  14. l3 <- l1.find{_.getLabel == }
  15. l4 <- l2.find{_.getLabel == }
  16. l5 <- l1.findChild{_.rootLabel == }
  17. l6 <- l1.findChild{_.rootLabel == }
  18. } yield l6 //> l : Option[scalaz.TreeLoc[Int]] = Some(TreeLoc(<tree>,Stream(<tree>, ?),St
  19. //| ream(),Stream((Stream(),1,Stream()), ?)))

注意:上面6个跳动都成功了。如果无法跳转结果会是None
insert,modify,delete这些操作函数:

  1. /** Replace the current node with the given one. */
  2. def setTree(t: Tree[A]): TreeLoc[A] = loc(t, lefts, rights, parents)
  3.  
  4. /** Modify the current node with the given function. */
  5. def modifyTree(f: Tree[A] => Tree[A]): TreeLoc[A] = setTree(f(tree))
  6.  
  7. /** Modify the label at the current node with the given function. */
  8. def modifyLabel(f: A => A): TreeLoc[A] = setLabel(f(getLabel))
  9.  
  10. /** Get the label of the current node. */
  11. def getLabel: A = tree.rootLabel
  12.  
  13. /** Set the label of the current node. */
  14. def setLabel(a: A): TreeLoc[A] = modifyTree((t: Tree[A]) => node(a, t.subForest))
  15.  
  16. /** Insert the given node to the left of the current node and give it focus. */
  17. def insertLeft(t: Tree[A]): TreeLoc[A] = loc(t, lefts, Stream.cons(tree, rights), parents)
  18.  
  19. /** Insert the given node to the right of the current node and give it focus. */
  20. def insertRight(t: Tree[A]): TreeLoc[A] = loc(t, Stream.cons(tree, lefts), rights, parents)
  21.  
  22. /** Insert the given node as the first child of the current node and give it focus. */
  23. def insertDownFirst(t: Tree[A]): TreeLoc[A] = loc(t, Stream.Empty, tree.subForest, downParents)
  24.  
  25. /** Insert the given node as the last child of the current node and give it focus. */
  26. def insertDownLast(t: Tree[A]): TreeLoc[A] = loc(t, tree.subForest.reverse, Stream.Empty, downParents)
  27.  
  28. /** Insert the given node as the nth child of the current node and give it focus. */
  29. def insertDownAt(n: Int, t: Tree[A]): Option[TreeLoc[A]] =
  30. for (lr <- splitChildren(Stream.Empty, tree.subForest, n)) yield loc(t, lr._1, lr._2, downParents)
  31.  
  32. /** Delete the current node and all its children. */
  33. def delete: Option[TreeLoc[A]] = rights match {
  34. case Stream.cons(t, ts) => Some(loc(t, lefts, ts, parents))
  35. case _ => lefts match {
  36. case Stream.cons(t, ts) => Some(loc(t, ts, rights, parents))
  37. case _ => for (loc1 <- parent) yield loc1.modifyTree((t: Tree[A]) => node(t.rootLabel, Stream.Empty))
  38. }
  39. }

用法示范:

  1. val tr = .leaf //> tr : scalaz.Tree[Int] = <tree>
  2. val tl = for {
  3. l1 <- tr.loc.some
  4. l3 <- l1.insertDownLast(.leaf).some
  5. l4 <- l3.insertDownLast(.leaf).some
  6. l5 <- l4.root.some
  7. l2 <- l5.insertDownFirst(.leaf).some
  8. l6 <- l2.root.some
  9. l7 <- l6.find{_.getLabel == }
  10. l8 <- l7.setLabel().some
  11. } yield l8 //> tl : Option[scalaz.TreeLoc[Int]] = Some(TreeLoc(<tree>,Stream(<tree>, ?),S
  12. //| tream(),Stream((Stream(),1,Stream()), ?)))
  13.  
  14. tl.get.toTree.drawTree //> res8: String = "1
  15. //| |
  16. //| +- 11
  17. //| |
  18. //| `- 102
  19. //| |
  20. //| `- 121
  21. //| "

setTree和delete会替换当前节点下的所有子树:

  1. val tree: Tree[Int] =
  2. .node(
  3. .leaf,
  4. .node(
  5. .leaf),
  6. .node(
  7. .leaf,
  8. .leaf)
  9. ) //> tree : scalaz.Tree[Int] = <tree>
  10. def modTree(t: Tree[Int]): Tree[Int] = {
  11. val l = for {
  12. l1 <- t.loc.some
  13. l2 <- l1.find{_.getLabel == }
  14. l3 <- l2.setTree { .node (.leaf) }.some
  15. } yield l3
  16. l.get.toTree
  17. } //> modTree: (t: scalaz.Tree[Int])scalaz.Tree[Int]
  18. val l = for {
  19. l1 <- tree.loc.some
  20. l2 <- l1.find{_.getLabel == }
  21. l3 <- l2.modifyTree{modTree(_)}.some
  22. l4 <- l3.root.some
  23. l5 <- l4.find{_.getLabel == }
  24. l6 <- l5.delete
  25. } yield l6 //> l : Option[scalaz.TreeLoc[Int]] = Some(TreeLoc(<tree>,Stream(<tree>, ?),St
  26. //| ream(),Stream((Stream(),1,Stream()), ?)))
  27. l.get.toTree.drawTree //> res7: String = "1
  28. //| |
  29. //| +- 11
  30. //| |
  31. //| `- 2
  32. //| |
  33. //| +- 21
  34. //| |
  35. //| `- 3
  36. //| |
  37. //| `- 31
  38. //| "

通过scalaz的Tree和TreeLoc数据结构,以及一整套树形结构游览、操作函数,我们可以方便有效地实现FP风格的不可变树形集合编程。

Scalaz(24)- 泛函数据结构: Tree-数据游览及维护的更多相关文章

  1. Scalaz(23)- 泛函数据结构: Zipper-游标定位

    外面沙尘滚滚一直向北去了,意识到年关到了,码农们都回乡过年去了,而我却留在这里玩弄“拉链”.不要想歪了,我说的不是裤裆拉链而是scalaz Zipper,一种泛函数据结构游标(cursor).在函数式 ...

  2. Oracle导出数据结构和数据表的方法

    1.PLSQL导出数据结构(数据表.序列.触发器.函数.视图) 1)在左侧 点击tables 2)Tools-->Export User Objects 3)红色1 是你要选择导出的表,红色2 ...

  3. Redis各种数据结构性能数据对比和性能优化实践

    很对不起大家,又是一篇乱序的文章,但是满满的干货,来源于实践,相信大家会有所收获.里面穿插一些感悟和生活故事,可以忽略不看.不过听大家普遍的反馈说这是其中最喜欢看的部分,好吧,就当学习之后轻松一下. ...

  4. SQLServer树形数据结构的数据进行数据统计

    前言 前几天朋友问我,关于SQLServer数据库中对树形结构的表数据统计问题,需求大致如下: 分类表(递归数据),A的子分类是B,B的子分类是C--分类关系不间断,A为第一层,B为第二层,C为第三层 ...

  5. 65*24=1560<2175 对数据的统计支撑决策假设 历史数据正确的情况下,去安排今后的任务

    没有达到目标,原因不是时间投入不够,而是不用数据决策,不用数据调度定时脚本 [数据源情况统计]----># 近30天,日生效coin数目SELECT COUNT(DISTINCT coin) A ...

  6. 24位PCM采样数据转成16位算法,已实现PCM转WAV在线工具源码支持24bits、16bits、8bits

    目录 算法来源 js版24位PCM转8位.16位代码 js版8位.16位PCM转成24位 附:浏览器控制台下载数据文件代码 相关实现 最近收到几个24位的PCM录音源文件,Recoder库原有的PCM ...

  7. Mysql 导入导出数据结构及数据

    方式一: mysqldump -ukevin -P3306 --default-character-set=utf8 -p -h10.1.15.123 activity sign_in_user &g ...

  8. oracle数据库,恢复到24小时内的数据

    因为更新的时候忘记添加条件,导致数据混乱. 用此方法,把数据恢复. --首先查询在某个时间点的数据 t_asn_dtl 为表名; SELECT * FROM t_asn_dtl AS OF TIMES ...

  9. 24.API爬天气预报数据

    1.免费注册API 地址: https://console.heweather.com/ 必须要用IE浏览器打开,注册邮箱激活,打开控制台,如图: 认证key是访问api的钥匙 2.阅读api说明开发 ...

随机推荐

  1. 使用finfo_file()函数检测上传图片的类型

    该函数可以检测文件的MIME类型.因为有时候我们只根据文件后缀来判断是不准确的. function getMIME($filename){ $finfo = finfo_open(FILEINFO_M ...

  2. Atitit 游戏引擎---物理系统(1)------爆炸效果

    Atitit 游戏引擎---物理系统(1)------爆炸效果 1.1. 动画框架的来源flex,jqueryuijs,anim , cocos2d 1 1.2. Jqueryui的特效库 1 1.3 ...

  3. Linux下MakeFile初探

    make是linux下的编译命令,用于编译和生成Linux下的可执行文件.这个命令处理的对象是Makefile,makefile等.由于make的强大解析能力,makefile文件的编写也变得极为简单 ...

  4. fabric upgrade from old crashlystic stuck in build

    提示build,一直没有反应. 问题:工程中原有的shell命令屏蔽掉了

  5. javascript运算符——算术运算符

    × 目录 [1]一元加 [2]一元减 [3]递增[4]递减[5]加法[6]减法[7]乘法[8]除法[9]求余 前面的话 javascript中的算术操作主要通过算术运算符来实现,本文将介绍算术运算符的 ...

  6. 如何安装Oracle Instant Client

    Oracle Instant Client是Oracle发布的轻量级数据库客户端,下面我们来看看官方的定义: Instant Client allows you to run your applica ...

  7. 构建自己的PHP框架--抽象Controller的基类

    上一篇博客中,我们将简单的路由解析和执行,从入口文件public/index.php中移入到框架中.入口文件顿时变得清爽无比-- 但是,去我们的controller里看一下,会看到如下的code: p ...

  8. 深入浅出JSONP--解决ajax跨域问题

    取不到数据! 上周客户新买了服务器,原本在旧的服务器上放着客户的Web主页信息和一个后台程序(asp.net),在客户的主页中有一个动态显示最新消息的处理,这个处理就是通过ajax异步从那个后台程序中 ...

  9. 【Android】Android ObjectAnimator动画初识、模仿

    ObjectAnimator: ObjectAnimator的概念这里就不解释了,直接从代码中说明,以下是模仿Persicope的加载动画,简单的几行代码即可实现,当然我也是模仿的,更好的实现思路还请 ...

  10. js cookie存储方法

    /*! * jQuery Cookie Plugin v1.4.0 * https://github.com/carhartl/jquery-cookie * * Copyright 2013 Kla ...