/* Licensed under Apache-2.0 (C) All Contributors */
package io.github.dreamylost.examples.fb

import scala.annotation.tailrec

/**
 * 第三章
 *
 * @author 梦境迷离
 * @version 1.0, 2019-04-19
 */
object datastructures extends App {

  import List._
  //打印x
  Console println List.x
  //打印删除第一个元素
  Console println List.tail(List(1, 2, 3, 4, 5, 6))
  //修改头元素
  Console println List.setHead(List(1, 2, 3, 4, 5, 6), 2)
  //删除前2个元素
  Console println List.drop(List(1, 2, 3, 4, 5, 6), 2)
  //删除列表中大于1的元素（或满足前缀判断的）
  Console println List.dropWhile[Int](List(1, 2, 3, 4, 5, 6), a => a > 5) //未知原因，＞无法生效
  Console println List
    .dropWhile[String](List("hello", "hello111", "hello444", "world"), a => a.startsWith("hello"))
  //柯里化的，第一个参数传入时返回一个函数，并使用第二个参数作为本函数的入参
  Console println List.dropWhile2(List("hello", "hello111", "hello444", "world2"))(a =>
    a.startsWith("hello")
  )
  //将a2复制到a1后面
  Console println List.append(List(1, 4), List(2, 3))
  //删除最后一个元素
  Console println List.init(List(1, 2, 3, 4, 5, 6))
  //对foldRight传入Nil和Cons
  Console println foldRight(Cons(1, Cons(2, Cons(3, Nil))), Nil: List[Int])(Cons(_, _))
  Console println Cons(1, foldRight(Cons(2, Cons(3, Nil)), Nil: List[Int])(Cons(_, _)))
  Console println Cons(1, Cons(2, foldRight(Cons(3, Nil), Nil: List[Int])(Cons(_, _))))
  //计算List的长度
  Console println List.length(List(1, 2, 3, 4, 5, 6))
  //左折叠实现
  Console println List.sum3(List(1, 2, 3, 4, 5, 6))
  Console println List.product3(List(1, 2, 3, 4, 5, 6))
  Console println List.length3(List(1, 2, 3, 4, 5, 6))
  //反转列表
  Console println List.reverse(List(1, 2, 3, 4, 5, 6))
  //使用右折叠实现append
  Console println List.appendViaFoldRight(List(1, 4), List(2, 3))
  //拼接列表
  Console println List.concat(List(List(1, 2), List(3, 4), List(2, 3)))
  //每个元素加1
  Console println List.add1(List(1, 2))
  //使用过滤，删除奇数
  Console println List.filter(List(1, 2, 3, 4, 5, 6))(_ % 2 == 1)
  //使用map（每个元素都变成List），与concat拼接
  Console println List.flatMap(List(1, 2, 3, 4, 5, 6))(i => List(i, i))
  //过滤
  Console println List.filterByFlatMap(List(1, 2, 3, 4, 5, 6))(_ % 2 == 1)
  //列表对应位相加
  Console println List.addPairwise(List(1, 2, 3, 4, 5, 6), List(1, 2, 3, 4, 5, 6))
  //判断子集合
  Console println List.hasSubsequence(List(1, 2, 3, 4, 5, 6), List(1, 2))

  //sealed表面本接口的实现类必须在当前文件中，且限定A是协变的，即List[A]是List[B]的子类，当且仅当A是B的子类
  sealed trait List[+A]

  //空的List是任何list的子类
  case object Nil extends List[Nothing]

  //非空的List必然是head+子集合
  case class Cons[+A](head: A, tail: List[A]) extends List[A]

  //列表操作
  object List {

    /**
     * 书上原定义方法
     *
     * @param ints
     * @return
     */
    def sum(ints: List[Int]): Int =
      ints match {
        case Nil => 0
        case Cons(x, xs) => x + sum(xs)
      }

    /**
     * 书上原定义方法
     *
     * @param ds
     * @return
     */
    def product(ds: List[Double]): Double =
      ds match {
        case Nil => 1.0
        case Cons(0.0, _) => 0.0
        case Cons(x, xs) => x * product(xs)
      }

