Значение по умолчанию для общей структуры данных - PullRequest
7 голосов
/ 04 декабря 2009

Я хотел бы написать класс SparseVector[T], где T может быть double, int или boolean.

Класс не будет поддерживаться массивом (потому что мне нужна разреженная структура данных), но я видел, что при создании пустого массива типа 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'

Спасибо

Ответы [ 4 ]

7 голосов
/ 05 декабря 2009

Вы можете использовать тот факт, что 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. Митч Блевинс указал, что код выдает null в качестве значения по умолчанию для типов AnyVal при запуске с Scala 2.8r.19890. Как поясняется в комментариях, это не должно быть возможным, поскольку Null не является подтипом AnyVal. . Общая идея должна быть аналогичной при использовании 2.8, так как var b: Boolean = _ все равно должна давать вам значение по умолчанию для Boolean тип. Использование коллекций для хранения разреженного вектора может быть другим, но, как я сказал в комментарии, я не знаком с редизайном коллекции 2.8.

EDIT2: ... поведение null не должно быть возможным, но, к сожалению, это так. Если провести еще исследования проблемы , кажется, что из-за стирания типа поле default всегда инициализируется до null. И после этого ... странность наступает. См. пост Митча для обсуждения и некоторого медвежьего кода, воспроизводящего проблему.

Вещи, которые я пробовал и потерпел неудачу, чтобы заставить код работать как следует:

  • null.asInstanceOf[T] - Нет, у Java нет улучшенных обобщений. Это все еще дает null
  • @specialised - нет, похоже, что даже если компилятор генерирует специализированный код для примитивов, вы все равно получаете нулевое поведение
  • Приведение результата к AnyVal, который не должен быть null. Нету. Все еще null.

Так что концептуально мое решение должно работать. Но это не из-за очень странного поведения, о котором я сообщил в Scala Trac.

См. Также это сообщение в блоге для хорошего обсуждения null способного AnyVal с.

- Flaviu Cipcigan

7 голосов
/ 04 декабря 2009

Вы можете добавить неявный аргумент в качестве второго параметра в конструктор:

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!");
4 голосов
/ 05 декабря 2009

Вы можете использовать манифест, чтобы получить то же значение по умолчанию, что и для Array, что устраняет необходимость указывать ваши собственные последствия. Заимствуя оставшуюся часть кода у Дэвида Уинслоу,

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'

и т.д.

0 голосов
/ 05 декабря 2009

Повторно используя класс Дэвида 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'
...