Scala集合的几个主要类型

Scala集合的几个主要类型

1. Iterable

1.1 概述

  • Iterable特质是Scala集合框架中的一个核心概念,它为不同类型的集合提供了一个统一的接口和实现
  • Iterable特质定义了一个抽象方法iterator,它返回一个Iterator对象,可以逐个访问集合中的元素
  • Iterable特质实现了Traversable特质中的foreach方法,使用iterator来遍历集合中的元素,并对每个元素应用给定的函数
  • Iterable特质提供了许多具体方法,可以对集合进行转换、过滤、分组、排序等操作,这些方法都是基于iterator实现的
  • Iterable特质有两个子特质:Seq和Set,分别表示有序和无序的集合

Iterable代表一个可以迭代的集合, 它继承了Traversable特质, 同时也是其他集合的父特质. 最重要的是, 它定义了获取迭代器(iterator)的方法: def iterator: Iterator[A], 这是一个抽象方法, 它的具体实现类需要实现这个方法, 从而实现迭代的返回集合中的元素.

1.2 分类

Traversable提供了两种遍历数据的方式:

  • iterator方法,它返回一个Iterator对象,可以逐个访问集合中的元素,有返回值

    这种方式属于主动迭代, 我们可以通过hasNext()检查是否还有元素, 并且可以主动的调用next()方法获取元素, 即: 我们可以自主的控制迭代过程.

  • foreach方法,它接受一个函数作为参数,并对集合中的每个元素应用该函数,没有返回值

    这种方式属于被动迭代, 我们只提供一个函数, 并不能控制遍历的过程, 即: 迭代过程是由集合本身控制的.

iterator方法

概述
  • iterator方法是Iterable特质中唯一需要子类实现的方法,它是遍历集合元素的基础
  • iterator方法返回一个Iterator对象,它有两个基本操作:next和hasNext
  • next方法返回当前元素并将迭代器移动到下一个元素,如果没有下一个元素,会抛出NoSuchElementException异常
  • hasNext方法检查是否有下一个元素,返回一个布尔值
  • iterator方法可以用于任何实现了Iterable特质的类,如List、Array、Set、Map等
格式
def iterator: Iterator[A]

其中,A是元素类型,Iterator是一个用于遍历元素的类

下面是一个使用iterator方法的例子:

// 创建一个序列
val seq = Seq(1, 2, 3, 4, 5)
println(seq) // Seq(1, 2, 3, 4, 5)

// 调用iterator方法,得到一个迭代器
val it = seq.iterator
println(it) // non-empty iterator

// 调用hasNext方法,判断是否有下一个元素
println(it.hasNext) // true

// 调用next方法,返回当前元素,并把迭代器向前移动一位
println(it.next()) // 1

// 再次调用next方法,返回下一个元素
println(it.next()) // 2

// 使用while循环,遍历剩余的元素
while (it.hasNext) {
  println(it.next())
}
// 输出:
// 3
// 4
// 5

// 再次调用hasNext方法,判断是否有下一个元素
println(it.hasNext) // false

// 再次调用next方法,抛出异常
println(it.next())
// java.util.NoSuchElementException: next on empty iterator

foreach方法

概述
  • foreach方法是一个集合操作方法,它可以对集合中的每个元素执行一个函数,并返回Unit类型的结果。
  • foreach方法的作用是对集合中的元素进行一些副作用操作,例如打印输出,修改变量,或者调用其他方法等等。
  • foreach方法不会改变原始集合,也不会返回一个新的集合。
格式
def foreach[U](f: A => U): Unit

其中,U是返回类型,表示函数f的返回值类型;f是参数,表示要执行的函数;A是原始集合的元素类型;Unit是返回值类型,表示没有返回值。

下面是一个使用foreach方法的例子:

// 创建一个序列
val seq = Seq(1, 2, 3, 4, 5)
println(seq) // Seq(1, 2, 3, 4, 5)

// 调用foreach方法,参数为打印输出函数
seq.foreach(println)
// 输出:
// 1
// 2
// 3
// 4
// 5

// 调用foreach方法,参数为一个匿名函数,对每个元素加1并打印输出
seq.foreach(x => println(x + 1))
// 输出:
// 2
// 3
// 4
// 5
// 6

// 定义一个变量sum,用于累加序列中的元素
var sum = 0

// 调用foreach方法,参数为一个匿名函数,对每个元素加到sum上
seq.foreach(x => sum += x)

// 打印sum的值
println(sum) // 15

遍历集合iterator方法和foreach方法

需求

  1. 定义一个列表, 存储1, 2, 3, 4, 5这五个数字.
  2. 通过iterator()方法遍历上述的列表.
  3. 通过foreach()方法遍历上述的列表.

参考代码

object ClassDemo01 {
  def main(args: Array[String]): Unit = {
    //定义一个列表, 存储1, 2, 3, 4, 5这五个数字.
    val list1 = (1 to 5).toList

    //通过iterator()方法遍历上述的列表.
    val it: Iterator[Int] = list1.iterator
    while(it.hasNext) {
      val ele = it.next()
      println(ele)
      if(ele == 3) println("额外的操作: " + ele * 10)
    }
    println("-" * 15)

    //通过foreach()方法遍历上述的列表.
    list1.foreach(println(_))
  }
}

分组遍历grouped()方法

如果遇到将Iterable对象中的元素分成固定大小的组, 然后遍历这种需求, 就可以通过grouped()方法来实现了.

  • grouped()方法是一个集合操作方法,它可以把一个集合分成若干个子集合,每个子集合的元素个数由参数指定。

例如,如果有一个集合Seq(1, 2, 3, 4, 5, 6),调用grouped(2)方法,就会得到一个迭代器,它包含三个子集合Seq(1, 2)Seq(3, 4)Seq(5, 6)
如果参数不能被集合的元素个数整除,那么最后一个子集合的元素个数会小于参数。例如,如果有一个集合Seq(1, 2, 3, 4, 5),调用grouped(2)方法,就会得到一个迭代器,它包含三个子集合Seq(1, 2)Seq(3, 4)Seq(5)

  • grouped()方法的作用是把一个大的集合分割成小的集合,方便对每个小的集合进行操作。

