2. Scala 高阶语法之集合与元组

背景

上一章简单介绍了scala是什么,以及scala的基础用法,本文介绍scala的高阶语法,希望看完本章之后,读者能体会到scala和java的明显区别,以及scala的强大之处。

1. 数组

Scala中提供了一种数据结构-数组,其中存储相同类型的元素的固定大小的连续集合。如果需要类似于Java中的ArrayList这种长度可变的集合类,则可以使用ArrayBuffer。

scala 复制代码
package com.wanlong

import Array._;
import scala.collection.mutable.ArrayBuffer;

object TestArrayAndArrayBuffer {

  /**
   * Scala中提供了一种数据结构-数组,其中存储相同类型的元素的固定大小的连续集合。
   * ■数组用于存储数据的集合,但它往往是更加有用认为数组作为相同类型的变量的集合。
   * ■数组的第一个元素的索引是数字0和最后一个元素的索引为元素的总数减去1。
   * Scala中,Array代表的含义与Java中的类似,也是长度不可改变的数组。
   * Scala数组的底层实际上是Java数组,因为Scala和Java都是在JVM中运行的,双方可以互相调用。
   * Scala中,如果需要类似于Java中的ArrayList这种长度可变的集合类,则可以使用ArrayBuffer。
   */

  //定义变长数组
  val c: ArrayBuffer[Int] = scala.collection.mutable.ArrayBuffer[Int]();
  /**
   * += 追加元素 ++=追加集合 -=删除元素 --=删除集合
   * trimEnd删除末尾元素 trimStart删除开头元素
   * insert插入元素 remove删除元素 toArray转换为数组
   * sum求和 max求最大值 min求最小 reverse反转数组
   *
   */
  c += 1; //产生新的数组
  c += (2, 3, 4, 5);
  //  c-=3;
  //  c--=Array(5,4);
  //  //移除最后5个元素
  //  c.trimEnd(5);
  //  //第0个位置开始删除5个元素
  //  c.trimStart(5);
  //  //第0个位置插入6
  //  c.insert(0,6);
  //  //第0个开始删除5个元素
  //  c.remove(0,5);
  //  //删除第0个元素
  //  c.remove(0);
  //排序
  scala.util.Sorting.quickSort(c.toArray);
  //  数组转化为字符串
  c.mkString(" and ");
  c.mkString("<", ",", ">")
  //<1,2,3,4,5>
  //数组的遍历
  for (i <- 0 to c.length - 1) println(c(i));
  for (i <- 0 until c.length) println(c(i));
  for (i <- c) println(i);
  //数组的常用方法
  //sum求和
  c.sum;


  def main(args: Array[String]): Unit = {
    val arr = Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    println(c.mkString("<", ",", ">"))
  }
}

2. 元组

在Scala中,元组(Tuple)是一种固定长度的异构数据集合。元组可以包含不同类型的元素,且其长度在编译时是固定的。Scala支持不同长度的元组,最多可以包含22个元素。元组的元素通过位置进行访问,而不是通过名称。

2.1 创建元组

scala 复制代码
val pair = (1, "one")   // 这是一个二元组
val triplet = (1, "one", 3.0) // 这是一个三元组

2.2 访问元组元素

元组的元素通过._1、._2、._3等访问,其中数字表示元素的位置。例如:

scala 复制代码
val pair = (1, "one")
val first = pair._1  // 结果是:1
val second = pair._2 // 结果是:"one"

2.3 元组的类型

每个元组都有一个类型,该类型由元组中每个元素的类型组成。例如,(Int, String)是一个包含Int和String的二元组类型。

2.4 空元组

Scala中有一个特殊的元组类型,称为空元组(Unit),用()表示。它实际上与Unit类型是同义的,通常用于需要返回类型但不需要返回实际值的情况。

2.5 元组在函数返回多个值中的应用

由于Scala函数只能返回一个值,因此可以使用元组来返回多个值。例如:

scala 复制代码
def findMinMax(nums: List[Int]): (Int, Int) = {
  if (nums.isEmpty) throw new IllegalArgumentException("Cannot find min and max of an empty list")
  val (min, max) = nums.foldLeft((Int.MaxValue, Int.MinValue)) {
    case ((currentMin, currentMax), num) =>
      val newMin = if (num < currentMin) num else currentMin
      val newMax = if (num > currentMax) num else currentMax
      (newMin, newMax)
  }
  (min, max)
}

val nums = List(3, 1, 4, 1, 5, 9, 2)
val (min, max) = findMinMax(nums)
println(s"Min: $min, Max: $max")

元组在Scala中是一种非常有用的数据结构,它们可以用于多种场景,包括返回多个值、临时存储数据等。然而,对于更复杂的数据结构或需要命名字段的情况,建议使用case class或其他自定义类型

3. 集合

Scala有一个非常通用,丰富,强大,可组合的集合库;集合是高阶的(high level)并暴露了一大套操作方法。Scala的所有的集合类都可以在包 scala.collection 包中找到,其中集合类都是高级抽象类或特性。

  1. IterableT 是所有可遍历的集合,它提供了迭代的方法(foreach)。
  2. SeqT是有序集合
  3. SetT是数学上的集合(无序且不重复)
  4. MapT是关联数组,也是无序的。

Scala 集合类系统地区分了可变的和不可变的集合。可变集合可以在适当的地方被更新或扩展,意味着你可以修改、添加、移除一个集合的元素。而不可变集合类,相比之下,永远不会改变。不过,你仍然可以模拟添加,移除或更新操作。但是这些操作将在每一种情况下都返回一个新的集合,同时使原来的集合不发生改变。

可变的集合类位于 scala.collection.mutable包中,而不可变的集合位于scala.collection.immutable ,scala.collection 包中的集合,既可以是可变的,也可以是不可变的。

集合主要继承体系图如下:

3.1 List

在Scala中,List是一种常用的集合类型,它表示一个有序的、不可变的元素序列。与Java中的ArrayList或LinkedList不同,Scala的List是不可变的,这意味着一旦创建,你就不能更改其内容(例如,不能添加、删除或修改元素)。不过,你可以通过操作来生成新的List。或者使用可变的ListBuffer(不推荐)

3.1.1 创建List

  1. 使用:::操作符将元素添加到列表的头部(注意,这是右结合的,所以1 ::: 2 ::: Nil等同于1 :: (2 :: Nil))。
  2. 使用List(elem1, elem2, ...)语法。
  3. 使用Nil表示空列表(但在实际使用中,你通常会直接使用List()或List.empty)
scala 复制代码
val list1 = 1 :: 2 :: 3 :: Nil  // 结果是:List(1, 2, 3)
val list2 = List(4, 5, 6)       // 结果是:List(4, 5, 6)

3.1.2 访问List元素

  1. 使用head方法获取列表的第一个元素。
  2. 使用tail方法获取除第一个元素之外的其余元素(返回一个新的列表)。
  3. 使用isEmpty方法检查列表是否为空。
scala 复制代码
val list = List(1, 2, 3)
val head = list.head  // 结果是:1
val tail = list.tail  // 结果是:List(2, 3)

3.1.3 List的操作

  1. ::操作符用于在列表的头部添加元素(创建新列表)。
  2. ++操作符用于连接两个列表(创建新列表)。
  3. reverse方法用于反转列表。
  4. map、filter、foldLeft、foldRight等高阶函数用于对列表进行转换和归约操作
scala 复制代码
val newList = 0 :: list  // 结果是:List(0, 1, 2, 3)
val combinedList = list1 ++ list2  // 结果是:List(1, 2, 3, 4, 5, 6)
val reversedList = list.reverse  // 结果是:List(3, 2, 1)

3.1.4 可变List(不推荐)

虽然Scala的List是不可变的,但你可以使用scala.collection.mutable.MutableList或scala.collection.mutable.ListBuffer来实现可变列表。然而,在大多数情况下,最好坚持使用不可变的数据结构,因为它们提供了更好的安全性和并发性能。