    /**
     * 书上原定义方法
     *
     * @param as
     * @tparam A
     * @return
     */
    def apply[A](as: A*): List[A] = {
      if (as.isEmpty) Nil
      else Cons(as.head, apply(as.tail: _*)) //注意这里是用: _*，表示将集合作为可变参数传递，而不是集合整体
    }

    /**
     * 3.1：List 数据构造器的模式匹配
     *
     * 引申：注意是右结合，调用顺序是反的。目前使用的Scala2.12
     * ::  {{{1 :: List(2, 3) = List(2, 3).::(1) = List(1, 2, 3)}}}
     *
     * ::: {{{List(1, 2) ::: List(3, 4) = List(3, 4).:::(List(1, 2)) = List(1, 2, 3, 4)}}}
     */
    val x = List(1, 2, 3, 4, 5) match {
      case Cons(x, Cons(2, Cons(4, _))) => x
      case Nil => 42
      case Cons(x, Cons(y, Cons(3, Cons(4, _)))) => x + y
      case Cons(h, t) => h + sum(t)
      case _ => 101
    }

    /**
     * 3.2：实现tail函数，删除一个List的第一个元素.时间开销是常量级，如果是Nil，在实现的时候有什么不同的选择？
     *
     * @param list
     * @tparam A
     * @return
     */
    def tail[A](list: List[A]): List[A] = {
      list match {
        case Nil => sys.error("空列表的尾无法获取")
        case Cons(_, t) => t
      }
    }

    /**
     * 3.3：使用与3.1相同思路实现用一个不同的值替代列表中第一个元素
     *
     * @param list
     * @param a
     * @tparam A
     * @return
     */
    def setHead[A](list: List[A], a: A): List[A] = {
      list match {
        case Nil => sys.error("空列表的尾无法操作")
        case Cons(_, t) => Cons(a, t)
      }
    }

    /**
     * 3.4：把tail泛化为drop函数，用于从列表中删除前n个元素。时间开销与drop的元素个数成正比（不能复制列表）
     *
     * @param list
     * @param n
     * @tparam A
     * @return
     */
    def drop[A](list: List[A], n: Int): List[A] = {
      //使用了临时变量，不好
      //            var ll = list
      //            for (i <- 0 until n) {
      //                ll = tail(ll)
      //            }
      //            ll
      if (n <= 0) list
      else
        list match {
          case Nil => Nil
          case Cons(_, t) => drop(t, n - 1) //转化为删除n-1个元素
        }
    }

    /**
     * 3.5：删除列表中前缀符合判断的元素
     *
     * @param list
     * @param f
     * @tparam A
     * @return
     */
    def dropWhile[A](list: List[A], f: A => Boolean): List[A] = {
      list match {
        case Cons(h, t) if f(h) => dropWhile(t, f) //转化为对每个头元素进行判断
        case _ => list
      }
    }

    /**
     * 3.5：改进高阶函数的类型推导，调用时不需要再使用声明第二个参数的类型
     *
     * 因为参数组里的类型信息会从第一个参数传递到第二个参数，因为第一个参数类型是Int，第二个也为Int
     *
     * @param list
     * @param f
     * @tparam A
     * @return
     */
    def dropWhile2[A](list: List[A])(f: A => Boolean): List[A] = {
      list match {
        case Cons(h, t) if f(h) => dropWhile(t, f) //转化为对每个头元素进行判断
        case _ => list
      }
    }

    /**
     * 书上原定义方法
     *
     * @param a1 被解开，并放在a2的前面
     * @param a2
     * @tparam A
     * @return
     */
    def append[A](a1: List[A], a2: List[A]): List[A] = {
      a1 match {
        case Nil => a2
        case Cons(h, t) => Cons(h, append(t, a2))
      }
    }

    /**
     * 3.6：返回除最后一个元素之外的所有元素
     *
     * @param list
     * @tparam A
     * @return
     */
    def init[A](list: List[A]): List[A] = {
      list match {
        case Nil => sys.error("空列表的尾无法操作")
        case Cons(_, Nil) => Nil
        case Cons(h, t) => Cons(h, init(t))
      }
    }

