Skip to content

Files

Latest commit

590e50c · Jan 11, 2022

History

History
664 lines (499 loc) · 24.8 KB

06.md

File metadata and controls

664 lines (499 loc) · 24.8 KB

六、其它集合类型

Scala 有很多有用的集合类型。最基本的是数组和列表,但是如果我们想快速实现各种算法,我们通常会使用其他数据类型,例如堆栈、队列和映射。在本节中,我们将了解一些其他有用的集合类型。

A Stack 是后进先出数据结构。我们使用push功能向Stack 添加物品,使用pop功能移除物品。项目弹出的顺序与推送的顺序相反。例如,如果我们推送值 1、2 和 3,Stack 将在弹出项目时返回 3、2、1。

A Queue 与 a Stack类似,也只允许两次操作。对于一个Queue,两个操作分别是enqueuedequeue。我们使用enqueueQueue添加项目,使用dequeue移除项目。A Queue 按照与enqueued相同的顺序返回项目。A Queue 有时被称为 FIFO 数据结构,是先进先出的简称。例如,如果我们enqueue项 1,2,然后 3,a Queue 将按照相同的顺序dequeue它们:1,2,然后 3。

代码清单 50 显示了使用Stack的一些基本操作,代码清单 51 显示了使用Queue的类似操作。请注意,当我们使用这些数据结构(以及许多其他数据结构)时,我们需要包含一个导入,以便从适当的库中导入该类。

代码清单 50:栈的基本操作

  import scala.collection.mutable.Stack

  object MainObject {
         def main(args: Array[String]): Unit = {

                // Define a stack
                var myStack = new Stack[Int]

                // Push a new item to the stack:
                myStack.push(89)
                println("Number of items: " +
  myStack.length)
                println("Item at the top of the stack: " +
  myStack.top)

                // Push a new item to the stack:
                myStack.push(21)
                println("Number of items: " +
  myStack.length)
                println("Item at the top of the stack: " +
  myStack.top)

                // Pop off the newest item:
                var itemFromStack = myStack.pop
                println("Popped item: " +
  itemFromStack)
                println("Number of items: " +
  myStack.length)
                println("Item at the top of the stack: " +
  myStack.top)

                // Push a new item to the stack:
                myStack.push(44)
                println("Number of items: " +
  myStack.length)
                println("Item at the top of the stack: " +
  myStack.top)

                // Pop off all remaining items:
                // Note: This is a stack, so the
  items are popped
                // off in reverse order! 
                while(myStack.length != 0)
                       println("Popped item: " +
  myStack.pop)
         }
  >}

代码清单 51:队列的基本操作

  import scala.collection.mutable.Queue

  object MainObject {
         def main(args: Array[String]): Unit = {

                // Define a queue:
                var myQueue = new Queue[Int]

                // Add an item to the queue:
                myQueue.enqueue(47)
                println("Number of items: " +
  myQueue.length)
                println("Item at the front of the queue: " +
  myQueue.front)

                // Add another item to the queue:
                myQueue.enqueue(83)
                println("Number of items: " +
  myQueue.length)
                println("Item at the front of the queue: " +
  myQueue.front)

                // Remove the oldest item from the
  queue:
                var itemFromStack = myQueue.dequeue
                println("Dequeued item: " +
  itemFromStack)
                println("Number of items: " +
  myQueue.length)
                println("Item at the front of the queue: " +
  myQueue.front)

                // Add an item to the queue:
                myQueue.enqueue(23)
                println("Number of items: " +
  myQueue.length)
                println("Item at the front of the queue: " +
  myQueue.front)

                // Loop until the queue is empty:
                // Note this is a queue, so items
  will be dequeued
                // in the same order they were
  queued!
                while(myQueue.length != 0)
                       println("Dequeued item: " +
  myQueue.dequeue)
         }
  }

集合是一种只包含不同元素的集合类型。集合是具有相同名称的数学实体的表示。它们被设计成允许与数学集合相同的操作——除了数学集合可以被定义为包含无限多个项目,而 Scala 集合包含有限个元素。代码清单 52 显示了集合的一些基本操作。