scala 复制代码
  val list6=scala.collection.mutable.ListBuffer[Int]();
  list6+=1; //ListBuffer(1)
  list6+=(2,3,4,5) // ListBuffer(1,2,3,4,5)
  list6++=List(6,7,8,9) // ListBuffer(1,2,3,4,5,6,7,8,9)
  list6-=1; // ListBuffer(2,3,4,5,6,7,8,9)
  list6--=List(2,3,4,5) // ListBuffer(6,7,8,9)

  list6.toList //List(6,7,8,9)  可变转不可变
  list6.toArray //Array(6,7,8,9)  可变转数组
  list6.foreach(println) //遍历
  for (elem <- list6) {
    println(elem)
  }

  println(list6.isEmpty)
  println(list6.head)
  println(list6.tail)
  println(list6.tail.head)

3.2 Set

在Scala中,Set是一个表示元素集合的不可变数据结构,其中每个元素都是唯一的。与List不同,Set不保证元素的顺序,并且不允许有重复元素。Scala提供了几种不同类型的Set实现,但最常用的是HashSet(基于哈希表的实现)和TreeSet(基于红黑树的实现,元素按自然顺序或提供的比较器排序)

3.2.1 创建Set

  1. 使用Set(elem1, elem2, ...)语法创建包含指定元素的集合。

  2. 使用HashSet或TreeSet伴生对象的apply方法来创建特定类型的集合(虽然通常不需要显式指定,因为Scala会根据上下文自动推断)。

    scala 复制代码
    val set1 = Set(1, 2, 3)  // 创建一个HashSet
    val set2 = TreeSet(1, 3, 2)  // 创建一个TreeSet,元素将按排序顺序存储

注意:在Scala 2.13及更高版本中,直接使用Set()将创建一个HashSet。如果你想要一个TreeSet,你需要显式地调用TreeSet()。

3.2.2. 基本操作:

  • 使用+操作符向集合中添加一个元素(返回一个新的集合,因为Set是不可变的)。
  • 使用-操作符从集合中移除一个元素。
  • 使用contains方法检查集合中是否包含某个元素。
  • 使用size方法获取集合中元素的数量。
  • 使用isEmpty方法检查集合是否为空。
scala 复制代码
val newSet = set1 + 4  // 创建一个包含1, 2, 3, 4的新集合
val smallerSet = newSet - 2  // 创建一个不包含2的新集合
val containsThree = set1.contains(3)  // 结果是:true

3.2.3. 集合操作:

  1. 使用&操作符或intersect方法获取两个集合的交集。
  2. 使用|操作符或union方法获取两个集合的并集。
  3. 使用&~操作符或diff方法获取两个集合的差集(在第一个集合中但不在第二个集合中的元素)。
scala 复制代码
val anotherSet = Set(3, 4, 5)
val intersection = set1 & anotherSet  // 结果是:Set(3)
val union = set1 | anotherSet  // 结果是:Set(1, 2, 3, 4, 5)
val difference = set1 &~ anotherSet  // 结果是:Set(1, 2)

3.2.4. 可变Set:

  • 虽然Scala的Set是不可变的,但你可以使用scala.collection.mutable.Set或scala.collection.mutable.HashSet/TreeSet来实现可变集合。然而,在函数式编程范式中,建议坚持使用不可变集合。

3.2.5. 转换:

  1. 可以使用toList、toArray等方法将Set转换为其他类型的集合。
  2. 使用toSeq可以将其转换为Seq,这在某些情况下很有用,因为Seq提供了更丰富的操作集。

3.2.6. 元素类型:

  • Set中的元素类型必须是可哈希的(对于HashSet)或可比较的(对于TreeSet,如果你想要自然的排序顺序)。所以不能将函数或不可哈希的对象直接放入HashSet中,除非提供了自定义的哈希函数。

3.3 Map

