import java.io.{PrintWriter}

import scala.collection.mutable
import scala.io.{Source, StdIn}
import scala.reflect.io.File

case class Person(id: Int, name: String, age: Int)

/**
 * Scala 基本笔记
 */
object ScalaLesson extends App {

  /**
   * 基础-算术
   */
  def lesson1() = {
    println(1.+(2))
    println(2.-(2))
    println(2.*(2))
    println(2./(2))
    println(10 max 2)
  }

  /**
   * 基础-重载
   */
  def lesson2() = {
    val str = "Hello"(2)
    //等同于java中的”Hello“.charAt(2)
    println(str)
    //实现原理为StringOps包中的一个apply方法:def apply(n:Int): Char
    val s = "Hello".apply(2)
    println(s)
  }

  /**
   * 基础-字符串操作
   */
  def lesson3() = {
    val str = "Hello World"
    //获取字符串的第一个字符
    println(str.head)
    //获取字符串的最后一个字符
    println(str.last)
    //获取字符串前3个字符
    println(str.take(3))
    //获取字符串后3个字符
    println(str.takeRight(3))
    //删除字符串前3个字符
    println(str.drop(3))
    //删除字符串后3个字符
    println(str.dropRight(3))
  }

  /**
   * 结构与函数-条件判断
   */
  def lesson4(n: Int) = {
    //if/else语法结构与java结构一致,在scala中表达式是有值的,这个值就是跟在if/else之后的表达式的值
    val s = if (n > 1) 1 else 0
    //等同于以下方式,不过在scala中推荐使用val而尽量不使用var可变量
    //注:scala中不支持三目运算,scala把java的三目运算结合在了if/else中
    var a = 0
    if (n > 1) a = 1 else a = 0
    println(s)
    println(a)
  }

  /**
   * 结构与函数-公共超类
   */
  def lesson5(n: Int) = {
    //在scala中存在一个公共超类Any,java中只能返回指定类型,scala可以返回不指定的类型,支持混合型表达式
    val s = if (n > 0) "Hello World" else 1
    println(s)
    //在scala中如果if/else中缺失了else后部分,如:
    val a = if (n > 0) "Hello World"
    //在scala中每个表达式都是有值的,如果缺失了else后部分,为了解决这个问题,在java的基础上引入了一个Unit类,写作(),这样就等同于if(n>0) "Hello World" else ()
    //这个()相当于一个无用值,Unit与java中的void相当,区别在于void没有值,Unit有一个“无用”的值。
    println(a)
  }

  /**
   * 结构与函数-终止语句
   */
  def lesson6(n: Int) = {
    //scala中不需要加分号结束,如果是多行写成1行则需要分号结束,但不建议这样写,如:
    val s = if (n > 0) {
      var r = 1 * n
      r -= 1
    } else 1
    //
  }

  /**
   * 结构与函数-块表达式
   */
  def lesson7(n: Int) = {
    //与java中的{}一致,但scala的块是一种表达式,所以有值,值为表达式的最后语句返回值
    val s = {
      val a = 5
      a * n
    }
    println(s)
  }

  /**
   * 结构与函数-输入与输出
   */
  def lesson8() = {
    //不换行打印
    //print("Scala")
    //换行打印
    //println("你好")
    //上面2个打印等同于下面语句
    //println("Scala" + "你好")
    //带C风格格式化字符串的函数
    //printf("Hello,%s! 你是一款很好的语言,你存在有 %y 年了吗?","Scala",10)
    //读取控制台一行输入(2.11.0以后的版本使用StdIn)
    val name = StdIn.readLine()
    println("你输入的姓名是:" + name)
    val age = StdIn.readInt()
    println("你输入的年龄是:" + age)
  }

  /**
   * 结构与函数-while循环
   */
  def lesson9(n: Int) = {
    //与java中的while和do循环一致
    var i = n
    while (i > 0) {
      println(i * 3)
      i -= 1
    }
  }

  /**
   * 结构与函数-for循环
   */
  def lesson10(n: Int) = {
    //scala中的for循环与java的结构不一样,scala的结构为 for(i <- 表达式)
    for (i <- 0 to n) println(i * 3)
    println("-----------------这是分割线-------------------")
    //在RichInt类中存在 to 这个方法。0 to n 代表的是 0到n的区间(包含n),如果只是0到n-1的话这采用until方法,如下:
    for (i <- 0 until n) println(i * 3)
    println("-----------------这是分割线-------------------")
    val str = "Hello"
    var sum = 0
    //在循环中不仅可以对数字区间进行遍历,也可以对字符串进行遍历
    for (ch <- str) sum += ch
    println(sum)
  }

