【问题标题】:Default value for generic data structure通用数据结构的默认值
【发布时间】:2010-12-23 08:25:47
【问题描述】:

我想编写一个SparseVector[T] 类,其中T 可以是双精度、整数或布尔值。

该类不会由数组支持(因为我想要一个稀疏数据结构),但我已经看到,当我构建一个 AnyVal 类型的空数组时,元素被初始化为默认值。例如:

 scala> new Array[Int](10)
 res0: Array[Int] = Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)

 scala> new Array[Boolean](10)
 res1: Array[Boolean] = Array(false, false, false, false, false, false, false, false, false, false)

 scala> new Array[Double](10) 
 res2: Array[Double] = Array(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)

如何在我的类中包含这个默认值?我想得到的行为是:

val v = new SparseVector[Double](100)
println( v(12) ) // should print '0.0'
val w = new SparseVector[Boolean](100)
println( v(85) ) // should print 'false'

谢谢

【问题讨论】:

  • 这很有趣。 C# 提供了一个“default”关键字,它接受一个类型并提供该类型的默认值。它以不同的方式处理值和引用类型,因此 default(int) 为 0 而 default(SomeClass) 为 null。我想知道 scala 是否有类似的构造。

标签: generics scala default-value


【解决方案1】:

您可以利用 Scala 已经为您提供了一种获取类型默认值的方法这一事实。当您编写var x: Int = _ 时,这会将x 初始化为0。所有AnyVal 类型都类似。所有AnyRef 类型都初始化为null

考虑到这一点,您可以将稀疏向量类重写如下:

class SparseVector[T](val size: Int) {
  import scala.collection.mutable.Map

  private var default: T = _
  private[this] val storage = Map[Int, T]() 

  def apply(key: Int) = 
    if(key < size)
      storage.getOrElse(key, default)
    else 
      throw new IllegalArgumentException("Index "  + key + " out of bounds")

  def update(key: Int, value: T) { storage(key) = value }
}

现在如下代码可以按预期工作:

scala> val b = new SparseVector[Boolean](10)
b: SparseVector[Boolean] = SparseVector@cfd22a

scala> b(1)
res20: Boolean = false

scala> b(1) = true

scala> b(1)
res22: Boolean = true

scala> val i = new SparseVector[Int](10)
i: SparseVector[Int] = SparseVector@1813c12

scala> i(1)
res23: Int = 0

scala> i(1) = 10

scala> i(1)
res25: Int = 10

scala> i(10)
java.lang.IllegalArgumentException: Index 10 out of bounds

我可能会对这个类做一些改进:

  • 有一个 `toString` 方法以合理的方式打印集合
  • 提供一个伴随对象,如果需要,它可以更改向量的默认值(参见下面的代码)。
object SparseVector {
  def apply[T](size: Int) = new SparseVector[T](size)
  def apply[T](size: Int, default: T) = {
    val result = new SparseVector[T](size)
    result.default = default

    result
  }
}

现在可以了:

scala> val b = SparseVector[Boolean](10, true)
b: SparseVector[Boolean] = SparseVector@126f29f

scala> b(4)
res28: Boolean = true

scala> val i = SparseVector[Int](10, 42)
i: SparseVector[Int] = SparseVector@b9979b

scala> i(3)
res30: Int = 42

编辑:我编写的代码适用于 Scala 2.7.6.final。 Mitch Blevins 指出,当使用 Scala 2.8r.19890 运行时,代码产生 null 作为 AnyVal 类型的默认值。正如 cmets 中所解释的,这应该是不可能的,因为 Null 不是 AnyVal 的子类型。如果使用 2.8,一般的想法应该是相似的,因为 var b: Boolean = _ 仍然应该为您提供默认值Boolean 类型。集合存储稀疏向量的用法可能会有所不同,但正如我在评论中所说的,我对 2.8 的集合重新设计并不熟悉。

EDIT2: ... null 行为应该是不可能的,但不幸的是它是。做一些more research into the problem 似乎由于类型擦除,字段default always 被初始化为null。在那之后……奇怪的事情接踵而至。请参阅Mitch's post 进行讨论和一些重现问题的熊骨头代码。