在Scala中,Map 是一种用于存储键值对(key-value pairs)的集合类型。每个键在 Map 中都是唯一的,并且与一个值相关联。Scala 提供了几种 Map 的实现,但最常用的是 HashMap(基于哈希表的实现,提供了快速的查找、插入和删除操作)和 TreeMap(基于红黑树的实现,键按自然顺序或提供的比较器排序)。

以下是一些关于 Scala Map 的关键点:

3.3.1. 创建 Map:

  1. 使用 Map(key1 -> value1, key2 -> value2, ...) 语法创建包含指定键值对的映射。
  2. 使用 HashMap 或 TreeMap 伴生对象的 apply 方法来创建特定类型的映射(虽然通常不需要显式指定,因为 Scala 会根据上下文自动推断)。
scala 复制代码
val map1 = Map("a" -> 1, "b" -> 2, "c" -> 3)  // 创建一个HashMap
val map2 = TreeMap(1 -> "one", 2 -> "two", 3 -> "three")  // 创建一个TreeMap,键将按排序顺序存储

注意:在 Scala 2.13 及更高版本中,直接使用 Map() 将创建一个 HashMap。如果你想要一个 TreeMap,你需要显式地调用 TreeMap()。

3.3.2. 基本操作:

  1. 使用 get(key) 方法根据键查找值。如果键不存在,则返回 Option.empty(即 None)。
  2. 使用 contains(key) 方法检查映射中是否包含指定的键。
  3. 使用 + 操作符或 updated(key, value) 方法向映射中添加或更新键值对(返回一个新的映射,因为 Map 是不可变的)。
  4. 使用 - 操作符或 removed(key) 方法从映射中移除指定的键(返回一个新的映射)。
  5. 使用 size 方法获取映射中键值对的数量。
  6. 使用 isEmpty 方法检查映射是否为空。
scala 复制代码
val value = map1.get("b")  // 结果是:Some(2)
val containsA = map1.contains("a")  // 结果是:true
val newMap = map1 + ("d" -> 4)  // 创建一个包含新键值对的新映射
val smallerMap = newMap - "c"  // 创建一个不包含键"c"的新映射

3.3.3. 遍历 Map:

  1. 使用 foreach 方法遍历映射中的每个键值对。
  2. 使用 keys 方法获取映射中所有键的集合。
  3. 使用 values 方法获取映射中所有值的集合。
  4. 使用 toSeq 方法将映射转换为一个包含键值对的序列,然后可以使用 for 循环进行遍历。
scala 复制代码
map1.foreach { case (key, value) => println(s"$key: $value") }
val keys = map1.keys  // 获取所有键
val values = map1.values  // 获取所有值
for ((key, value) <- map1.toSeq) {
  println(s"$key: $value")
}

3.3.4. 可变 Map:

虽然 Scala 的 Map 是不可变的,但可以使用 scala.collection.mutable.Map、scala.collection.mutable.HashMap 或 scala.collection.mutable.TreeMap 来实现可变映射。然而,在函数式编程范式中,通常建议坚持使用不可变映射,除非有特定的理由需要可变性。

3.3.5. 转换:

  1. 可以使用 toList 方法将 Map 转换为一个包含键值对的列表。
  2. 使用 toMap(虽然这听起来有些多余,但在某些情况下,例如从另一个类型的映射转换时,可能是有用的)。
  3. 使用 toSeq 可以将其转换为 Seq,这在某些情况下很有用,因为 Seq 提供了更丰富的操作集。

3.3.6. 默认值和部分应用函数:

  1. Map 提供了 getOrElse(key, defaultValue) 方法,它允许在键不存在时返回一个默认值。
  2. 还可以使用 withDefaultValue(defaultValue) 方法创建一个新的映射,该映射在键不存在时返回指定的默认值(注意,这仅对之后添加到映射中的操作有效,对已经存在的键没有影响)。

4. zip

在Scala中,zip方法是一个非常有用的集合操作,它允许你将两个集合的元素按照相同的索引位置配对组合成一个新的集合。这个新集合的元素类型是元组(Tuple),每个元组包含来自原始集合的对应位置的元素。