  /**
   * 结构与函数-退出循环
   */
  def lesson11(n: Int) = {
    //scala中的并没有提供break或者continue语句来退出循环,如果需要break该怎么做,如下:
    //1:需要引入import util.control.Breaks._包
    import util.control.Breaks._
    //break例子
    breakable(
      for (i <- 0 to 5) {
        println(i)
        if (i == n) break
      }
    )
    println("-----------------这是分割线-------------------")
    //continue例子,需要注意判断要放在最前面
    for (i <- 0 to 5) {
      breakable {
        if (i == n) break
        println(i)
      }
    }
    println("-----------------这是分割线-------------------")
    //中断嵌套循环
    breakable(
      for (i <- 0 to 5) {
        println(i)
        breakable(
          for (a <- 1 to 3) {
            println("i * a = " + i * a)
            if (i * a == 1) break
          }
        )
      }
    )
  }

  /**
   * 结构与函数-高级循环
   */
  def lesson12(n: Int) = {
    //scala支持多个生成器,之间用分号隔开,(等同于多个嵌套for循环)如下:
    for (i <- 0 to 3; j <- 1 to 4) println(i * j)
    println("-----------------这是分割线-------------------")
    //scala支持守卫,以if开头的Boolean表达式(注:if之前没有分号):
    for (i <- 0 to 3; j <- 1 to 4 if i < j) println(i * j)
    //scala还支持引入循环中的变量:
    for (i <- 1 to 3; j <- n to 3) println(i * j)
    //for推导式,以yield开始:
    val list = for (i <- 1 to 10) yield i % 3
    println(list)
  }

  /**
   * 结构与函数-默认参数与带名参数
   */
  def lesson13(name: String, n: Int = 18) = {
    println(s"你的姓名是:$name,年龄是:$n")
  }

  /**
   * 懒值,只需要在需要懒加载的值或函数前面加 lazy关键字即可,如:
   * 只有在调用的时候才执行,不调用的时候不执行
   */
  lazy val m = System.currentTimeMillis()

  /**
   * 结构与函数-异常处理
   */
  def lesson14(n: Int): Unit = {
    //scala异常的工作原理与java的一样,区别在于scala没有受体异常--不需要声明函数或者方法可能会抛出某种异常
    // throw new XXException(xxx)
    //捕抓异常采用模式匹配
    val a = try {
      10 / n
    } catch {
      case ex: Exception => println(ex.getMessage)
    }
  }

  /**
   * 数组操作
   */
  def lesson15() = {
    import scala.collection.mutable.ArrayBuffer
    //定长数组
    val arr1 = new Array[Int](5)
    println(arr1.toString, arr1.length)
    //推导数组,类型根据推导出来,提供初始值的时候不需要new
    val arr2 = Array("Hello", "world")
    println(arr2.toString, arr2.length)
    //根据角标获取值时采用()而不是java的[]
    println(arr2(0))

    //变长数组:数组缓冲
    //创建空的数组缓冲,准备存放整数
    val arr3 = ArrayBuffer[Int]()
    //用 += 在尾端添加元素
    arr3 += 1
    //得到ArrayBuffer(1)
    arr3 += (1, 3, 4, 8, 2)
    //得到ArrayBuffer(1,1,3,4,8,2)
    arr3 ++= Array(9, 3, 5)
    // 可以用 ++= 操作符追加任何的集合
    //得到ArrayBuffer(1,1,3,4,8,2,9,3,5)
    arr3.trimEnd(2)
    //移除最后2个元素
    arr3.trimStart(2)
    //移除最前2个元素

    //可以在任意位置插入元素(低效)
    arr3.insert(1, 3)
    //可以在任意地方移除多少个元素(低效)
    arr3.remove(1, 2)
    //数组缓冲转成Array
    arr3.toArray
    //数组转数组缓冲
    arr1.toBuffer
  }

  /**
   * 数组操作-遍历
   */
  def lesson16() = {
    val arr1 = Array(1, 2, 6, 3, 8, 4)
    //需要下标时
    for (i <- 0 until arr1.length) println(i + " : " + arr1(i))
    println("-----------------这是分割线-------------------")
    //不需要下标时
    for (e <- arr1) println(e)
    println("-----------------这是分割线-------------------")
    //数组转换,采用yield关键字
    val r = for (e <- arr1) yield 2 * e
    println(r.toList)
  }