为了让代码正常工作,我尝试过但失败了:

  • null.asInstanceOf[T] - 不,Java 没有具体化的泛型。这仍然产生null
  • @specialised - 不,似乎即使编译器为原语生成专门的代码,你仍然会得到空行为
  • 将结果转换为AnyVal,它不应该是null。没有。还是null

所以从概念上讲,我的解决方案应该有效。但这并不是因为我在 Scala Trac 中有 reported 的非常奇怪的行为。

另请参阅this blog post,了解有关nullable AnyVals 的精彩讨论。

-- 弗拉维乌·西普西根

【讨论】:

  • 使用 2.8r.19890,这将返回 null 作为布尔值(或任何 AnyVals)的默认值
  • @Mitch Hm...那一定是一个错误。从逻辑上讲,AnyVal 类型不应该是 nullnull的类型是Null,是AnyRef的底层类型。如果您尝试将null 分配给AnyVal 类型,您应该得到一个编译错误-val b: Boolean = null 产生类型不匹配(找到Null,需要Boolean)。或者可能是由于集合重新设计(我不熟悉) - 我怀疑getOrElse 方法在这种情况下表现得很奇怪。在 Scala REPL 中尝试 var b: Boolean = _。它应该默认为false :)。
  • 导致我困惑的行为在这里发布:stackoverflow.com/questions/1853397/…
  • 所以如果我使用private var default: T = null.asInstanceOf[T] 它将与scala 2.7 和2.8 兼容?
  • 不幸的是不是:(。请参阅我的修订答案进行简短讨论。
【解决方案2】:

您可以添加一个隐式参数作为构造函数的第二个参数:

class SparseVector[A](size: Int) (implicit default: () => A) {
  private var storage = scala.collection.mutable.Map[Int, A]()
  def apply(i: Int) = storage.getOrElse(i, default())
  def update(i: Int, v: A) = storage.update(i, v)
}

implicit def strDefault(): String = "default"

并为您关心的类型提供隐式。这也允许调用者提供他们自己的默认值,通过将他们自己的默认值传入:

val sparseWithCustomDefault = new SparseVector[String](10) (() => "dwins rules!");

【讨论】:

  • 不错的建议,但我对implicit 参数不满意。我害怕定义冲突。
【解决方案3】:

您可以使用清单来获得与 Array 相同的默认值,这样就无需提供您自己的隐式。再次向 David Winslow 借用其余代码,

class SparseVector[T](size: Int)(implicit manifest: Manifest[T]) {
    private val default = manifest.newArray(1)(0)
    private var storage = scala.collection.mutable.Map[Int, T]()
    def apply(i: Int) = storage.getOrElse(i, default)
    def update(i: Int, v: T) = storage.update(i, v)
}

那么,

val v = new SparseVector[Int](100)
println( v(12) ) // prints '0'

等等

【讨论】:

  • 起初看起来像一个肮脏的黑客,但这个解决方案避免了导入隐式。
【解决方案4】:

重新使用 David 的 SparseVector 类,您可以使用如下内容:

class SparseVector[T](size: Int, default: T = 0) {
  private var storage = scala.collection.mutable.Map[Int, T]()
  def apply(i: Int) = storage.getOrElse(i, default)
  def update(i: Int, v: T) = storage.update(i, v)
}

object SparseVector {
  implicit def svInt2String(i: Int) = "default"
  implicit def svInt2Boolean(i: Int = false
}

您需要导入隐式,这很可惜,但这给了您:-

import SparseVector._    

val v = new SparseVector[Int](100)
println( v(12) ) // prints '0'
val w = new SparseVector[Double](100)
println( w(12) ) // prints '0.0'
val x = new SparseVector[Boolean](100)
println( x(85) ) // prints 'false'
val y = new SparseVector[String](100)
println( y(85) ) // prints 'default'

【讨论】:

    猜你喜欢
    • 2013-02-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-19
    • 2014-01-17
    • 2016-09-19
    • 1970-01-01
    相关资源
    最近更新 更多