代码清单 52:集合运算

  object MainObject {
         def main(args: Array[String]): Unit = {

                // Define a Set
                val evenNumbers = Set(2,
  4, 6,
  8, 10, 12, 14)

                // Print out some properties:
                println("Head: " + evenNumbers.head)
                println("Tail: " + evenNumbers.tail)
                println("IsEmpty: " + evenNumbers.isEmpty)

                // Testing if the set contains 3:
                if(evenNumbers.contains(3))
                       println("Set contains 3!")
                else
                       println("Set does not contain 3...")

                // Test if the set contains 2:
                if(evenNumbers.contains(2))
                       println("Set contains 2!")
                else
                       println("Set does not contain 2...")
         }
  }

为了将两个集合连接在一起,我们使用 ++ 运算符( ++ 运算符形成两个集合的数学并集)。在代码清单 52 中,我们将一个包含(1, 2, 3)的集合与另一个包含(3, 4, 5)的集合连接起来。当我们运行代码清单 52 中的程序时,注意输出显示包含(5, 1, 2, 3, 4)set3 。还要注意的是,虽然set1set2 都包含3,但是串联的集合只包含一个3的副本。代码清单 53 还显示,我们可以分别使用+操作符轻松添加和移除项目。

代码清单 53:添加和移除元素

  object MainObject {
         def main(args: Array[String]): Unit = {

         // Define some sets:
         var set1 = Set(1,
  2, 3)
         var set2 = Set(3,
  4, 5)

         // Concatenate with ++ operator:
         var set3 = set1 ++ set2

         // Output the concatenated set:
         println("Set3: " + set3)

         // Adding items to a set:
         println("Set containing an extra 10: " +

                (set3 + 10))

         // Removing items from a set:
         println("Joined set without the 3's: " +

                ((set1 ++ set2) - 3))
         }
  }
  > 

集合被设计成允许快速的项目查找。但是集合中元素的顺序是没有意义的——请注意,当我们运行代码清单 53 中的程序时,项目的最终顺序(5、1、2、3、4)与我们在原始集合中指定的项目顺序不同(实际上,根据您安装的特定 Java Runtime 的实现,我的机器中的顺序可能与您的不同)。这是因为集合的实现采用了散列技术。如果您的集合中元素的顺序很重要,您不应该使用集合,或者您应该使用 Scala SortedSet集合。但是,如果您知道集合中的每个元素都是唯一的,并且您想要快速的项目查找,那么Set是完美的。

可变集合

默认情况下,我们不能在集合中添加和移除项目——它们是不可变的(这意味着元素都是固定的)。代码清单 53 展示了如何添加和删除项目,但是这个例子实际上创建了一个新的集合,它没有从不可变的集合中添加和删除项目。如果要在不创建新集合的情况下从集合中添加和移除项目,请通过导入 scala.collections.mutable.set 来使用可变集合(这意味着元素不是固定的,我们可以自由更改它们)。为了向可变集合添加项目,我们可以使用 + 运算符,而要移除项目,我们可以使用 运算符。还要注意,当我们创建一个可变集时,我们使用 var someName = Set[dataType]() 来创建,其中 dataType 是该集包含的数据类型, someName 是我们想要用于该集的标识符。

代码清单 54:从集合中添加和移除项目

  import scala.io.StdIn.readInt
  import scala.collection.mutable.Set

  object MainObject {
         def main(args: Array[String]): Unit = {
                // Create a mutable set:
                var setOfInts = Set[Int]()
                var newNumber = 0

                while(newNumber != -1)
  {
                       // Print a prompt:
                       print("Input a number (use -1 to quit): ")

                       // Read a new number:
                       newNumber = readInt

                       // If the set contains the new
  number, remove it:
                       if(setOfInts.contains(newNumber))
                             setOfInts = setOfInts - newNumber

                       // Otherwise, add it (if not -1):

  else if (newNumber != -1)

  setOfInts = setOfInts + newNumber

                       // Print out the items in the set
  so far:
                       println("Set contains: " +
  setOfInts)
                }
         }
  >}

代码清单 54 显示了一个程序,它使用集合来测试一个有趣的现象,叫做“生日悖论”用来证明这一现象的问题是:*在至少两个人共享一个生日之前,一个房间平均需要多少人?*代码清单 54 中的程序使用一组整数,我们从这些整数中反复生成随机生日,直到出现重复。此时,我们记录到目前为止生成的生日数量,将其添加到总数中,然后重复。实验按照迭代变量指定的次数重复——我已经将这个变量设置为 1,000,000。我们重复的迭代次数越多,我们就越有可能在两个或更多的人共享一个生日之前找到一个房间里我们需要的实际平均人数。