  /**
   * 数组操作-常用算法
   */
  def lesson17() = {
    val arr1 = Array(1, 2, 6, 3, 8, 4)
    //求和
    println(arr1.sum)
    //获取最大值
    println(arr1.max)
    //获取最小值
    println(arr1.min)
    //获取平均值
    println(if (arr1.nonEmpty) arr1.sum / arr1.length else 0)
    //从小到大排序(注:排序得到的是一个新数组缓冲,原数组不会改变)
    val r = arr1.sorted
    //指定排序
    val r1 = arr1.sortWith(_ < _)
    println(r.toList)
    println(r1.toList)
    //数组拼接成字符串
    println(r1.toString)
    println(r1.mkString)
    println(r1.mkString(","))
  }

  /**
   * 数组操作-多维数组
   */
  def lesson18(): Unit = {
    //与java一样,多维数组是通过数组的数组来实现的。Double的二维数组类型为Array[Array[Double]]
    //要构造这样函数可以采用ofDim方法:
    val matrix = Array.ofDim[Double](3, 4) //三行四列
    //访问这样的数组,使用两对圆括号(row)(column):
    matrix(1)(2)
  }

  /**
   * 数组操作-与java的相互操作
   */
  def lesson19(): Unit = {
    //引入相应的包
    import scala.collection.JavaConversions.bufferAsJavaList
    import scala.collection.mutable.ArrayBuffer
    val command = ArrayBuffer("ls", "-al", "/home/cay")
    val pb = new ProcessBuilder(command) //Scala转java

    import scala.collection.JavaConversions.asScalaBuffer
    import scala.collection.mutable.Buffer
    val cmd: Buffer[String] = pb.command() //java转Scala
  }

  /**
   * 映射与元组-映射
   */
  def lesson20(): Unit = {
    //构建一个不可变的映射(建议)
    val map = Map("张三" -> 98, "李四" -> 83, "王五" -> 100)
    //构建一个可变的映射(不建议)
    val map1 = mutable.Map("张三" -> 98, "李四" -> 83, "王五" -> 100)
    //构建一个空的映射,需要指定类型参数
    val map2 = new mutable.HashMap[String, Int]()

    // -> 操作符用来创建对偶
    // ("张三" -> 98) 等同于 ("张三" , 98),所以也可以用以下方式定义映射:
    val map3 = Map(("张三", 98), ("李四", 83), ("王五", 100))
    //获取映射中的值,直接使用(),相当于java中的map.get("张三")
    println(map("张三"))
    //如果key不存在,则会抛异常,检查映射中是否存在某个key,可以使用contains方法:
    val value = if (map.contains("张三")) map("张三") else 0
    //可以简写成getOrElse(key,result):
    println(map.getOrElse("张三", 0))
    //更新可变映射中的值
    map1("张三") = 67
    //增加可变映射中的值
    map1("赵六") = 74
    //可以使用 += 增加多个关系
    map1 += ("王七" -> 35, "蔡八" -> 86)
    //可以使用 -= 移除对应的键值
    map1 -= "王五"
    //可以使用 + 操作符生成新的映射
    val map4 = map + ("王七" -> 35, "蔡八" -> 86)
    //映射遍历 for((k,v) <- map)
  }

  /**
   * 映射与元组-元组
   */
  def lesson21() = {
    //映射是键值对的集合,对偶则是元组(tuple)的最简单形态--元组是不同类型的值的聚集,元组的值是通过将单个的值包含在()构成的,如:
    val t = (2, "张三", 4d) //类型分别为 Tuple[Int, String, Double]
    //元组的访问可以使用_1、_2、_3这样
    println(t._1)
    println(t._2)
    println(t._3)
    //也可以在返回值的时候就定义下来,通常使用这种,这样更为直观的表达出返回的结果分别代表什么
    val (first, second, third) = t
    println(first)
    println(second)
    println(third)
  }

  /**
   * 映射与元组-拉链操作
   */
  def lesson22(): Unit = {
    //使用元组的原因之一是吧多个值绑定在一起,以方便它们能够被一起处理,这个通常可以使用zip方法来完成,如下:
    val a1 = Array("张三", "李四", "王五")
    val a2 = Array(55, 26, 96)
    val p = a1.zip(a2) //得到的对偶数组为Array(("张三",55),("李四",26),("王五",96))
    //还可以把对偶数组转成映射
    println(p.toMap)
    //得到结果 Map(张三 -> 55, 李四 -> 26, 王五 -> 96)
  }

