11

我有一个要随机排列的数组。在 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));

我尝试在 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))
                                                             ^

这里到底发生了什么?我不想用特殊标志(-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
}

它在适当的位置打乱数组,并为方便起见返回数组本身。

4

3 回答 3

6
java.util.Collections.shuffle(java.util.Arrays.asList(a:_*))

要使上述内容正常工作,a 的元素类型必须是 scala.AnyRef 的子类(等效于 java.lang.Object),因为 Arrays.asList() 使用传入的数组作为结果 java.util 的后备存储。 List 和 java.util.List 只能包含对象引用(不是原始值)。*

这也是为什么将传入的 java.util.List 打乱的 Collections.shuffle() 实际上打乱了数组的原因。 *

*:见下面的注释

例如:

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)

注意:Scala 2.7.5 用于上述代码片段。正如 Daniel 所展示的,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]
于 2009-08-11T09:37:23.087 回答
6

在可变参数方面,Scala 似乎在做一些与 Java 不同的事情。至少,我无法以任何我尝试的方式改变该数组。假设,列表上的洗牌会洗牌数组,因为列表是数组支持的。好吧,似乎 Scala 在将可变参数参数传递给 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]

尽管另有声明,但它确实适用于 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]

在 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)

请注意,以 Scala 方式,它不会更改原始数组。

于 2009-08-11T14:00:59.067 回答
5

回答“这里到底发生了什么?” 部分:

当您说 java.util.Arrays.asList(a) 时,您正在调用一个静态 Java 方法,该方法被定义为采用可变数量的参数(Java 中的 vararg ... 语法):

public static <T> List<T> asList(T... a) 

在 Scala 中,当您调用 asList 时,您传递的是单个 Array[String] 而不是三个字符串参数。即使 Scala 将 T* 表示为 Array[T] ,您也需要告诉编译器您的真正意思是传递三个参数,而不是一个包含三件事的 List 的参数。

Scala 有一种方便的方法将您的 Array[String] 转换为 String,String,String:您使用 _* 符号,如 Walter Chang 的回答中所示。每当您将某些内容传递给可变参数函数时,您都可以使用它。

这在 Scala 编程的第 188 页和第 189 页中进行了概述。

您还将在模式匹配中看到 _* 以匹配列表中的零个或多个元素。

于 2009-08-11T10:34:52.877 回答