例如,可以对每个小的集合求和,或者排序,或者转换成其他类型等等。grouped()方法返回的是一个迭代器,所以需要用toVector或者toList等方法转换成具体的集合类型。

grouped()方法的定义如下:

def grouped(size: Int): Iterator[Repr]

其中,size是参数,表示每个子集合的元素个数;Repr是返回类型,表示原始集合的类型;Iterator是返回值的容器类型,表示一个迭代器。

下面是一个使用grouped()方法的例子:

// 创建一个序列
val seq = Seq(1, 2, 3, 4, 5, 6)
println(seq) // Seq(1, 2, 3, 4, 5, 6)

// 调用grouped()方法,参数为2
val grouped = seq.grouped(2)
println(grouped) // Iterator(Seq(1, 2), Seq(3, 4), Seq(5, 6))

// 转换成向量
val vector = grouped.toVector
println(vector) // Vector(Seq(1, 2), Seq(3, 4), Seq(5, 6))

// 对每个子向量求和
val sum = vector.map(_.sum)
println(sum) // Vector(3, 7, 11)

需求

  1. 定义一个Iterable集合, 存储1~13之间的所有整数.
  2. 通过grouped()方法, 对Iterable集合按照5个元素为一组的形式进行分组, 遍历并打印结果.

参考代码

object Demo {
  def main(args: Array[String]): Unit = {
    //定义Iterable集合, 存储1~13之间的数字.
    val i1 = (1 to 13).toIterable
    //通过grouped方法按照5个元素为一组的方式, 对Iterable集合分组.
    val result1 = i1.grouped(5)
    //遍历元素, 打印结果.
    while (result1.hasNext) {
      println(result1.next())
    }/*
    Vector(1, 2, 3, 4, 5)
    Vector(6, 7, 8, 9, 10)
    Vector(11, 12, 13)
    */
  }
}

按照索引生成元组zipWithIndex方法

Iterable集合中存储的每个元素都是有索引的, 如果我们想按照元素 -> 索引这种格式, 生成一个新的集合, 此时, 就需要用到zipWithIndex()方法了.

  • zipWithIndex方法是一个集合操作方法,它可以把一个集合中的每个元素和它对应的索引组成一个元组,然后返回一个新的集合。
  • 索引从0开始,依次递增。
  • zipWithIndex方法可以用于给集合中的元素添加编号,或者根据索引进行筛选或排序等操作。
  • zipWithIndex方法不会改变原始集合,也不会受到原始集合类型的影响,它可以用于可变或不可变的集合。

zipWithIndex方法的定义如下:

def zipWithIndex: Iterable[(A, Int)]

其中,A是原始集合的元素类型;Iterable是返回值类型,表示一个可迭代的集合;(A, Int)是返回集合的元素类型,表示一个由原始元素和索引组成的元组。

下面是一个使用zipWithIndex方法的例子:

// 创建一个序列
val seq = Seq("a", "b", "c", "d", "e")
println(seq) // Seq(a, b, c, d, e)

// 调用zipWithIndex方法,得到一个新的序列
val zipped = seq.zipWithIndex
println(zipped) // List((a,0), (b,1), (c,2), (d,3), (e,4))

// 遍历新的序列,打印每个元素和索引
zipped.foreach { case (elem, index) =>
  println(s"Element = $elem, Index = $index")
}
// 输出:
// Element = a, Index = 0
// Element = b, Index = 1
// Element = c, Index = 2
// Element = d, Index = 3
// Element = e, Index = 4

// 使用新的序列,筛选出索引为偶数的元素
val evenIndexed = zipped.filter { case (_, index) =>
  index % 2 == 0
}
println(evenIndexed) // List((a,0), (c,2), (e,4))

需求

  1. 定义一个Iterable集合, 存储"A", “B”, “C”, “D”, "E"这五个字符串.
  2. 通过zipWithIndex()方法, 按照 字符串->索引这种格式, 生成新的集合.
  3. 重新按照 索引->字符串这种格式, 生成新的集合.
  4. 打印结果.

参考代码

object Demo {
  def main(args: Array[String]): Unit = {
    //定义一个Iterable集合, 存储"A", "B", "C", "D", "E"这五个字符串.
    val i1 = Iterable("A", "B", "C", "D", "E")
    //通过zipWithIndex()方法, 按照 字符串->索引这种格式, 生成新的集合.
    val i2 = i1.zipWithIndex    //List((A,0), (B,1), (C,2))
    //重新按照 索引->字符串这种格式, 生成新的集合.
    val i3 = i1.zipWithIndex.map(x => x._2 -> x._1)
    //打印结果.
    println(i2)//List((A,0), (B,1), (C,2), (D,3), (E,4))
    println(i3)//List((0,A), (1,B), (2,C), (3,D), (4,E))
  }
}

判断集合是否相同sameElements()方法

有时候, 我们不仅想判断两个集合中的元素是否全部相同, 而且还要求这两个集合元素的迭代顺序保持一致, 此时, 就可以通过sameElements()方法来实现该需求了.

即sameElements()方法的功能是: 判断集合所包含的元素及元素的迭代顺序是否一致.

  • sameElements方法是一个集合操作方法,它可以判断两个集合是否包含相同的元素,并且元素的顺序也相同。
  • 这个方法不会受到集合类型的影响,它可以用于可变或不可变的集合,也可以用于有限或无限的集合。
  • 如果两个集合是无限的,那么这个方法可能永远不会终止。

sameElements方法的定义如下:

def sameElements(that: Iterable[_]): Boolean

其中,that是另一个要比较的集合;Boolean是返回值类型,表示是否相同。

例子

object ClassDemo04 {
  def main(args: Array[String]): Unit = {
    //1. 定义Iterable集合i1, 包含"A", "B", "C"这三个元素.
    val i1 = Iterable("A", "B", "C")
    //2. 判断i1和Iterable("A", "B", "C")集合是否相同.
    println(i1.sameElements(Iterable("A", "B", "C")))
    //3. 判断i1和Iterable("A", "C", "B")集合是否相同.
    println(i1.sameElements(Iterable("A", "C", "B")))
    //4. 定义Iterable集合i2, 包含"A", "B", "C", "D"这四个元素.
    val i2 = Iterable("A", "B", "C", "D")
    //5. 判断i1和i2是否相同.
    println(i1.sameElements(i2))

    //6. 扩展: 创建HashSet集合存储1, 2, 创建TreeSet集合存储2, 1, 然后判断它们是否相同.
    val hs = mutable.HashSet(1, 2)
    val ts = mutable.TreeSet(2, 1)
    println(hs.sameElements(ts))    //结果是true, 因为TreeSet会对元素排序.
  }
}