zip方法通常用于两个长度相同的集合,但如果集合长度不同,结果集合的长度将与较短的集合相同,多余的元素将被忽略。

4.1. 基本用法:

当你对两个集合调用zip方法时,它会返回一个新的集合,其中每个元素都是一个包含两个原始集合对应位置元素的二元组。

scala 复制代码
val list1 = List(1, 2, 3)
val list2 = List("a", "b", "c")
val zipped = list1.zip(list2)  // 结果是:List((1,a), (2,b), (3,c))

4.2. 不同长度的集合

如果两个集合的长度不同,zip方法将只配对到较短集合的末尾。

scala 复制代码
val shortList = List(1, 2)
val longList = List("a", "b", "c", "d")
val zippedWithDifferentLengths = shortList.zip(longList)  // 结果是:List((1,a), (2,b))

4.3. 与其他集合类型的组合

zip方法不仅限于列表(List),它还可以用于其他集合类型,如数组(Array)、序列(Seq)等。

scala 复制代码
val array1 = Array(1, 2, 3)
val array2 = Array("x", "y", "z")
val zippedArrays = array1.zip(array2)  // 结果是:Array((1,x), (2,y), (3,z))

4.4. 与Option的zip

Scala的Option类型也有一个zip方法,它允许你将两个Option值组合成一个包含两个值的Some(如果两个Option都是Some),或者如果任何一个Option是None,则结果是None。

scala 复制代码
val opt1: Option[Int] = Some(1)
val opt2: Option[String] = Some("a")
val zippedOptions = opt1.zip(opt2)  // 结果是:Some((1,a))

val noneOpt: Option[Int] = None
val zippedWithNone = opt1.zip(noneOpt)  // 结果是:None

4.5. 自定义配对函数

虽然zip方法默认创建二元组,但你可以通过zipWithIndex方法获得每个元素及其索引的配对,或者通过zipWith方法提供自定义的配对函数。

scala 复制代码
val indexedList = list1.zipWithIndex  // 结果是:List((1,0), (2,1), (3,2))

val addedList = list1.zipWith(list2)(_ + _.toString.length)  // 假设列表2的元素是字符串,这里计算数字加上字符串长度的和
// 结果可能是:List(2, 3, 4),假设"a"、"b"、"c"的长度都是1

注意:上面的zipWith示例中的lambda表达式(_ + _.toString.length)是一个简化的表示,实际上你可能需要更明确地指定参数类型或使用其他逻辑来处理不同类型的元素。

4.6. 结果集合的类型:

zip方法的结果集合的类型取决于输入集合的类型。如果输入是两个列表,结果也是一个列表;如果输入是两个数组,结果也是一个数组。结果集合中的元素类型将是包含两个原始集合元素类型的元组。

以上,如有错误,请不吝指正!

相关推荐
障碍的枫子2 分钟前
我的第一个Agent
开发语言·python·langchain
打工仔折腾 AI4 分钟前
260+异构同步链路实战:如何用语义化自动翻译破解CDC DDL同步痛点
人工智能·后端·python·自然语言处理·性能优化·langchain·机器翻译
Pniubi12 分钟前
区分agent中长期记忆和偏好记忆
开发语言·ai·智能体
松就是我9029819 分钟前
Agent工具调用实现原理
后端
我就是不信30 分钟前
Swift 方法派发机制详解
开发语言·ios·swift
CCCCCCCCharlie34 分钟前
深入解析I/O底层:文件描述符与缓冲区真相
linux·开发语言·c++·算法
学习的老炮36 分钟前
反汇编基础速记
后端
步行cgn38 分钟前
@RequestMapping 注解详解
java·后端·spring
骑着蜗牛撵大象32738 分钟前
设备监控数据存与查:Spring Boot 接入 TimescaleDB 超表的完整落地
android·spring boot·后端·springboot·时序数据库·es·设备监控
鶴哥只手遮天38 分钟前
Spring Boot 4.1.1 深度源码解析-第 15 章 · 条件注解体系:全部 `@ConditionalOnXxx` 的实现原理
java·后端·开源