    /**
     * 书上原定义方法
     *
     * 右折叠简单运用
     *
     * @param as
     * @param z 起始值
     * @param f 独立出来，让类型系统推出f的输入类型
     * @tparam A 元素类型
     * @tparam B 起始值类型
     * @return
     */
    def foldRight[A, B](as: List[A], z: B)(f: (A, B) => B): B = {
      as match {
        case Nil => z
        case Cons(x, xs) => f(x, foldRight(xs, z)(f))
      }
    }

    def sum2(ns: List[Int]): Int = {
      //foldRight函数不是面向特定的元素类型，泛化的类型不必一定与List中的元素类型相同
      foldRight(ns, 0)((x, y) => x + y)
    }

    def product2(ns: List[Double]): Double = {
      foldRight(ns, 1.0)(_ * _) //(_ * _)是(x,y) => x*y的简写
    }

    //3.7：问题有点难，第五章再来回顾

    //3.8：对foldRight传入Nil和Cons
    foldRight(Cons(1, Cons(2, Cons(3, Nil))), Nil: List[Int])(Cons(_, _))
    Cons(1, foldRight(Cons(2, Cons(3, Nil)), Nil: List[Int])(Cons(_, _)))
    Cons(1, Cons(2, foldRight(Cons(3, Nil), Nil: List[Int])(Cons(_, _))))
    Cons(1, Cons(2, Cons(3, foldRight(Nil, Nil: List[Int])(Cons(_, _)))))
    Cons(1, Cons(2, Cons(3, Nil)))

    /**
     * 3.9：使用foldRight计算LIst的长度
     *
     * @param as
     * @tparam A
     * @return
     */
    def length[A](as: List[A]): Int = {
      //每存在一个元素对acc进行加1，右折叠从1开始
      foldRight(as, 1)((_, acc) => acc + 1)
    }

    /**
     * 3.10：使用尾递归实现，防止List太大造成StackOverflow
     *
     * @param as
     * @param z
     * @param f
     * @tparam A
     * @tparam B
     * @return
     */
    def foldLeft[A, B](as: List[A], z: B)(f: (B, A) => B): B = {
      as match {
        case Nil => z
        case Cons(h, t) => foldLeft(t, f(z, h))(f)
      }
    }

    /**
     * 3.11-1：使用foldLeft实现
     *
     * @param ns
     * @return
     */
    def sum3(ns: List[Int]): Int = {
      foldLeft(ns, 0)(_ + _)
    }

    /**
     * 3.11-2：使用foldLeft实现
     *
     * @param ns
     * @return
     */
    def product3(ns: List[Double]): Double = {
      foldLeft(ns, 1.0)(_ * _)
    }

    /**
     * 3.11-3：使用foldLeft实现
     *
     * @param as
     * @tparam A
     * @return
     */
    def length3[A](as: List[A]): Int = {
      //左折叠从0开始，第一个参数是B，第二个才是A，acc是长度计算，_是元素
      foldLeft(as, 0)((acc, _) => acc + 1)
    }

    /**
     * 3.12：反转列表。使用一个折叠实现
     *
     * @param list
     * @tparam A
     * @return
     */
    def reverse[A](list: List[A]): List[A] = {
      //默认传入空列表，elements参数是返回类型，h参数是A元素。相当于每次做链接的头插入操作
      foldLeft(list, List[A]())((elements, h) => Cons(h, elements))
    }

    //3.13：（使用foldLeft实现foldRight，避免栈溢出）
    def foldRightViaFoldLeft[A, B](l: List[A], z: B)(f: (A, B) => B): B = {
      foldLeft(reverse(l), z)((b, a) => f(a, b))
    }

    def foldRightViaFoldLeft_1[A, B](l: List[A], z: B)(f: (A, B) => B): B = {
      foldLeft(l, (b: B) => b)((g, a) => b => g(f(a, b)))(z)
    }

