Jes*_*per 11 arrays collections scala shuffle scala-collections
我有一个我想要随机排列的数组.在Java中,有一个方法Collections.shuffle()可以随机地重新排列List的元素.它也可以在数组上使用:
String[] array = new String[]{"a", "b", "c"};
// Shuffle the array; works because the list returned by Arrays.asList() is backed by the array
Collections.shuffle(Arrays.asList(array));
Run Code Online (Sandbox Code Playgroud)
我尝试在Scala数组上使用它,但Scala解释器回答了一个冗长的答案:
scala> val a = Array("a", "b", "c")
a: Array[java.lang.String] = Array(a, b, c)
scala> java.util.Collections.shuffle(java.util.Arrays.asList(a))
<console>:6: warning: I'm seeing an array passed into a Java vararg.
I assume that the elements of this array should be passed as individual arguments to the vararg.
Therefore I follow the array with a `: _*', to mark it as a vararg argument.
If that's not what you want, compile this file with option -Xno-varargs-conversion.
java.util.Collections.shuffle(java.util.Arrays.asList(a))
^
<console>:6: error: type mismatch;
found : Array[java.lang.String]
required: Seq[Array[java.lang.String]]
java.util.Collections.shuffle(java.util.Arrays.asList(a))
^
Run Code Online (Sandbox Code Playgroud)
到底发生了什么?我不想使用特殊标志(-Xno-varargs-conversion)编译我的代码,如果这是解决方案,只是因为这个.
那么,如何在Scala数组上使用Java的Collections.shuffle()?
在此期间我在Scala中编写了自己的shuffle方法:
// Fisher-Yates shuffle, see: http://en.wikipedia.org/wiki/Fisher–Yates_shuffle
def shuffle[T](array: Array[T]): Array[T] = {
val rnd = new java.util.Random
for (n <- Iterator.range(array.length - 1, 0, -1)) {
val k = rnd.nextInt(n + 1)
val t = array(k); array(k) = array(n); array(n) = t
}
return array
}
Run Code Online (Sandbox Code Playgroud)
它将数组混洗到位,并为方便起见返回数组本身.
java.util.Collections.shuffle(java.util.Arrays.asList(a:_*))
Run Code Online (Sandbox Code Playgroud)
为了使上述工作正常,一个元素类型必须是scala.AnyRef的子类(相当于java.lang.Object),因为Arrays.asList()使用传入的数组作为结果java.util的后备存储. List和java.util.List只能包含对象引用(不是原始值).*
这也是为什么Collections.shuffle()改组传入的java.util.List实际上改组数组的原因.*
*:请参阅下面的注释
例如:
scala> val a = Array[java.lang.Integer](1, 2, 3) // note the type parameter
a: Array[java.lang.Integer] = Array(1, 2, 3)
scala> java.util.Collections.shuffle(java.util.Arrays.asList(a:_*))
scala> a
res43: Array[java.lang.Integer] = Array(1, 3, 2)
scala> java.util.Collections.shuffle(java.util.Arrays.asList(a:_*))
scala> a
res45: Array[java.lang.Integer] = Array(1, 2, 3)
Run Code Online (Sandbox Code Playgroud)
注意:Scala 2.7.5用于上述代码段.如丹尼尔所示,Scala 2.8.0表现出不同的行为.为了安全起见,不要依赖于数组被洗牌的事实,而是从Arrays.asList()返回的列表被洗牌.
scala> val a = Array[java.lang.Integer](1, 2, 3)
a: Array[java.lang.Integer] = Array(1, 2, 3)
scala> val b = java.util.Arrays.asList(a:_*)
b: java.util.List[java.lang.Integer] = [1, 2, 3]
scala> java.util.Collections.shuffle(b)
scala> b
res50: java.util.List[java.lang.Integer] = [2, 1, 3]
scala> java.util.Collections.shuffle(b)
scala> b
res52: java.util.List[java.lang.Integer] = [3, 1, 2]
Run Code Online (Sandbox Code Playgroud)
当涉及到varargs时,似乎Scala正在做一些与Java不同的事情.至少,我无法以任何方式洗牌.据推测,列表中的shuffle会混乱数组,因为列表是数组支持的.好吧,似乎Scala会在将vararg参数传递给Java时创建一个新数组,因此上述示例无用.
scala> val b = java.util.Arrays.asList(a: _*)
b: java.util.List[java.lang.String] = [a, b, c]
scala> java.util.Collections.shuffle(b); println(a.toString+" "+b.toString)
Array(a, b, c) [a, b, c]
scala> java.util.Collections.shuffle(b); println(a.toString+" "+b.toString)
Array(a, b, c) [c, b, a]
scala> java.util.Collections.shuffle(b); println(a.toString+" "+b.toString)
Array(a, b, c) [a, c, b]
scala> java.util.Collections.shuffle(b); println(a.toString+" "+b.toString)
Array(a, b, c) [b, a, c]
scala> java.util.Collections.shuffle(b); println(a.toString+" "+b.toString)
Array(a, b, c) [a, b, c]
scala> java.util.Collections.shuffle(b); println(a.toString+" "+b.toString)
Array(a, b, c) [c, a, b]
Run Code Online (Sandbox Code Playgroud)
尽管有其他声明,它确实可以与Ints一起使用,但是:
scala> val a = Array(1,2,3)
a: Array[Int] = Array(1, 2, 3)
scala> val b = java.util.Arrays.asList(a: _*)
b: java.util.List[Int] = [1, 2, 3]
scala> java.util.Collections.shuffle(b); println(a.toString+" "+b.toString)
Array(1, 2, 3) [2, 3, 1]
scala> java.util.Collections.shuffle(b); println(a.toString+" "+b.toString)
Array(1, 2, 3) [3, 2, 1]
scala> java.util.Collections.shuffle(b); println(a.toString+" "+b.toString)
Array(1, 2, 3) [3, 2, 1]
scala> java.util.Collections.shuffle(b); println(a.toString+" "+b.toString)
Array(1, 2, 3) [1, 2, 3]
Run Code Online (Sandbox Code Playgroud)
在Scala 2.8上,有一种更简单的方法:
scala> scala.util.Random.shuffle(a)
res32: Sequence[Int] = Array(1, 2, 3)
scala> scala.util.Random.shuffle(a)
res33: Sequence[Int] = Array(2, 1, 3)
scala> scala.util.Random.shuffle(a)
res34: Sequence[Int] = Array(3, 2, 1)
Run Code Online (Sandbox Code Playgroud)
请注意,以Scala方式,它不会更改原始数组.
回答"这里到底发生了什么?" 部分:
当您说java.util.Arrays.asList(a)时,您正在调用静态Java方法,该方法被定义为采用可变数量的参数(Java中的vararg ...语法):
public static <T> List<T> asList(T... a)
Run Code Online (Sandbox Code Playgroud)
在Scala中调用asList时,您传入的是一个Array [String],而不是三个字符串参数.尽管Scala将T*表示为Array [T],但您需要告诉编译器您确实要传递三个参数,而不是单个参数,它是一个包含三个参数的List.
Scala有一种方便的方法将Array [String]转换为String,String,String:你使用_*符号,如Walter Chang的回答所示.无论何时将某些内容传递给vararg函数,都可以使用它.
在Scala编程的第188和189页中概述了这一点.
您还会在模式匹配中看到_*以匹配List中的零个或多个元素.