生日悖论实际上并不是一个悖论,但令人惊讶的是,在两个人共享一个生日之前,房间里需要的人是如此之少。该程序还演示了查找项目集的速度。有 100 万次试验,该程序可能会在一两秒钟内在任何现代台式电脑上完成。每次试验都包含对一个包含许多元素的集合的多次查找。

代码清单 55:生日悖论测试器

  import scala.collection.mutable.Set

  object MainObject {
         def main(args: Array[String]): Unit = {
                // Define a mutable set:
                var birthdays = Set[Int]()

                // Define how many trials to run:
                var iterations = 1000000

                // Set total to 0 birthdays
  counted so far:
                var totalBirthdays = 0.0

                println("Beginning trials...")

                // Repeat the experiment up to
  iterations times:
                for(i
  <- 1 to iterations) {

                       // Reset the birthdays:
                       var duplicateDetected = false
                       birthdays.clear
                       while(!duplicateDetected) {

                             // Generate a new random birthday:
                             val newBirthday = (Math.random() *
  365.0).toInt

                             // Check if the birthday exists in
  the set or not:
                             if(birthdays.contains(newBirthday)) {
                                    totalBirthdays += birthdays.size.toDouble
                                    duplicateDetected = true
                             }
                             else {
                                    // Add the birthday to the set:
                                    birthdays += newBirthday
                             }
                       }
                }

                // Output the total and average
  number of days: 
                println("Total birthays: " +
  totalBirthdays)
                println("Average birthdays before duplicate: " +
  (totalBirthdays /
  iterations))
         }
  }

与数学集合一样,Scala 中的集合允许我们通过从两个集合中选择相交的项目或从集合之间不共享的项目中选择来形成新的集合。还要注意,我们可以使用“或”运算符|,而不是用++运算符连接集合。有关&|,&~运算符的示例,请参见代码清单 56。

代码清单 56:集合上类似集合的操作

  import scala.collection.mutable.Set

  object MainObject {
         def main(args: Array[String]): Unit = {

                // Define two sets:
                var set1 = Set(1,
  5, 4,
  6, 9)
                var set2 = Set(5,
  3, 7,
  1, 6)

                // Output the shared elements:
                // Note: & operator is the
  same as: set1.intersects(set2)
                println("Shared elements: " +
  (set1 & set2))

                // Using | combines all elements:
                println("All Elements: " +
  (set1 |
  set2))

                // Using &~ filters to items
  not shared between sets: 
                // Note: &~ is the same as:
  set1.diff(set2)
                println("Elements in set1, not in set2: " +
  (set1 &~ set2))
                println("Elements in set2, not in set1: " +
  (set2 &~ set1))
         }
  }

我们还可以过滤和计数与特定布尔表达式匹配的集合中的元素。代码清单 57 显示了一个使用过滤器函数的例子。

代码清单 57:计算过滤集中的元素

  object MainObject {
         def main(args: Array[String]): Unit = {

                // Define a set:
                val mySet = Set(1,
  5, 4,
  6, 9)

                // Filtering:
                println("Number of odd elements in mySet: " +
                       mySet.count(x => x % 2
  == 1))
                println("Number of even elements in mySet: " +
                       mySet.count(x => x % 2
  == 0))

                // For these operations, we can
  also create new sets,
                // instead of just counting
  elements:
                val evenNumbers = mySet.filter { x => x % 2
  == 0
  } 
                val oddNumbers = mySet.filter { x => x % 2
  == 1
  }
                println("Set of Even Elements: " +
  evenNumbers)
                println("Set of Odd Elements: " +
  oddNumbers)
         }
  }

套装功能强大,用途广泛,这一定是对它们的简单介绍。有关更多信息,请参考位于【http://docs.scala-lang.org/overviews/collections/sets.html】的 set 类的 Scala 文档。

元组

A Tuple是可以是不同类型的对象的集合,我们可以作为一个单一的实体传递和使用。这与其他集合不同,例如Array,它包含的对象都具有相同的类型。Tuples对很多事情都很有用,包括从一个函数返回多个值——我们可以传递一个Tuple并修改它的值作为多个返回值,而不是实际定义一个具有多个返回值的函数。