    //我们正在调用“foldright”，将“b”类型实例化为“b=>b”，然后
    //使用'z'参数调用生成的函数。尝试用等号替换等号来扩展定义。
    //使用一个简单的例子，比如“foldLeft（list（1,2,3），0）”注意，这些实现更重要的是理论上的兴趣——它们不安全，也不适用于大的列表
    def foldLeftViaFoldRight[A, B](l: List[A], z: B)(f: (B, A) => B): B = {
      foldRight(l, (b: B) => b)((a, g) => b => g(f(b, a)))(z)
    }

    /**
     * 3.14：根据foldLeft或者foldRight实现append函数
     *
     * @param l
     * @param r
     * @tparam A
     * @return
     */
    def appendViaFoldRight[A](l: List[A], r: List[A]): List[A] = {
      //列表，初始值r，拼接方法是构造函数
      foldRight(l, r)(Cons(_, _))
    }

    /**
     * 3.15：写一个函数将一组列表连接成一个单个列表。它的运行效率应该随着所有列表的总长度线性增长，试着用我们定义过的函数。
     *
     * @param l
     * @tparam A
     * @return
     */
    def concat[A](l: List[List[A]]): List[A] = {
      //列表集，初始值列表Nil，拼接函数append
      foldRight(l, Nil: List[A])(append)
    }

    /**
     * 3.16：对列表中每个元素进行加1操作
     *
     * @param list
     * @return
     */
    def add1(list: List[Int]): List[Int] = {
      //列表、初始值Nil、操作函数（List的尾部一定是Nil，用右折叠，第一个元素是已知）
      foldRight(list, Nil: List[Int])((h, t) => Cons(h + 1, t))
    }

    /**
     * 3.17：将列表的每个元素改为String类型
     *
     * @param list
     * @return
     */
    def doubleToString(list: List[Double]): List[String] = {
      foldRight(list, Nil: List[String])((h, t) => Cons(h.toString, t))
    }

    /**
     * 3.18：对列表中的每个元素进行修改，并维持列表结构
     *
     * @param as
     * @param f
     * @tparam A
     * @tparam B
     * @return
     */
    def map[A, B](as: List[A])(f: A => B): List[B] = {
      //            foldRightViaFoldLeft(as, Nil:List[B])((h,t) => Cons(f(h),t))
      foldRight(as, Nil: List[B])((h, t) => Cons(f(h), t))
    }

    /**
     * 3.19：从列表中删除不满足断言的元素，并用它删除一个List[Int]中所有奇数
     *
     * @param as
     * @param f
     * @tparam A
     * @return
     */
    def filter[A](as: List[A])(f: A => Boolean): List[A] = {
      foldRight(as, Nil: List[A])((h, t) => if (f(h)) Cons(h, t) else t)
    }

    /**
     * 3.20：与map相似，但是传入的f函数是返回列表，这个f返回的列表会被塞到flatMap最终返回的列表中
     *
     * @param as
     * @param f
     * @tparam A
     * @tparam B
     * @return
     */
    def flatMap[A, B](as: List[A])(f: A => List[B]): List[B] = {
      concat(map(as)(f))
    }

    /**
     * 3.21：使用flatMap实现filter
     *
     * @param as
     * @param f
     * @tparam A
     * @return
     */
    def filterByFlatMap[A](as: List[A])(f: A => Boolean): List[A] = {
      flatMap(as)(a => if (f(a)) List(a) else Nil)
    }

    /**
     * 3.22： 接收两个列表，对相应元素相加构造出新的列表
     *
     * @param a
     * @param b
     * @return
     */
    def addPairwise(a: List[Int], b: List[Int]): List[Int] = {
      (a, b) match {
        case (Nil, _) => Nil
        case (_, Nil) => Nil
        case (Cons(h1, t1), Cons(h2, t2)) => Cons(h1 + h2, addPairwise(t1, t2))
      }
    }

    /**
     * 3.23：与上面相同，进行泛化
     *
     * @param a
     * @param f
     * @tparam A
     * @return
     */
    def zipWith[A, B, C](a: List[A], b: List[B])(f: (A, B) => C): List[C] =
      (a, b) match {
        case (Nil, _) => Nil
        case (_, Nil) => Nil
        case (Cons(h1, t1), Cons(h2, t2)) => Cons(f(h1, h2), zipWith(t1, t2)(f))
      }