  /**
   * 对象-单例对象
   * 在Scala中没有静态方法或者静态字段,可以通过object语法结构来定义
   *
   */
  object Accounts { //伴生对象
    private var lastNumber = 0

    def newUniqueNumber() = {
      lastNumber += 1; lastNumber
    }
  }

  /**
   * 对象-伴生对象
   * 在Scala中没有静态方法或者静态字段,可以通过object语法结构来定义
   *
   */
  class Accounts {
    val id = Accounts.newUniqueNumber()
  }

  /**
   * 对象-扩展类或特质对象
   */
  abstract class UndoableAction(val desc: String) {
    def undo(): Unit

    def redo(): Unit
  }

  object DoNothingAction extends UndoableAction("Do nothing") {
    override def undo(): Unit = {}

    override def redo(): Unit = {}
  }

  //DoNothingAction可以被所有所需要这个缺省行为的地方共用
  val actions = Map("open" -> DoNothingAction, "save" -> DoNothingAction)

  /**
   * 对象-应用程序对象
   * 每个Scala程序都必须从一个对象main方法开始,也可以扩展App特质
   * object Hello{
   * def main(args:Array[String]) {
   * println("Hello World")
   * }
   * }
   */

  /**
   * 对象-枚举
   * scala中并没有枚举类型,不过提供了一个Enumeration助手类,可以用于产出枚举
   */
  object TrafficLightColor extends Enumeration {
    val Red = Value(0, "Stop")
    val Yellow = Value(10)
    val Green = Value("Go")
  }

  def lesson23(): Unit = {
    println(TrafficLightColor.Red)
    println(TrafficLightColor.Yellow)
    println(TrafficLightColor.Green)
  }

  /**
   * 文件操作-读取文件
   */
  def lesson24(): Unit = {
    //以指定的GBK字符集读取文件,第一个参数可以是字符串或者是java.io.File
    val source = Source.fromFile("贪腐词库.txt", "GBK")
    //获取所有行
    val lines = source.getLines()
    //将所有行放到list中
    val list = lines.toList
    //将文件用逗号串起来(注:旧版采用source.mkString,新版中获取不到值)
    val str = list.mkString
    //println("------------"+str)
    //迭代打印集合
    //list.foreach(it=>println(it))
    //关闭流
    source.close()
  }

  /**
   * 文件操作-从URL读取文件
   */
  def lesson25(): Unit = {
    val source = Source.fromURL("https://www.baidu.com", "UTF-8")
    //获取所有行
    val lines = source.getLines()
    //将所有行放到list中
    val list = lines.toList
    //将文件用逗号串起来(注:旧版采用source.mkString,新版中获取不到值)
    val str = list.mkString
    //println("------------"+str)
    //迭代打印集合
    list.foreach(it => println(it))
    //关闭流
    source.close()
  }

  /**
   * 文件操作-读取二进制文件
   */
  def lesson26(): Unit = {
    val file = File("其他词库.txt")
    val in = file.inputStream()
    val bytes = new Array[Byte](file.length.toInt)
    in.read(bytes)
    println(bytes.mkString(","))
    in.close()
  }

  /**
   * 文件操作-写入文件
   */
  def lesson27() = {
    //scala中没有内建对文件的支持,所以使用java.io.PrintWriter
    val out = new PrintWriter("test.txt")
    for (i <- 1 to 100) out.println(i)
  }

  /**
   * 正则表达式
   */
  def lesson28() = {
    //采用String中的r方法
    val pattern = "[0-9]+".r
    println(pattern findAllIn "02,2")
  }

  /**
   * 操作符
   */
  def lesson29() = {
    //在scala中可以使用任意序列的操作字符作为定义,如:
    val * = "ere"
    val & = "ere"
    val ! = "ere"
    //一旦遇到命名定义是scala关键字的还可以采用反引号来拯救(当然平时还是注意少用scala的关键字来命名)
    val `type` = 354
    //还可以使用 a 标识符 b 这样写
    //1 to 10 //实际上是调用了 1.to(10) 的方法
    //1-> 10 //等同调用 1.—>(10) 的方法
    //1 toString //等同于 1.toString ,以上均为一元操作符,点号可以省略
    //赋值操作符 a 操作符= b 等同于 a = a + b
    //1 += 2 //等同于 1 = 1 + 2
    //结合性,已冒号结束,操作符是右结合
    //1::2::Nil //等同于 1::(2::Nil)
  }

  import math._

  /**
   * 高阶函数-值函数
   */
  def lesson30() = {
    val num = 3.14
    //把 ceil函数赋值给fun
    val fun = ceil _
    val a = Array(2.14, 1.42, 2.0).map(fun)
    println(a.mkString(","))
  }