2. Seq

2.1 概述

Seq特质代表按照一定顺序排列的元素序列, 序列是一种特别的可迭代集合, 它的元素特点是有序(元素存取顺序一致), 可重复, 有索引.

Seq特质有两个子特质:IndexedSeq和LinearSeq,分别表示基于索引和基于链表的序列。

2.2 Seq特质的定义

Seq特质的定义如下:

trait Seq[+A] extends Iterable[A]

其中,A是序列元素的类型,+表示协变,即如果B是A的子类型,那么Seq[B]也是Seq[A]的子类型。Seq特质继承自Iterable特质,因此具有可迭代的性质。

2.3 分类

在这里插入图片描述

IndexedSeq和LinearSeq是Seq的子特质, 代表着序列的两大子门派.

  1. IndexedSeq特质代表索引序列, 相对于Seq来说, 它并没有增加额外的方法, 对于随机访问元素的方法来讲, 它更加有效, 常用的子集合有: NumericRange, Range, Vector, String等.

    • Range集合

      Range代表一个有序的整数队列, 每个元素之间的间隔相同, 例如奇数队列:1, 3, 5, 7, 9, 但是斐波那契数列1, 1, 2, 3, 5, 8就不属于Range类. 简单来说, Range类似于数学中的等差数列.

    • NumericRange集合

      NumericRange集合是一个更通用的等差队列, 可以生成Int, BigInteger, Short, Byte等类型的序列.

    • Vector集合

      Vector是一个通用的不可变的数据结构, 相对来讲, 它获取数据的时间会稍长一些, 但是随机更新数据要比数组和链表快很多.

  2. LinearSeq特质代表线性序列, 它通过链表的方式实现, 因此它的head, tail, isEmpty执行起来相对更高效. 常用的子集合有: List, Stack, Queue等.

    • Stack: 表示数据结构, 元素特点是先进后出.

      由于历史的原因, Scala当前的库中还包含一个immutable.Stack, 但当前已经被标记为弃用, 因为它的设计不怎么优雅, 而且性能也不太好, 因为栈会涉及到大量元素的进出, 所以不可变栈的应用场景还是比较小的, 最常用的还是可变栈, 例如: mutable.Stack, mutable.ArrayStack.

      • mutable.Stack: 是通过List, 也就是链表的方式实现的, 增删快, 查询慢.
      • mutable.ArrayStack: 是通过Array, 也就是数组的方式实现的, 查询快, 增删慢.
    • Queue: 表示队列数据结构, 元素特点是先进先出.

2.4 Seq特质的常用方法

Seq特质提供了很多方法来操作序列中的元素,其中一些常用方法如下:

  • apply(i): 返回序列中索引为i的元素。
  • length: 返回序列中元素的个数。
  • isEmpty: 判断序列是否为空。
  • head: 返回序列中第一个元素。
  • tail: 返回除了第一个元素之外剩余元素组成的新序列。
  • last: 返回序列中最后一个元素。
  • init: 返回除了最后一个元素之外剩余元素组成的新序列。
  • take(n): 返回序列中前n个元素组成的新序列。
  • drop(n): 返回除了前n个元素之外剩余元素组成的新序列。
  • slice(from, until): 返回从索引from(包含)到until(不包含)之间的元素组成的新序列。
  • updated(i, x): 返回一个新序列,其中索引为i的元素被替换为x。
  • indexOf(x): 返回x在序列中第一次出现时的索引,如果不存在则返回-1。
  • lastIndexOf(x): 返回x在序列中最后一次出现时的索引,如果不存在则返回-1。
  • contains(x): 判断序列中是否包含x。
  • reverse: 返回一个新序列,其中元素顺序被反转。
  • sorted: 返回一个新序列,其中元素按照默认顺序排序。
  • sortWith(f): 返回一个新序列,其中元素按照给定函数f定义的顺序排序。
  • sortBy(f): 返回一个新序列,其中元素按照给定函数f映射后得到值进行排序。

2.4.1创建Seq集合

参考代码

object Demo {
  def main(args: Array[String]): Unit = {
    //创建Seq集合, 存储元素1, 2, 3, 4, 5.
    val s1 = (1 to 5).toSeq
    val s2 = Seq(1,2,3,4,5)
    //2. 打印结果.
    println(s1)
    println(s2)
  }
}

2.4.2 获取长度及元素length()或者size()方法

因为Seq集合的每个元素都是有索引的, 所以我们可以通过索引直接获取其对应的元素, 而且可以通过length()或者size()方法获取集合的长度.

参考代码

object Demo {
  def main(args: Array[String]): Unit = {
    //创建Seq集合, 存储元素1, 2, 3, 4, 5.
    val s1 = (1 to 5).toSeq
    //打印集合的长度.
    println(s1.length, s1.size)
    println("-" * 15)

    //获取索引为2的元素.
    println(s1(2))
    println(s1.apply(2))
  }
}

2.4.3 获取指定元素的索引值indexOf(), lastIndexOf(), indexWhere(), lastIndexWhere(),indexOfSlice()方法

在Scala中,有一些方法可以用来在序列(Seq)中查找元素或子序列的位置。这些方法有:

  • indexOf(elem: A): Int:返回序列中第一个等于给定元素elem的索引,如果没有找到则返回-1。
  • lastIndexOf(elem: A): Int:返回序列中最后一个等于给定元素elem的索引,如果没有找到则返回-1。
  • indexWhere(p: A => Boolean): Int:返回序列中第一个满足谓词函数p的元素的索引,如果没有找到则返回-1。
  • lastIndexWhere(p: A => Boolean): Int:返回序列中最后一个满足谓词函数p的元素的索引,如果没有找到则返回-1。
  • indexOfSlice(that: Seq[A]): Int:返回序列中第一个包含给定子序列that的位置,如果没有找到则返回-1。
  • lastIndexOfSlice(that: Seq[A]): Int:返回序列中最后一个包含给定子序列that的位置,如果没有找到则返回-1。

