本章要点:
- 若长度固定则使用Array,若长度有可能有变化则使用ArrayBuffer
- 提供初始值时不要使用new
- 用()来访问元素
- 用for(elem<- arr)来遍历元素
- 用for(elem<- arr if …) yeild …来将原数组转型为新数组
- Scala数组和Java数组可以互相操作,ArrayBuffer,使用scala.collection.JavaConversion中的转换函数。
3.1 定长数组
如果你需要一个长度不变的数组,可以使用Scala中的Array。例如:
scala> val nums = new Array[Int](10)
//整数数组的初始化值均为0
nums: Array[Int] = Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
scala> val s=new Array[String](3)
//字符串数组的初始化值均为null
s: Array[String] = Array(null, null, null)
//当你提供初始值时,就不用new和数组长度了
scala> val hi=Array("hello","liusong")
hi: Array[String] = Array(hello, liusong)
scala> hi.length
res2: Int = 2
scala> hi(1)
res3: String = liusong
scala> hi(0)="good morning"
scala> hi
//注意这里val定义的数组是可以修改的
res7: Array[String] = Array(good morning, liusong)
在JVM中,Scala的Array以Java数组方式实现。
3.2 变长数组
对于那种长度需要变化的数组,Scala中的数据结构为import scala.collection.mutable.ArrayBuffer
。
scala> import scala.collection.mutable.ArrayBuffer
import scala.collection.mutable.ArrayBuffer
scala> val b = ArrayBuffer[Int]()
//或者用new ArrayBuffer[Int]
b: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer()
scala> b+=1
//用+=在尾端添加元素
res8: b.type = ArrayBuffer(1)
scala> b+=(1,2,3,5)
//在尾端添加多个元素,用()括起来
res9: b.type = ArrayBuffer(1, 1, 2, 3, 5)
scala> val a =Array(7,8)
a: Array[Int] = Array(7, 8)
scala> b++=a
//用++=在尾端添加一个数组
res10: b.type = ArrayBuffer(1, 1, 2, 3, 5, 7, 8)
scala> b.trimEnd(4)
//移除最后4个元素
scala> b
res12: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 1, 2)
你也可以在任意位置插入或移除元素,但这样的操作不太高效(所有在那个位置之后的元素都必须平移)。
scala> b.insert(2,6) //在下标2之前插入
scala> b
res14: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 1, 6, 2)
scala> b.insert(2,3,4,5) //你可以插入任意多个元素
scala> b
res16: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 1, 3, 4, 5, 6, 2)
scala> b.remove(2) //移除指定下标的元素
res17: Int = 3
scala> b
res18: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 1, 4, 5, 6, 2)
scala> b.remove(2,3) //第二个参数的含义是你要移除多少个元素
scala> b
res20: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 1, 2)
有时你需要构建一个Array但不知道最终需要装多少个元素。在这种情况下,先构建一个ArrayBuffer,再调用toArray
;反之,调用toBuffer
可以将一个数组转换成数组缓冲。
scala> b.toArray //
res25: Array[Int] = Array(1, 1, 2)
scala> a
res21: Array[Int] = Array(7, 8)
scala> a.toBuffer
res22: scala.collection.mutable.Buffer[Int] = ArrayBuffer(7, 8)
3.3 遍历数组和数组缓冲
一下是for循环遍历数组或数组缓冲的语法:
scala> for (i<-0 until b.length) println(s"$i:${b(i)}")
0:1
1:1
2:2
until
方法和to
方法很像,只不过它排除了最后一个元素。因此i的取值为0到b.length-1。
一般来说
for(i<- 区间)
会让变量i遍历区间的所有值。
scala> 0 until 9
res26: scala.collection.immutable.Range = Range(0, 1, 2, 3, 4, 5, 6, 7, 8)
scala> 0 until 9 reverse
warning: there were 1 feature warning(s); re-run with -feature for details
res27: scala.collection.immutable.Range = Range(8, 7, 6, 5, 4, 3, 2, 1, 0)
scala> 0 until 9 by 2
res28: scala.collection.immutable.Range = Range(0, 2, 4, 6, 8)
scala> 0 until 9 by -1
res29: scala.collection.immutable.Range = Range()
使用守卫reverse或by -1可以从数组的尾部开始遍历
如果循环体中不需要用到数组下标,也可以像这样直接遍历数组元素:
scala> for (i<- b) print(i)
112
3.4 数组转换
从一个数组(或数组缓冲)出发,以某种方式对它进行转换是很容易的。这些转换动作不会修改原始数组,而是交出一个新的数组。
scala> val a= Array(2,3,4,5,6)
a: Array[Int] = Array(2, 3, 4, 5, 6)
scala> val result=for(i<-a) yield 2*i
result: Array[Int] = Array(4, 6, 8, 10, 12)
**for/yield循环创建里一个类型与原始集合相同的新集合。**如果从数组缓冲出发,那么你在for/yield之后得到的也是一个数组缓冲。
scala> val b = ArrayBuffer(9,8,7,6)
b: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(9, 8, 7, 6)
scala> for (i<-b) yield i*2
res3: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(18, 16, 14, 12)
当你遍历一个集合时,只想处理那些满足特定条件的元素。可以通过守卫:for中的if来实现。
scala> for(i<-b if i%2==0) yield i*2
res4: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(16, 12)
另一种函数式编程风格的做法如下:
scala> a
res5: Array[Int] = Array(1, 2, 3, 5)
scala> a.filter(_%2==0).map(2*_)
res7: Array[Int] = Array(4)
//甚至可以这样做
scala> a filter {_%2==0} map{2*_}
res8: Array[Int] = Array(4)
假定我们想从一个整数数组缓冲中移除所有的负数,传统的顺序执行方案可能是遍历数组缓冲,并在遇到不需要的元素时将它们移除。
var n=b.length
var i=0
while(i<n){
if (a(i)>=0) i+=1
else {a.remove(i); n-=1}
}
这有点小题大做了——你需要记住,在移除元素时不递增i,而是对n做递减操作。从数组缓冲中移除元素也并不高效。这个循环不必要地移动了后面会被移除的元素。
Scala中明显的解决方案是使用for/yield循环并保持所有的非负数元素:
scala> val b= ArrayBuffer(-1,8,3,-4,90)
b: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(-1, 8, 3, -4, 90)
scala> val result =for (i<-b if i>=0) yield i
result: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(8, 3, 90)
结果是一个新的数组缓冲。
假定我们需要修改原始数组缓冲,我们可以收集这些元素的位置,使用indices
方法;然后从后往前移除这些位置的元素:
scala> val positionToRemove=for(i<-b.indices if b(i)<0) yield i
positionToRemove: scala.collection.immutable.IndexedSeq[Int] = Vector(0, 3)
scala> for (i<-positionToRemove.reverse) b.remove(i)
scala> b
res2: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(8, 3, 90)
或者更好的办法,记住要保留的位置,将对应的元素(往前)复制,最后缩短数组缓冲:
scala> val b= ArrayBuffer(-1,8,3,-4,90)
b: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(-1, 8, 3, -4, 90)
scala> val positionToKeep=for (i<-b.indices if b(i)>=0) yield i
positionToKeep: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 2, 4)
scala> for (j<-positionToKeep.indices) b(j)=b(positionToKeep(j))
scala> b.trimEnd(b.length-positionToKeep.length)
scala> b
res5: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(8, 3, 90)
3.5 常用算法
也许你听过过,很大的比例的业务运算不过实在求和与排序。
sum
,数组的元素类型必须要是数值类型
scala> Array(1.7,Pi,34).sum
res15: Double = 38.8415926535897
min
和max
,交出数组或数组缓冲中最大或最小的元素
scala> ArrayBuffer("Mary","Lucy","Chris").max
res16: String = Mary
scala> ArrayBuffer("Mary","Lucy","Chris","little").max
res17: String = little
这里值得推敲,对于一个字符串数组缓冲,是根据ASCII值比较出最大值
sorted
方法,返回经过排序的数组或数组缓冲,不修改原始数组
scala> val bSorted=b.sorted
bSorted: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(3, 8, 90)
scala> b
res19: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(8, 3, 90)
sortWith
方法,你还可以提供一个比较函数,比如降序排列
scala> val bDescending=b.sortWith(_>_)
bDescending: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(90, 8, 3)
quickSort
函数,直接对数组进行排序
你可以直接对一个Array排序,但是不能直接对一个ArrayBuffer排序
scala> val a=Array(3,45,1,30)
a: Array[Int] = Array(3, 45, 1, 30)
scala> import scala.util.Sorting.quickSort
import scala.util.Sorting.quickSort
scala> quickSort(a)
scala> a
res22: Array[Int] = Array(1, 3, 30, 45)
scala> quickSort(b)
<console>:14: error: overloaded method value quickSort with alternatives:
(a: Array[Float])Unit <and>
(a: Array[Int])Unit <and>
[K](a: Array[K])(implicit evidence$1: scala.math.Ordering[K])Unit <and>
(a: Array[Double])Unit
cannot be applied to (scala.collection.mutable.ArrayBuffer[Int])
quickSort(b)
^
mkString
,将数组或数组缓冲转换成字符串,并可指定元素之间的分隔符
scala> a.mkString(" and ")
res26: String = 1 and 3 and 30 and 45
该方法的另一个重载版本,可以让你指定前缀和后缀。
scala> val c=Array(1,2,3,45,5)
c: Array[Int] = Array(1, 2, 3, 45, 5)
scala> c.mkString("<",",",">")
res37: String = <1,2,3,45,5>
注意:数组的toString方法是调用的Java的毫无意义的toString方法,而数组缓冲可以查看类型和元素:
scala> c.toString
res38: String = [I@27ada0e8
scala> b.toString
res39: String = ArrayBuffer(8, 3, 90)
3.6 解读Scaladoc
数组和数组缓冲有许多有用的方法,可以浏览Scala文档来获取这些信息。
- a.count(_>0) 清点有多少个大于0的元素
- b.append(1,2,3) 对b追加元素
- ++
scala> var d=b++"cd"
d: scala.collection.mutable.ArrayBuffer[AnyVal] = ArrayBuffer(8, 3, 90, c, d)
3.7 多维数组
和Java一样,Scala的多维数组是通过数组的数组来实现的。
要构造一个,Double的二维数组类型为Array[Array[Double]],可以用ofDim
方法:
scala> val matrix=Array.ofDim[Double](3,4)
matrix: Array[Array[Double]] = Array(Array(0.0, 0.0, 0.0, 0.0), Array(0.0, 0.0, 0.0, 0.0), Array(0.0, 0.0, 0.0, 0.0))
scala> matrix(2)(3)=23 //用两对括号访问数组的元素
scala> matrix
res47: Array[Array[Double]] = Array(Array(0.0, 0.0, 0.0, 0.0), Array(0.0, 0.0, 0.0, 0.0), Array(0.0, 0.0, 0.0, 23.0))
注意:这里有一个易踩的坑,多维数组访问,索引是从编号0开始
你可以创建不规则数组,每一行长度各不相同:
scala> val triangle=new Array[Array[Int]](4)
triangle: Array[Array[Int]] = Array(null, null, null, null)
scala> for (i<-triangle.indices) triangle(i)=new Array[Int](i+1)
scala> triangle
res52: Array[Array[Int]] = Array(Array(0), Array(0, 0), Array(0, 0, 0), Array(0, 0, 0, 0))
练习
- 编写一段代码,将a设置为一个n个随机整数的数组,要求随机数介于0和n之间
scala>
def mkarray(n:Int)={
val a =new Array[Int](n)
for (i<- 0 until n) a(i)=new scala.util.Random().nextInt(n)
a
}
mkarray: (n: Int)Array[Int]
scala> mkarray(9)
res61: Array[Int] = Array(7, 6, 5, 0, 4, 4, 2, 8, 8)
scala> mkarray(9)
res62: Array[Int] = Array(8, 5, 0, 7, 4, 6, 0, 1, 1)
scala> mkarray(9)
res63: Array[Int] = Array(7, 8, 5, 5, 3, 5, 1, 8, 1)
- 编写一个循环,将整数数组中相邻的元素置换。
scala> def substitution(a:Array[Int])={
| for (i<- 0 until (a.length-1) by 2) { val t=a(i); a(i)=a(i+1);a(i+1)=t}
| a
| }
scala> a
res94: Array[Int] = Array(2, 3, 3, 7, 0, 1, 0, 5, 5)
scala> substitution(a)
res95: Array[Int] = Array(3, 2, 7, 3, 1, 0, 5, 0, 5)
- ~~重复前一个练习,不过这一次生成一个新的值交换过的数组。使用for/yield。~
object App
{
def main(args: Array[String]) {
val a = Array(1, 2, 3, 4, 5);
val b = revertYield(a);
b.foreach(println);
}
def revertYield(arr : Array[Int]) = {
for (i <- 0 until arr.length) yield {
if (i < (arr.length - 1) && i % 2 == 0) {
val t = arr(i);
arr(i) = arr(i + 1);
arr(i + 1) = t;
}
arr(i);
}
}
}
- 给定一个整数数组,产出一个新的数组,包含数组中的所有正值,以原有数组顺序排列;之后的元素是所有零或负值,以原有顺序排列。
scala> def pone(a:Array[Int])={
| var pone= ArrayBuffer[Int]()
| pone ++=( for(i<-a if i>0) yield i)
| pone ++=( for(i<-a if i<=0) yield i)
| pone}
pone: (a: Array[Int])scala.collection.mutable.ArrayBuffer[Int]
scala> b
res4: Array[Int] = Array(-959, 491, 308, 895, 359, 1325, -511)
scala> pone(b)
res5: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(491, 308, 895, 359, 1325, -959, -511)
- 如何计算Array[Double]的平均值
scala> def mkarray(n:Int)={
val a =new Array[Double](n)
for (i<- 0 until n) a(i)=new scala.util.Random().nextDouble()*10
a
}
mkarray: (n: Int)Array[Double]
scala> val b=mkarray(6)
b: Array[Double] = Array(5.209708976240694, 6.883409231622068, 3.6504482299879424, 6.975258597309519, 8.3932468226679, 8.49206155876563)
scala> mean(b)
scala> def mean(a:Array[Double]) ={
a.sum/a.length
}
mean: (a: Array[Double])Double
scala> mean(b)
res130: Double = 6.600688902765626
- 如何重新组织Array[Int] 的元素,将它们反序排列
def arrReverse(a:Array[Int])={
for (i<-0 until a.length/2) {
val t=a(i)
a(i)=a(a.length-i-1)
a(a.length-i-1)=t
}
a
}
scala> arrReverse(b)
res15: Array[Int] = Array(-511, 1325, 359, 895, 308, 491, -959)
scala> arrReverse(b)
res15: Array[Int] = Array(-511, 1325, 359, 895, 308, 491, -959)
- 编写一段代码,产出数组中的所有值,去掉重复项
scala> val a=mkarray(9)
a: Array[Int] = Array(2, 8, 3, 2, 2, 8, 6, 8, 5)
scala> a.distinct
res0: Array[Int] = Array(2, 8, 3, 6, 5)
- 去掉数组缓冲中,去除负数元素,除第一个之外
scala> def delunfirstF(a:Array[Int])={
| val n=for (i<-a.indices if a(i)<0) yield i
| val remv= n.reverse.dropRight(1)
| val temp=a.toBuffer
| for(index<-remv) temp.remove(index)
| temp
| }
delunfirstF: (a: Array[Int])scala.collection.mutable.Buffer[Int]
scala> a
res17: Array[Int] = Array(2, 8, -10, 2, 2, 8, -9, 8, 5)
scala> delunfirstF(a)
res18: scala.collection.mutable.Buffer[Int] = ArrayBuffer(2, 8, -10, 2, 2, 8, 8, 5)
- 创建一个由java.util.TimeZone.getAvailableIDs返回的时区集合,判断条件是它们在美洲。去掉"America/"前缀并反序。
scala> var timezones=ArrayBuffer[String]()
timezones: scala.collection.mutable.ArrayBuffer[String] = ArrayBuffer()
scala> timezones ++=java.util.TimeZone.getAvailableIDs()
res22: scala.collection.mutable.ArrayBuffer[String] = ArrayBuffer(Africa/Abidjan, Africa/Accra, Africa/Addis_Ababa, Africa/Algiers, Africa/Asmara, Africa/Asmera, Africa/Bamako, Africa/Bangui, Africa/Banjul, Africa/Bissau, Africa/Blantyre, Africa/Brazzaville, Africa/Bujumbura, Africa/Cairo, Africa/Casablanca, Africa/Ceuta, Africa/Conakry, Africa/Dakar, Africa/Dar_es_Salaam, Africa/Djibouti, Africa/Douala, Africa/El_Aaiun, Africa/Freetown, Africa/Gaborone, Africa/Harare, Africa/Johannesburg, Africa/Juba, Africa/Kampala, Africa/Khartoum, Africa/Kigali, Africa/Kinshasa, Africa/Lagos, Africa/Libreville, Africa/Lome, Africa/Luanda, Africa/Lubumbashi, Africa/Lusaka, Africa/Malabo, Africa/Maputo, Africa/Maseru, Africa/Mbabane, Africa/Mogadishu, Africa/Monrovia, Africa/Nairobi, Africa/Ndjamena, ...
scala> timezones=timezones.filter(_.take(8)=="America/")
mutated timezones
scala> timezones
res23: scala.collection.mutable.ArrayBuffer[String] = ArrayBuffer(America/Adak, America/Anchorage, America/Anguilla, America/Antigua, America/Araguaina, America/Argentina/Buenos_Aires, America/Argentina/Catamarca, America/Argentina/ComodRivadavia, America/Argentina/Cordoba, America/Argentina/Jujuy, America/Argentina/La_Rioja, America/Argentina/Mendoza, America/Argentina/Rio_Gallegos, America/Argentina/Salta, America/Argentina/San_Juan, America/Argentina/San_Luis, America/Argentina/Tucuman, America/Argentina/Ushuaia, America/Aruba, America/Asuncion, America/Atikokan, America/Atka, America/Bahia, America/Bahia_Banderas, America/Barbados, America/Belem, America/Belize, America/Blanc-Sablon, America/Boa_Vista, America/Bogota, America/Boise, America/Buenos_Aires, America/Cambridge_Bay, Ameri...
scala> timezones=timezones.map(_.drop(8)).sorted
mutated timezones
scala> timezones
res24: scala.collection.mutable.ArrayBuffer[String] = ArrayBuffer(Adak, Anchorage, Anguilla, Antigua, Araguaina, Argentina/Buenos_Aires, Argentina/Catamarca, Argentina/ComodRivadavia, Argentina/Cordoba, Argentina/Jujuy, Argentina/La_Rioja, Argentina/Mendoza, Argentina/Rio_Gallegos, Argentina/Salta, Argentina/San_Juan, Argentina/San_Luis, Argentina/Tucuman, Argentina/Ushuaia, Aruba, Asuncion, Atikokan, Atka, Bahia, Bahia_Banderas, Barbados, Belem, Belize, Blanc-Sablon, Boa_Vista, Bogota, Boise, Buenos_Aires, Cambridge_Bay, Campo_Grande, Cancun, Caracas, Catamarca, Cayenne, Cayman, Chicago, Chihuahua, Coral_Harbour, Cordoba, Costa_Rica, Creston, Cuiaba, Curacao, Danmarkshavn, Dawson, Dawson_Creek, Denver, Detroit, Dominica, Edmonton, Eirunepe, El_Salvador, Ensenada, Fort_Nelson, Fort_Wayn...
- 引入java.awt.datatransfer.并构建一个类型为SystemFlavorMap类型的对象:
val flavors = SystemFlavorMap.getDefaultFlavorMap().asInstanceOf[SystemFlavorMap] 然后以DataFlavor.imageFlavor为参数调用getNativesForFlavor方法,以Scala缓冲保存返回值。 (为什么用这样一个晦涩难懂的类?因为在Java标准库中很难找到使用java.util.List的代码)
scala> val flavors = SystemFlavorMap.getDefaultFlavorMap().asInstanceOf[SystemFlavorMap]
flavors: java.awt.datatransfer.SystemFlavorMap = java.awt.datatransfer.SystemFlavorMap@1e1f808c
scala> println(flavors.getNativesForFlavor(DataFlavor.imageFlavor).toArray.toBuffer.mkString(" | "))
image/png | image/x-png | image/jpeg | image/gif | PNG | JFIF