  /**
   * 高阶函数-匿名函数
   */
  def lesson31() = {
    val s = (x: Double) => 3 * x
    //等同于 def s(x:Double) = 3 * x
    println(s(3))
  }

  /**
   * 高阶函数-带函数参数的函数
   */
  def lesson32(f: (Double) => Double) = {
    f(0.25)
  }
  //对于只出现一次的参数,可以使用_替代
  //lesson32(ceil _)

  /**
   * 高阶函数-一些有用的高阶函数
   */
  def lesson33() = {
    val list = List("张三", "李四", "王五")
    //map方法,遍历应用到集合中的所有元素,并返回全新的结果集合
    println(list.map(it => it -> "你好"))
    //foreach方法,遍历集合的所有元素,但不返回结果(对于只出现一次的参数,可以使用_替代)
    list.foreach(println(_))
    //filter方法,根据条件过滤生成全新的集合
    val f = list.filter(_ != "张三")
    println(f)
    //sortWith方法,指定参数排序,返回全新的集合
    println(list.sortWith(_ > _))
    //sorted方法,从低到高排序,返回全新的集合
    println(list.sortWith(_ > _).sorted)
    //groupBy方法,根据指定参数进行合并分组,返回map[object,List[object]]集合
    val userList = List(Person(1, "张三", 23), Person(2, "李四", 45), Person(1, "王五", 30))
    val map: Map[Int, List[Person]] = userList.groupBy(_.id)
    //Map遍历,_1代表key,_2代表value
    map.foreach(it => {
      println(it._1)
      println(it._2)
    })
    //exists方法,遍历判断条件是否成立
    println(list.exists(_ == "张三"))
    //distinct方法,去除重复的元素
    println(list.distinct)
  }

  /**
   * 集合之间的操作
   */
  def lesson34() = {
    //注:在scala中不可变集合进行操作生成的都为新的集合,原有集合不发生改变

    val list = List(1, 2, 3)
    val map = Map(1 -> "java", 2 -> "scala", 3 -> "php")
    //List追加元素, 后面追加采用 :+ 前面追加采用 +:
    println(list :+ 2)
    //List移除元素
    println(list.toSet - 2)
    //List第一位置增加元素
    println(5 +: list)
    //2个List合并,采用:::或者 ++ 或者 | 或者++:
    println(list ++ List(6))
    println(list ::: List(6))
    println(list ++: List(6))
    println(list.toSet | List(7).toSet)
    //list中移除另外一个List中所有包含的元素,采用 -- 或者 &~
    println(list.toSet -- List(2))
    println(list.toSet &~ List(2).toSet)
    //俩个list的交集
    println(list.toSet & List(2).toSet)
  }

  /**
   * 模式匹配-更好的switch
   */
  def lesson35(n: Int) = {
    //scala的switch更优雅的处理方式,在c和类C语言中,模式匹配必须在末尾显式的使用break语句进行退出switch,scala不会有这样的问题
    //match与if类似,都是使用表达式。在很多时候match的使用更优雅与if/else if,这样不会出现多层嵌套的情况
    val result = n match {
      case 1 => "张三"
      case 2 => "李四"
      case _ => "王五"
    }
    println(result)
    //模式用还可以使用守卫,如下:
    val r = n match {
      case 1 => "张三"
      case a if (a > 5) => "李四"
      case _ => "王五"
    }
    println(r)

    //类型匹配,如下:
    val ar: Any = if (n > 0) 1 else "Hello"
    val s = ar match {
      case i: Int => "张三"
      case s: String => "李四"
      case _ => "王五"
    }
    println(s)
  }

  /**
   * 模式匹配-Option类型
   */
  def lesson36() = {
    //生成Option带值的元素
    val o: Option[String] = Some("张三")
    println(o)
    //生成不带值的Option元素
    val o1: Option[Nothing] = None
    println(o1)
    //Option元素获取值,采用get方法
    println(o.get)
    //但是直接采用get方法很容易出现 java.util.NoSuchElementException: None.get异常,scala提供了组合方法getOrElse
    println(o1.getOrElse(""))
  }

  /**
   * 模式匹配-断言
   */
  def lesson37(n: Int) = {
    //在scala中与java一样使用断言,格式 assert(条件,提示语)
    assert(n > 1, "参数不能小于2")
  }

  def lesson38() = {
    (0 to 10).map(println(_))
  }

  lesson38()

}