代码清单 58:定义元组

  object MainObject {
         def main(args: Array[String]): Unit = {

                // Verbose syntax for tuple of 3
  elements:
                val tupleSlow = new Tuple3(2, "Banana", 2.6)

                // Quick syntax for tuple of 3
  elements:
                val tupleQuick = (1, "Pineapple", 3.5)

                // Many element tuple:
                val oneOne = (1, 1,
  "was", 'a', "racehorse", 
                       2, 2, "was", 1, 2,
  1, 1,
  1, 1,
  "race", 2,
                       2, 1, 1,
  2)
         }
  >}

代码清单 58 显示了三个Tuples的定义。第一个例子展示了详细的语法,其中我们使用new操作符并定义Tuple,就像我们定义任何其他对象一样,即调用构造函数并传递参数。

第二个例子展示了Tuples更简单的语法。我们可以省略new Tuple3,只需在括号中指定参数列表。

最后一个例子使用了快速语法,但是Tuple 有很多元素。在撰写本文时,最新版本的 Scala 可以包含 1 到 22 个元素。

Tuple的数据类型由传递给构造函数的项隐含。故行**newTuple3(2**、** "Banana" 2.6 ) 将创建一个带有数据类型的TupleIntStringDouble。同样,最后一个示例使用数据类型(IntIntStringChar、…、StringIntIntIntIntInt)创建了一个 20 元素Tuple。**

**### 访问元组的元素

代码清单 59:访问元组元素

  object MainObject {
         def main(args: Array[String]): Unit = {

                // Define two complex numbers as tuples:
                var complexNumberA = (1.5, 7.8)
                var complexNumberB = (2.6, 5.1)

                // Multiply them together to get
  complex product:
                var complexProduct = (
                       complexNumberA._1 * complexNumberB._1 -

                       complexNumberA._2 * complexNumberB._2,
                       complexNumberA._1 * complexNumberB._2 +

                       complexNumberA._2 * complexNumberB._1
                       )

                // Output results:
                println("Complex product of " +
  complexNumberA +
  " and " +
  complexNumberB +

                       " is " +
  complexProduct)
         }
  }

代码清单 59 显示了一个访问元组元素的例子。元素编号从 1 到 N,其中 N 是Tuple中的项目数。请注意,我们将两个复数定义为 Tuple2 对象,然后将它们相乘以产生复数乘积。还要注意使用 complexNumberA._1 来访问 complexNumberA 的第一个元素。

当我们向控制台打印一个Tuple时,Scala 会将元素用括号括起来,作为一个逗号分隔的列表。所以,当我们打印 complexNumberA 时,Scala 会输出 (1.5, 7.8)

代码清单 60 显示了一个使用foreach迭代Tuple中的项目的例子。该示例将把Tuple 的元素分配给变量x,并将每个元素打印在单独的一行上。

代码清单 60:迭代元组的元素

  object MainObject {
         def main(args: Array[String]): Unit = {

                // Define a tuple:
                val tuple5 = ("One", 2,
  3.0f, 4.0, '5')

                // Output elements by iterating
  over tuple:
                println("Elements of tuple: ")          
                tuple5.productIterator.foreach { x
  => println(x)
  }
         }
  >}

| | 注意:访问像这样的元组元素可能看起来很尴尬。_1.如果你想知道为什么我们不能使用语法 suchAndSuch(1),那是因为(和)括号隐式定义了一个函数,函数需要有一些特定的返回类型——它们不能返回元组中的每一个可能的类型。 |

命名元组的元素

我们可以给一个Tuple ,的元素命名,然后用名称而不是索引来引用它们。代码清单 61 显示了一个命名Tuple元素的例子。

代码清单 61:命名元组的元素

  object MainObject {
         def main(args: Array[String]): Unit = {

                // Define a tuple:
                val point3D = (-9.5, 5.6, 7.2)

                // Name the elements of the tuple:
                val (x,
  y, z)
  = point3D

                // Print out the elements using
  names:
                println("Element x: " + x)
                println("Element y: " + y)
                println("Element z: " + z)
         }
  }

请注意,在代码清单 61 中,名称xyz指的是名为point3DTuple的元素。这不是一般的Tuples元素命名方法,只是一个具体Tuple元素的命名方法。

二元元组快捷方式