    /**
     * 3.24：检查一个List子序列是否包含另一个List
     *
     * @param sup
     * @param sub
     * @tparam A
     * @return
     */
    @tailrec
    def hasSubsequence[A](sup: List[A], sub: List[A]): Boolean =
      sup match {
        //sup是空集时，当且仅当sub是空集，集合相等
        case Nil => sub == Nil
        case _ if startsWith(sup, sub) => true
        case Cons(h, t) => hasSubsequence(t, sub)
      }

    /**
     * 判断sub的元素是否均存在于sup
     *
     * @param sup
     * @param sub
     * @tparam A
     * @return
     */
    @tailrec
    def startsWith[A](sup: List[A], sub: List[A]): Boolean = {
      (sup, sub) match {
        //空集是任何集合的子集，此时sup还有元素
        case (_, Nil) => true
        //均是非空集
        case (Cons(h, t), Cons(h2, t2)) if h == h2 => startsWith(t, t2)
        case _ => false
      }
    }
  }

  /*=============未测试=========================*/
  //定义树结构
  sealed trait Tree[+A]

  //叶子
  case class Leaf[A](value: A) extends Tree[A]

  //非叶子
  case class Branch[A](left: Tree[A], right: Tree[A]) extends Tree[A]

  //树（二叉树）
  //与List类似，Tree也只有两种情况，叶子与非叶子（非空树）
  object Tree {

    /**
     * 3.25：统计树的节点数
     *
     * @param t
     * @tparam A
     * @return
     */
    def size[A](t: Tree[A]): Int =
      t match {
        case Leaf(_) => 1
        //左右子树个数+1（root）
        case Branch(l, r) => 1 + size(l) + size(r)
      }

    /**
     * 3.26：获取Tree中最大的元素值
     *
     * @param t
     * @return
     */
    def maximum(t: Tree[Int]): Int =
      t match {
        case Leaf(l) => l
        case Branch(l, r) => maximum(l) max maximum(r)
      }

    /**
     * 3.27：计算根节点到叶子的最大深度
     *
     * @param t
     * @tparam A
     * @return
     */
    def depth[A](t: Tree[A]): Int =
      t match {
        //叶子深度为-
        case Leaf(l) => 0
        //左边最大深度+右边最大深度+1
        case Branch(l, r) => 1 + (depth(l) max depth(r))
      }

    /**
     * 3.28：类似List中map
     *
     * @param t
     * @tparam A
     * @tparam B
     * @return
     */
    def map[A, B](t: Tree[A])(f: A => B): Tree[B] =
      t match {
        case Leaf(n) => Leaf(f(n))
        case Branch(l, r) => Branch(map(l)(f), map(r)(f))
      }

    /**
     * 3.29 泛化上面三个函数，进一步抽象三个函数
     *
     * 与List类似，使用折叠
     *
     * @param t 需要操作的二叉树
     * @param f 入参是树的元素类型A，返回参数是具体操作函数g的参数类型B
     * @param g 具体操作函数
     * @tparam A
     * @tparam B
     * @return
     */
    def fold[A, B](t: Tree[A])(f: A => B)(g: (B, B) => B): B =
      t match {
        case Leaf(a) => f(a)
        //对左子树应用操作函数且对右子树应用操作函数
        case Branch(l, r) => g(fold(l)(f)(g), fold(r)(f)(g))
      }

    def sizeViaFold[A](t: Tree[A]): Int = {
      //对左右子树进行size操作，从1开始
      fold(t)(_ => 1)(1 + _ + _)
    }

    def maximumViaFold(t: Tree[Int]): Int = {
      //对左右子树进行max操作，从任意元素开始
      fold(t)(a => a)(_ max _)
    }

    def depthViaFold[A](t: Tree[A]): Int = {
      //对左右子树进行depth操作，从0开始
      fold(t)(_ => 0)((d1, d2) => 1 + (d1 max d2))
    }

  }

}