这些方法都可以接受一个可选的参数end: Int,用来指定搜索的结束位置。例如,indexOf(elem, end)表示从序列头部开始,到索引为end - 1的位置结束,查找第一个等于给定元素的位置。类似地,lastIndexOf(elem, end)表示从索引为end - 1的位置开始,到序列尾部结束,查找最后一个等于给定元素的位置。

下面是一些例子:

// 创建一个字符串序列
val s = Seq("Scala", "is", "a", "functional", "programming", "language")

// 查找"Scala"在序列中的位置
s.indexOf("Scala") // 返回0

// 查找"Java"在序列中的位置
s.indexOf("Java") // 返回-1

// 查找长度大于2的第一个单词在序列中的位置
s.indexWhere(_.length > 2) // 返回0

// 查找长度大于2的最后一个单词在序列中的位置
s.lastIndexWhere(_.length > 2) // 返回5

// 查找"a"在前四个单词中的位置
s.indexOf("a", 4) // 返回2

// 查找"a"在后四个单词中的位置
s.lastIndexOf("a", 4) // 返回3

// 查找"fun"在序列中的位置
s.indexOfSlice(Seq("fun")) // 返回3
// 查找“Scala”, “is”在序列中的位置
s.indexOfSlice(Seq("Scala", "is")) //返回0
//子序列可以是任意的序列,只要它是原序列的一部分。Seq(“fun”)是Seq(“functional”)的一部分,所以它也是一个子序列,Seq(“Scala”, “is”)也是一个子序列.
// 查找"ing"在序列中的位置
s.lastIndexOfSlice(Seq("ing")) // 返回4

修改指定的元素updated(), patch()方法

在Scala中,有一些方法可以用来更新序列(Seq)中的元素或子序列。这些方法有:

  • updated(index: Int, elem: A): Seq[A]:返回一个新的序列,其中在给定索引index处的元素被替换为给定元素elem
  • patch(from: Int, that: Seq[A], replaced: Int): Seq[A]:返回一个新的序列,其中从给定索引from开始的replaced个元素被替换为给定序列that

这些方法都是不可变的,也就是说,它们不会修改原始的序列,而是返回一个新的序列。如果要更新可变序列(如ArrayBuffer),可以使用update(index: Int, elem: A)patchInPlace(from: Int, that: IterableOnce[A], replaced: Int)方法,它们会直接修改原始的序列。

下面是一些例子:

// 创建一个整数序列
val s = Seq(1, 2, 3, 4, 5)

// 更新第二个元素为10
val s1 = s.updated(1, 10) // 返回Seq(1, 10, 3, 4, 5)

// 替换第三个和第四个元素为6和7
val s2 = s.patch(2, Seq(6, 7), 2) // 返回Seq(1, 2, 6, 7, 5)

// 在第三个位置插入8和9
val s3 = s.patch(2, Seq(8, 9), 0) // 返回Seq(1, 2, 8, 9, 3, 4, 5)

// 删除第三个和第四个元素
val s4 = s.patch(2, Nil, 2) // 返回Seq(1, 2, 5)

3. Stack

3.1 概述

表示数据结构, 元素特点是先进后出. 由于历史的原因, Scala当前的库中还包含一个immutable.Stack, 但当前已经被标记为弃用, 因为它的设计不怎么优雅, 而且性能也不太好, 因为栈会涉及到大量元素的进出, 所以不可变栈的应用场景还是比较小的, 最常用的还是可变栈, 例如: mutable.Stack, mutable.ArrayStack.

3.2 图解

把元素1, 2, 3添加到栈中, 图解如下:

在这里插入图片描述

3.3 可变Stack

可变Stack是指可以修改其内容的Stack。我们可以使用scala.collection.mutable.Stack来创建和操作可变Stack。

创建可变Stack

我们可以使用以下语法来创建一个空的或带有初始元素的可变Stack:

import scala.collection.mutable.Stack
var s = Stack[type]() // 创建一个空的Stack
var s = Stack(val1, val2, val3, ...) // 创建一个带有初始元素的Stack

例如:

import scala.collection.mutable.Stack
// 创建一个空的整数Stack
var s1 = Stack[Int]()
// 创建一个带有初始元素的字符串Stack
var s2 = Stack("Scala", "Java", "Python")

操作可变Stack

一旦创建了可变Stack,我们可以使用以下方法来操作它:

  • push(elem: A): Unit:将给定元素elem压入栈顶。
  • pop(): A:从栈顶弹出并返回一个元素。
  • top: A:返回栈顶的元素,但不弹出。
  • isEmpty: Boolean:检查Stack是否为空。
  • size: Int:返回Stack中元素的个数。
  • clear(): Unit:清空Stack中的所有元素。
  • pushAll(elems: IterableOnce[A]): Stack.this.type: 它可以将一个集合中的所有元素压入栈顶。
  • mutable.ArrayStack集合独有的方法为:
    • dup(duplicate缩写): 可以用来复制栈顶元素.
    • preserving: 该方法会执行一个表达式, 在表达式执行完毕后恢复栈, 即: 栈的内容和调用前一致.

例如:

import scala.collection.mutable.Stack
// 创建一个整数Stack
var s = Stack(1, 2, 3)
// 压入一个元素
s.push(4) // s变为Stack(4, 1, 2, 3)
// 弹出并返回一个元素
val x = s.pop() // x为4,s变为Stack(1, 2, 3)
// 返回栈顶的元素,但不弹出
val y = s.top // y为1,s不变
// 检查Stack是否为空
val b = s.isEmpty // b为false,s不变
// 返回Stack中元素的个数
val n = s.size // n为3,s不变
// 清空Stack中的所有元素
s.clear() // s变为空Stack()

import scala.collection.mutable.Stack
// 创建一个整数Stack
var s = Stack(1, 2, 3)
// 创建一个整数集合
var c = Seq(4, 5, 6)
// 将集合中的所有元素压入栈顶
s.pushAll(c) // s变为Stack(6, 5, 4, 1, 2, 3)
// 再压入一个元素
s.push(7) // s变为Stack(7, 6, 5, 4, 1, 2, 3)