代码清单 62 显示了创建 Tuple2 对象的简写。我们使用 point2D 定义中的语法“element1 - > element2”。请注意,我们无法通过这种方式创建 Tuple3valpoint3D=-9.5->5.6->7.2里面其实又多了一个 Tuple2 :()****

**代码清单 62:tuple 2 的简写

  object MainObject {
         def main(args: Array[String]): Unit = {
                // Short hand for two element
  tuple:
                val point2D = -9.5 -> 5.6

                // Be careful, the following is
  not a Tuple3!
                val point3D = -9.5 -> 5.6 -> 7.2

                // Print out the tuples:
                println(point2D)
                println(point3D)
         }
  }

映射和元组

Tuples 最常见的一种用法是搭配 Maps 。A Map是键/值对的集合,这意味着Tuple2是完美的。 Maps 有时被称为映射或关联;它们表示键到值的映射。

Maps 有两种口味:不可变和可变。默认值是不可变,为了使用可变地图,您应该使用 import scala.collection.mutable.map 。代码清单 63 展示了一些如何使用不可变的Map的例子。请注意,一旦创建了不可变的Map,项目就被固定了。

代码清单 63:不可变映射

  object MainObject {
         def main(args: Array[String]): Unit = {

                // Immutable map:
                val staff = Map(1 -> "Tom", 2 -> "Tim", 3 -> "Jenny")
                val staff2 = Map(10 -> "Geoff", 7 -> "Sara")

                // Print out some info the staff
  map:
                println("Keys: " + staff.keys)
                println("Values: " + staff.values)
                println("IsEmpty: " + staff.isEmpty)

                // Concatenate two maps with the
  ++ operator:
                val staffConcat = staff ++ staff2
                println("All staff: " + staffConcat.values)

                // Access values by key:
                println("Element with key 1: " +
  staffConcat(1))
                println("Element with key 7: " +
  staffConcat(7))
                // The following will throw an
  exception because the key
                // does not exist:
                // println("Element
  with key 12: " + staffConcat(12))

                // To check if a key exists:
                if(staffConcat.contains(12))
                       println("Element with key 12: " +
  staffConcat(12))
                else
                       println("Element with key 12: Does not exist!")

                // Removing elements by key:
                val timGotFired = staffConcat -
  2 // 2 is the key for Tim 
                // Now timGotFired will be the
  same as staffConcat, but Tim has
                // been removed:
                println(timGotFired)
         }
  }

| | 注意:和集合一样,Scala 的映射非常有用和快速。有很多操作可供他们使用,感兴趣的读者可以看看http://docs.scala-lang.org/overviews/collections/maps.html了解更多信息。 |

可变地图

可变Maps本质上和不可变Map是一样的,除了我们可以在每次不创建新地图的情况下添加和移除项目。

代码清单 64:可变映射

  import scala.collection.mutable.Map

  object MainObject {
         def main(args: Array[String]): Unit = {

                // Create a new map object:
                val staff: Map[Int, String] = Map()

                // Adding tuples (key/value pairs)
  to a map with +=
                staff += (5 -> "Teddy")
                staff += (1 -> "Rene")
                staff += new Tuple2(3, "Ronnie")

                // Print out some info on the map:
                println("Keys: " + staff.keys)
                println("Values: " + staff.values)
                println("IsEmpty? " + staff.isEmpty)

                // To remove an item by key:
                staff -= 5
                println(staff) // Teddy
  got fired!

                staff += (5 -> "Dean") // Dean took Teddy's old key.

                // We are not able to add multiple
  items with the
                // same key so the following is
  illegal:
                // staff += (5, "Teddy")

                // Iterating through a map:
                for(i
  <- staff.keys) {
                       println("Staff Member ID: " +
  i +
  " -> " +
  staff(i))

                }
                // To set map elements, we use
  map(x)=xyz
                for(i
  <- staff.keys) {
                       staff(i)
  = "Teddy" 
                }

                println(staff)
         }
  }

代码清单 64 展示了可变映射的使用。唯一真正的区别是,Mutuable 映射可以添加项目和更改键的值。此外,注意Maps的操作与集合的操作相同,因为地图的键是Set

Scala 中还有许多其他类型的集合。每一个都有不同的实现,并为不同类型的数据和算法而设计。感兴趣的读者可以访问页面http://docs . Scala-lang . org/overview/collections/concrete-mutatable-collection-class . html了解更多可用集合类型的信息。****