3.4 不可变Stack

不可变Stack是指不能修改其内容的Stack。我们可以使用scala.collection.immutable.Stack来创建和操作不可变Stack。

创建不可变Stack

我们可以使用以下语法来创建一个空的或带有初始元素的不可变Stack:

import scala.collection.immutable.Stack
val s = Stack[type]() // 创建一个空的Stack
val s = Stack(val1, val2, val3, ...) // 创建一个带有初始元素的Stack

例如:

import scala.collection.immutable.Stack
// 创建一个空的整数Stack
val s1 = Stack[Int]()
// 创建一个带有初始元素的字符串Stack
val s2 = Stack("Scala", "Java", "Python")

操作不可变Stack

一旦创建了不可变Stack,我们可以使用以下方法来操作它:

  • push(elem: A): Stack[A]:返回一个新的Stack,其中在栈顶压入了给定元素elem
  • pop(): (A, Stack[A]):返回一个二元组,其中第一个元素是从栈顶弹出的元素,第二个元素是弹出后剩余的新的Stack。
  • top: A:返回栈顶的元素,但不弹出。
  • isEmpty: Boolean:检查Stack是否为空。
  • size: Int:返回Stack中元素的个数。

例如:

import scala.collection.immutable.Stack
// 创建一个整数Stack
val s = Stack(1, 2, 3)
// 压入一个元素
val s1 = s.push(4) // s1为Stack(4, 1, 2, 3),s不变
// 弹出并返回一个元素和剩余的Stack
val (x, s2) = s.pop() // x为1,s2为Stack(2, 3),s不变
// 返回栈顶的元素,但不弹出
val y = s.top // y为1,s不变
// 检查Stack是否为空
val b = s.isEmpty // b为false,s不变
// 返回Stack中元素的个数
val n = s.size // n为3,s不变

3.5ArrayStack

ArrayStack是一种可变的Stack,它使用数组作为底层的数据结构。它提供了快速的索引,通常比普通的可变Stack更高效。

创建ArrayStack

我们可以使用以下语法来创建一个空的或带有初始元素的ArrayStack:

import scala.collection.mutable.ArrayStack
var s = ArrayStack[type]() // 创建一个空的ArrayStack
var s = ArrayStack(val1, val2, val3, ...) // 创建一个带有初始元素的ArrayStack

例如:

import scala.collection.mutable.ArrayStack
// 创建一个空的整数ArrayStack
var s1 = ArrayStack[Int]()
// 创建一个带有初始元素的字符串ArrayStack
var s2 = ArrayStack("Scala", "Java", "Python")

操作ArrayStack

一旦创建了ArrayStack,我们可以使用以下方法来操作它:

  • push(elem: A): Unit:将给定元素elem压入栈顶。
  • pop(): A:从栈顶弹出并返回一个元素。
  • top: A:返回栈顶的元素,但不弹出。
  • isEmpty: Boolean:检查ArrayStack是否为空。
  • size: Int:返回ArrayStack中元素的个数。
  • clear(): Unit:清空ArrayStack中的所有元素。
  • apply(index: Int): A:返回在给定索引index处的元素。
  • dup(): Unit:复制栈顶的元素,并压入栈顶。
  • preserving(expr: => Unit): Unit:执行一个表达式,在表达式执行完毕后恢复栈,即栈的内容和调用前一致。

例如:

    import scala.collection.mutable.ArrayStack
    // 创建一个整数ArrayStack
    var s = ArrayStack(1, 2, 3)
    // 压入一个元素
    s.push(4) // s变为ArrayStack(4, 1, 2, 3)
    // 弹出并返回一个元素
    val x = s.pop() // x为4,s变为ArrayStack(1, 2, 3)
    // 返回栈顶的元素,但不弹出
    val y = s.top // y为1,s不变
    // 检查ArrayStack是否为空
    val b = s.isEmpty // b为false,s不变
    // 返回ArrayStack中元素的个数
    val n = s.size // n为3,s不变
    // 清空ArrayStack中的所有元素
    s.clear() // s变为空ArrayStack()

    s.push(1)
    s.push(2)
    s.push(3) //ArrayStack(3, 2, 1)

    // 返回在第二个位置的元素
    val z = s(1) // z为2,s不变

    // 复制栈顶的元素,并压入栈顶
    s.dup() // s变为ArrayStack(3, 3, 2, 1)

    // 执行一个表达式,在表达式执行完毕后恢复栈
    s.preserving({
      s.clear(); println("看看我执行了吗!")
    }) // 打印"看看我执行了吗!",然后s恢复为ArrayStack(3, 3, 2, 1)

4. Queue

4.1 概述

Queue是一种数据结构,它遵循先进先出(FIFO)的原则。我们只能从一端(称为队首)删除元素,从另一端(称为队尾)添加元素。Scala有不可变和可变的两种版本的Queue。我们常用的队列是可变队列: mutable.Queue, 它内部是以MutableList数据结构实现的.

4.2 图解

把元素1, 2, 3添加到队列中, 图解如下:

在这里插入图片描述

4.3 可变Queue

可变Queue是指可以修改其内容的Queue。我们可以使用scala.collection.mutable.Queue来创建和操作可变Queue。

创建可变Queue

我们可以使用以下语法来创建一个空的或带有初始元素的可变Queue:

import scala.collection.mutable.Queue
var q = Queue[type]() // 创建一个空的Queue
var q = Queue(val1, val2, val3, ...) // 创建一个带有初始元素的Queue

例如:

import scala.collection.mutable.Queue
// 创建一个空的整数Queue
var q1 = Queue[Int]()
// 创建一个带有初始元素的字符串Queue
var q2 = Queue("Scala", "Java", "Python")

操作可变Queue

一旦创建了可变Queue,我们可以使用以下方法来操作它:

  • +=(elem: A): Queue.this.type:将给定元素elem添加到队尾。
  • ++=(elems: TraversableOnce[A]): Queue.this.type:将给定元素集合elems添加到队尾。
  • dequeue(): A:从队首删除并返回一个元素。
  • front: A:返回队首的元素,但不删除。
  • isEmpty: Boolean:检查Queue是否为空。
  • size: Int:返回Queue中元素的个数。
  • clear(): Unit:清空Queue中的所有元素。

例如:

import scala.collection.mutable.Queue
// 创建一个整数Queue
var q = Queue(1, 2, 3)
// 在队尾添加一个元素
q += 4 // q变为Queue(1, 2, 3, 4)
// 在队尾添加多个元素
q ++= Seq(5, 6) // q变为Queue(1, 2, 3, 4, 5, 6)
// 从队首删除并返回一个元素
val x = q.dequeue // x为1,q变为Queue(2, 3, 4, 5, 6)
// 返回队首的元素,但不删除
val y = q.front // y为2,q不变
// 检查Queue是否为空
val b = q.isEmpty // b为false,q不变
// 返回Queue中元素的个数
val n = q.size // n为5,q不变
// 清空Queue中的所有元素
q.clear() // q变为空Queue()

4.4 不可变Queue

不可变Queue是指不能修改其内容的Queue。我们可以使用scala.collection.immutable.Queue来创建和操作不可变Queue。

创建不可变Queue

我们可以使用以下语法来创建一个空的或带有初始元素的不可变Queue:

import scala.collection.immutable.Queue
val q = Queue[type]() // 创建一个空的Queue
val q = Queue(val1, val2, val3, ...) // 创建一个带有初始元素的Queue

例如:

import scala.collection.immutable.Queue
// 创建一个空的整数Queue
val q1 = Queue[Int]()
// 创建一个带有初始元素的字符串Queue
val q2 = Queue("Scala", "Java", "Python")

操作不可变Queue

一旦创建了不可变Queue,我们可以使用以下方法来操作它:

  • enqueue(elem: A): Queue[A]:返回一个新的Queue,其中在队尾添加了给定元素elem
  • enqueue(elems: A*): Queue[A]:返回一个新的Queue,其中在队尾添加了给定元素序列elems
  • dequeue: (A, Queue[A]):返回一个二元组,其中第一个元素是从队首删除的元素,第二个元素是删除后剩余的新的Queue。
  • front: A:返回队首的元素,但不删除。
  • isEmpty: Boolean:检查Queue是否为空。
  • size: Int:返回Queue中元素的个数。

例如:

import scala.collection.immutable.Queue
// 创建一个整数Queue
val q = Queue(1, 2, 3)
// 在队尾添加一个元素
val q1 = q.enqueue(4) // q1为Queue(1, 2, 3, 4),q不变
// 在队尾添加多个元素
val q2 = q.enqueue(5, 6) // q2为Queue(1, 2, 3, 5, 6),q不变
// 从队首删除并返回一个元素和剩余的Queue
val (x, q3) = q.dequeue // x为1,q3为Queue(2, 3),q不变
// 返回队首的元素,但不删除
val y = q.front // y为1,q不变
// 检查Queue是否为空
val b = q.isEmpty // b为false,q不变
// 返回Queue中元素的个数
val n = q.size // n为3,q不变

5. Set

Set是一种数据结构,它可以存储不重复的元素。Set有以下特点:

  • Set中的元素是无序的,不能通过索引访问。
  • Set中的元素是唯一的,不能有重复的元素。
  • Set可以进行集合运算,如并集、交集、差集等。

5.1 Set的创建和操作

在scala中,可以使用Set()函数或者花括号{}来创建一个Set。例如:

// 使用Set()函数创建一个Set
val s1 = Set(1, 2, 3, 4)
// 使用花括号{}创建一个Set
val s2 = {3, 4, 5, 6}

Set支持以下操作:

  • 添加元素:使用+运算符或者++运算符可以向Set中添加元素。+运算符只能添加一个元素,++运算符可以添加多个元素。例如:
// 添加一个元素
val s3 = s1 + 5
// 添加多个元素
val s4 = s1 ++ Set(5, 6)
  • 删除元素:使用-运算符或者–运算符可以从Set中删除元素。-运算符只能删除一个元素,–运算符可以删除多个元素。例如:
// 删除一个元素
val s5 = s3 - 5
// 删除多个元素
val s6 = s4 -- Set(5, 6)
  • 清空元素:使用empty方法可以创建一个空的Set。例如:
// 创建一个空的Set
val s7 = Set.empty[Int]
  • 遍历元素:使用for循环或者foreach方法可以遍历Set中的元素。例如:
// 使用for循环遍历Set中的元素
for (x <- s1) {
  println(x)
}
// 使用foreach方法遍历Set中的元素
s1.foreach(println)
  • 检查元素:使用size方法可以获取Set中的元素个数,使用contains方法可以检查某个元素是否在Set中。例如:
// 获取Set中的元素个数
val n = s1.size
// 检查某个元素是否在Set中
val b = s1.contains(3)

5.2 Set的集合运算

Set可以进行以下集合运算:

  • 并集:使用union方法或者|运算符可以求两个Set的并集,即包含两个Set中所有的元素。例如:
// 求两个Set的并集
val s8 = Set(3, 4, 5, 6)
val s9 = s1 union s8
  • 交集:使用intersect方法或者&运算符可以求两个Set的交集,即包含两个Set中共有的元素。例如:
// 求两个Set的交集
val s10 = s1 intersect s8
  • 差集:使用diff方法或者&~运算符可以求两个Set的差集,即包含第一个Set中有而第二个Set中没有的元素。例如:
// 求两个Set的差集
val s11 = s1 diff s8
  • 子集:使用subsetOf方法可以检查一个Set是否是另一个Set的子集,即包含在另一个Set中的所有元素。例如:
// 检查一个Set是否是另一个Set的子集
val s12 = Set(1, 2)
val b2 = s12 subsetOf s1

5.3 Set的子类

5.3.1 分类

继承关系如下图:

在这里插入图片描述

HashSet

HashSet是一种数据结构,它可以存储不重复的元素。HashSet使用哈希表来存储元素,因此它具有高效的添加、删除和查找操作。HashSet有以下特点:

  • HashSet中的元素是无序的,不能通过索引访问。
  • HashSet中的元素是唯一的,不能有重复的元素。
  • HashSet可以进行集合运算,如并集、交集、差集等。
HashSet的定义格式

在scala中,可以使用以下格式来定义一个HashSet:

// 定义一个不可变的HashSet
val hashSet = scala.collection.immutable.HashSet[A]
// 定义一个可变的HashSet
var hashSet = scala.collection.mutable.HashSet[A]

其中,A是元素的类型,可以是任意类型,如Int, String, Person等。

HashSet的创建

在scala中,可以使用以下方法来创建一个HashSet:

// 使用HashSet()函数创建一个空的HashSet
val s1 = HashSet[Int]()
// 使用HashSet()函数创建一个非空的HashSet
val s2 = HashSet(1, 2, 3, 4)
// 使用花括号{}创建一个非空的HashSet
val s3 = {5, 6, 7, 8}
HashSet的方法操作

在scala中,可以使用以下方法来操作一个HashSet:

  • 添加元素:使用+运算符或者+=运算符可以向HashSet中添加元素。+运算符会返回一个新的HashSet,+=运算符会修改原来的HashSet。例如:
// 使用+运算符添加一个元素
val s4 = s2 + 5
// 使用+=运算符添加一个元素
s2 += 6
  • 删除元素:使用-运算符或者-=运算符可以从HashSet中删除元素。-运算符会返回一个新的HashSet,-=运算符会修改原来的HashSet。例如:
// 使用-运算符删除一个元素
val s5 = s3 - 5
// 使用-=运算符删除一个元素
s3 -= 6
  • 清空元素:使用clear方法可以清空HashSet中的所有元素。例如:
// 清空HashSet中的所有元素
s2.clear()
  • 遍历元素:使用for循环或者foreach方法可以遍历HashSet中的元素。例如:
// 使用for循环遍历HashSet中的元素
for (x <- s1) {
  println(x)
}
// 使用foreach方法遍历HashSet中的元素
s1.foreach(println)
  • 检查元素:使用size方法可以获取HashSet中的元素个数,使用contains方法可以检查某个元素是否在HashSet中。例如:
// 获取HashSet中的元素个数
val n = s1.size
// 检查某个元素是否在HashSet中
val b = s1.contains(3)
  • 集合运算:使用union方法或者|运算符可以求两个HashSet的并集,使用intersect方法或者&运算符可以求两个HashSet的交集,使用diff方法或者&~运算符可以求两个HashSet的差集。例如:
// 求两个HashSet的并集
val s6 = s2 union s3
// 求两个HashSet的交集
val s7 = s2 intersect s3
// 求两个HashSet的差集
val s8 = s2 diff s3

LinkedHashSet和HashSet都是Set的子类,它们都可以存储不重复的元素,但是它们有一些不同的点:

  • HashSet是一个无序的Set,它不保证元素的插入顺序。而LinkedHashSet是一个有序的Set,它保持元素的插入顺序。
  • HashSet内部使用HashMap来存储元素,它使用哈希函数来计算元素的位置。而LinkedHashSet内部使用LinkedHashMap和双向链表来存储元素,它使用链表来维护元素的顺序。
  • HashSet的添加、删除和查找操作的时间复杂度是O(1),它比LinkedHashSet更高效。而LinkedHashSet的添加、删除和查找操作的时间复杂度也是O(1),但是它比HashSet稍慢,因为它需要维护链表。
  • HashSet和LinkedHashSet都可以进行集合运算,如并集、交集、差集等。
  • HashSet和LinkedHashSet都可以存储一个null元素。

总之,如果你不需要保持元素的插入顺序,而且想要更高效的操作,你可以使用HashSet。如果你需要保持元素的插入顺序,而且不介意稍慢的操作,你可以使用LinkedHashSet。

TreeSet

TreeSet是一种数据结构,它可以存储不重复的元素。TreeSet使用红黑树来存储元素,因此它具有自动排序的功能。TreeSet有以下特点:

  • TreeSet中的元素是有序的,可以按照元素的自然顺序或者指定的比较器来排序元素。
  • TreeSet中的元素是唯一的,不能有重复的元素。
  • TreeSet可以进行集合运算,如并集、交集、差集等。
TreeSet的定义格式

在scala中,可以使用以下格式来定义一个TreeSet:

// 定义一个不可变的TreeSet
val treeSet = scala.collection.immutable.TreeSet[A]
// 定义一个可变的TreeSet
var treeSet = scala.collection.mutable.TreeSet[A]

其中,A是元素的类型,可以是任意类型,如Int, String, Person等。如果元素的类型实现了Comparable接口,那么TreeSet会按照元素的自然顺序来排序。如果元素的类型没有实现Comparable接口,或者想要使用自定义的排序规则,那么需要提供一个比较器(Comparator)来指定如何比较元素。

TreeSet的创建

在scala中,可以使用以下方法来创建一个TreeSet:

// 使用TreeSet()函数创建一个空的TreeSet
val s1 = TreeSet[Int]()
// 使用TreeSet()函数创建一个非空的TreeSet
val s2 = TreeSet(1, 2, 3, 4)
// 使用花括号{}创建一个非空的TreeSet
val s3 = {5, 6, 7, 8}
// 使用自定义的比较器创建一个非空的TreeSet
val s4 = TreeSet(1, 2, 3, 4)((x, y) => y - x) // 按照降序排序
TreeSet的方法操作

在scala中,可以使用以下方法来操作一个TreeSet:

  • 添加元素:使用+运算符或者+=运算符可以向TreeSet中添加元素。+运算符会返回一个新的TreeSet,+=运算符会修改原来的TreeSet。例如:
// 使用+运算符添加一个元素
val s5 = s2 + 5
// 使用+=运算符添加一个元素
s2 += 6
  • 删除元素:使用-运算符或者-=运算符可以从TreeSet中删除元素。-运算符会返回一个新的TreeSet,-=运算符会修改原来的TreeSet。例如:
// 使用-运算符删除一个元素
val s6 = s3 - 5
// 使用-=运算符删除一个元素
s3 -= 6
  • 清空元素:使用clear方法可以清空TreeSet中的所有元素。例如:
// 清空TreeSet中的所有元素
s2.clear()
  • 遍历元素:使用for循环或者foreach方法可以遍历TreeSet中的元素。例如:
// 使用for循环遍历TreeSet中的元素
for (x <- s1) {
  println(x)
}
// 使用foreach方法遍历TreeSet中的元素
s1.foreach(println)
  • 检查元素:使用size方法可以获取TreeSet中的元素个数,使用contains方法可以检查某个元素是否在TreeSet中。例如:
// 获取TreeSet中的元素个数
val n = s1.size
// 检查某个元素是否在TreeSet中
val b = s1.contains(3)
  • 集合运算:使用union方法或者|运算符可以求两个TreeSet的并集,使用intersect方法或者&运算符可以求两个TreeSet的交集,使用diff方法或者&~运算符可以求两个TreeSet的差集。例如:
// 求两个TreeSet的并集
val s7 = s2 union s3
// 求两个TreeSet的交集
val s8 = s2 intersect s3
// 求两个TreeSet的差集
val s9 = s2 diff s3
  • 获取元素:使用head, last, min, max等方法可以获取TreeSet中的第一个或最后一个元素,或者最小或最大的元素。使用range, from, to等方法可以获取子集,或者使用slice方法来获取指定范围的元素。例如:
// 获取TreeSet中的第一个元素
val x = s1.head
// 获取TreeSet中的最后一个元素
val y = s1.last
// 获取TreeSet中的最小元素
val z = s1.min
// 获取TreeSet中的最大元素
val w = s1.max
// 获取TreeSet中从3到6的子集
val s10 = s1.range(3, 6)
// 获取TreeSet中从3开始的子集
val s11 = s1.from(3)
// 获取TreeSet中到6结束的子集
val s12 = s1.to(6)
// 获取TreeSet中第2到第4个元素
val s13 = s1.slice(2, 4)

6. Map

Map是一种数据结构,它可以存储键值对(key-value pair)。Map可以根据键来快速查找或更新对应的值。Map有以下特点:

  • Map中的键是唯一的,不能有重复的键。
  • Map中的值可以是任意类型,可以有重复的值。
  • Map可以进行映射运算,如获取、更新、删除键值对等。

6.1 Map的定义格式

在scala中,可以使用以下格式来定义一个Map:

// 定义一个不可变的Map
val map = scala.collection.immutable.Map[K, V]
// 定义一个可变的Map
var map = scala.collection.mutable.Map[K, V]

其中,K是键的类型,V是值的类型,可以是任意类型,如Int, String, Person等。

6.2 Map的创建

在scala中,可以使用以下方法来创建一个Map:

// 使用Map()函数创建一个空的Map
val m1 = Map[Int, String]()
// 使用Map()函数创建一个非空的Map
val m2 = Map(1 -> "one", 2 -> "two", 3 -> "three")
// 使用箭头符号->创建一个非空的Map
val m3 = 4 -> "four"

6.3 Map的方法操作

在scala中,可以使用以下方法来操作一个Map:

  • 获取值:使用get方法或者apply方法可以根据键来获取对应的值。get方法会返回一个Option类型的值,如果键存在,返回Some(value),如果键不存在,返回None。apply方法会直接返回值,如果键存在,返回value,如果键不存在,抛出异常。例如:
// 使用get方法获取值
val v1 = m2.get(1) // 返回Some("one")
val v2 = m2.get(4) // 返回None
// 使用apply方法获取值
val v3 = m2(1) // 返回"one"
val v4 = m2(4) // 抛出异常
  • 更新值:使用+运算符或者+=运算符可以向Map中添加或更新键值对。+运算符会返回一个新的Map,+=运算符会修改原来的Map。例如:
// 使用+运算符添加或更新键值对
val m4 = m2 + (4 -> "four") // 返回一个新的Map
// 使用+=运算符添加或更新键值对
m2 += (4 -> "four") // 修改原来的Map
  • 删除值:使用-运算符或者-=运算符可以从Map中删除键值对。-运算符会返回一个新的Map,-=运算符会修改原来的Map。例如:
// 使用-运算符删除键值对
val m5 = m2 - 3 // 返回一个新的Map
// 使用-=运算符删除键值对
m2 -= 3 // 修改原来的Map
  • 清空元素:使用clear方法可以清空Map中的所有元素。例如:
// 清空Map中的所有元素
m2.clear()
  • 遍历元素:使用for循环或者foreach方法可以遍历Map中的元素。例如:
// 使用for循环遍历Map中的元素
for ((k, v) <- m1) {
  println(k + " -> " + v)
}
// 使用foreach方法遍历Map中的元素
m1.foreach((k, v) => println(k + " -> " + v))
  • 检查元素:使用size方法可以获取Map中的元素个数,使用contains方法可以检查某个键是否在Map中。例如:
// 获取Map中的元素个数
val n = m1.size
// 检查某个键是否在Map中
val b = m1.contains(3)

6.4Map的子类

继承关系如下图:

在这里插入图片描述

Map有多个子类,它们都可以存储键值对,但是它们有一些不同的点:

  • HashMap是一个无序的Map,它使用哈希表来存储元素。它的特点是添加、删除和查找操作的时间复杂度是O(1),但是不保证元素的插入顺序
  • LinkedHashMap是一个有序的Map,它使用哈希表和双向链表来存储元素。它的特点是添加、删除和查找操作的时间复杂度也是O(1),而且保持元素的插入顺序
  • TreeMap是一个有序的Map,它使用红黑树来存储元素。它的特点是添加、删除和查找操作的时间复杂度是O(log n),而且按照元素的自然顺序或者指定的比较器来排序元素
  • ListMap是一个有序的Map,它使用链表来存储元素。它的特点是保持元素的插入顺序,但是添加、删除和查找操作的时间复杂度是O(n)

总之,如果你不需要保持元素的顺序,而且想要更高效的操作,你可以使用HashMap。如果你需要保持元素的插入顺序,而且不介意稍慢的操作,你可以使用LinkedHashMap。如果你需要按照元素的排序顺序来访问元素,而且不介意较慢的操作,你可以使用TreeMap。如果你需要保持元素的插入顺序,而且不需要频繁地添加、删除或查找元素,你可以使用ListMap

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值