Вопрос новичка Скала о простых операциях с математическим массивом - PullRequest
9 голосов
/ 07 сентября 2010

Новичок Скала Вопрос:

Скажем, я хочу сделать это [код Java] в Scala:

public static double[] abs(double[] r, double[] im) {
  double t[] = new double[r.length];
  for (int i = 0; i < t.length; ++i) {
    t[i] = Math.sqrt(r[i] * r[i] + im[i] * im[i]);
  }
  return t;
}  

, а также сделать его универсальным (поскольку Scala эффективно выполняет общие примитивы, которые я прочитал). Опираясь только на базовый язык (без библиотечных объектов / классов, методов и т. Д.), Как можно это сделать? По правде говоря, я вообще не понимаю, как это сделать, поэтому думаю, что это просто вопрос о бонусных баллах.

Я столкнулся с множеством проблем, пытаясь сделать эту простую вещь, от которой я отказался в Scala на данный момент. Надеюсь, когда я увижу Scala, у меня будет «ага» момент.

UPDATE: Обсуждая это с другими, это лучший ответ, который я нашел до сих пор.

def abs[T](r: Iterable[T], im: Iterable[T])(implicit n: Numeric[T]) = {
   import n.mkNumericOps                                                   
   r zip(im) map(t => math.sqrt((t._1 * t._1 + t._2 * t._2).toDouble))          
}

Ответы [ 4 ]

13 голосов
/ 07 сентября 2010

Выполнение универсальных / производительных примитивов в scala на самом деле включает в себя два связанных механизма, которые scala использует для избежания упаковки / распаковки (например, упаковка int в java.lang.Integer и наоборот):

  • @specialize тип аннотации
  • Использование Manifest с массивами

specialize - это аннотация, которая сообщает компилятору Java о создании «примитивных» версий кода (как я уже говорил, в шаблонах C ++). Проверьте объявление типа Tuple2 (которое является специализированным) по сравнению с List (которое не является). Он был добавлен в 2.8 и означает, что, например, код, подобный CC[Int].map(f : Int => Int), выполняется без каких-либо ограничений int s (при условии, что CC специализирован, конечно!).

Manifest s - это способ создания улучшенных типов в scala (что ограничено стиранием типа JVM ). Это особенно полезно, когда вы хотите иметь обобщенный метод для некоторого типа T, а затем создать массив T (т.е. T[]) внутри метода. В Java это невозможно, потому что new T[] недопустимо. В Scala это возможно с помощью Manifests. В частности, и в этом случае это позволяет нам построить примитив T-массив, такой как double[] или int[]. (Это круто, если вам интересно)

Бокс так важен с точки зрения производительности, потому что он создает мусор, если только все ваши int не <127. Он также, очевидно, добавляет уровень косвенности с точки зрения дополнительных шагов процесса / вызовов методов и т.д. Но учтите, что вы, вероятно, не будете кричать, если не будете абсолютно уверены в том, что определенно это делаете (т.е. большая часть кода не нуждается в такой микрооптимизации) </em>


Итак, вернемся к вопросу: для того, чтобы сделать это без упаковки / распаковки, вы должны использовать Array (List еще не специализирован, и в любом случае будет более требовательным к объектам, даже если бы он был! ). Функция zipped для пары коллекций возвращает коллекцию Tuple2 с (для которой не требуется бокс, поскольку специализируется на ).

Для того, чтобы сделать это в общем (то есть для различных числовых типов), вы должны требовать контекст, связанный с вашим универсальным параметром, чтобы он был Numeric и чтобы можно было найти Manifest (требуется для создания массива). Итак, я начал в том же духе ...

def abs[T : Numeric : Manifest](rs : Array[T], ims : Array[T]) : Array[T] = {
    import math._
    val num = implicitly[Numeric[T]]
    (rs, ims).zipped.map { (r, i) => sqrt(num.plus(num.times(r,r), num.times(i,i))) }
    //                               ^^^^ no SQRT function for Numeric
}

... но это не совсем работает . Причина в том, что «универсальное» значение Numeric не имеет такой операции, как sqrt ->, поэтому вы можете сделать это только в том случае, если знаете, что у вас есть Double. Например:

scala> def almostAbs[T : Manifest : Numeric](rs : Array[T], ims : Array[T]) : Array[T] = {
 | import math._
 | val num = implicitly[Numeric[T]]
 | (rs, ims).zipped.map { (r, i) => num.plus(num.times(r,r), num.times(i,i)) }
 | }
almostAbs: [T](rs: Array[T],ims: Array[T])(implicit evidence$1: Manifest[T],implicit     evidence$2: Numeric[T])Array[T]

Отлично - теперь посмотрите, как этот чисто общий метод делает что-то!

scala> val rs = Array(1.2, 3.4, 5.6); val is = Array(6.5, 4.3, 2.1)
rs: Array[Double] = Array(1.2, 3.4, 5.6)
is: Array[Double] = Array(6.5, 4.3, 2.1)

scala> almostAbs(rs, is)
res0: Array[Double] = Array(43.69, 30.049999999999997, 35.769999999999996)

Теперь мы можем sqrt результат, потому что у нас есть Array[Double]

scala> res0.map(math.sqrt(_))
res1: Array[Double] = Array(6.609841147864296, 5.481788029466298, 5.980802621722272)

И чтобы доказать, что это будет работать даже с другим Numeric типом:

scala> import math._
import math._
scala> val rs = Array(BigDecimal(1.2), BigDecimal(3.4), BigDecimal(5.6)); val is =     Array(BigDecimal(6.5), BigDecimal(4.3), BigDecimal(2.1))
rs: Array[scala.math.BigDecimal] = Array(1.2, 3.4, 5.6)
is: Array[scala.math.BigDecimal] = Array(6.5, 4.3, 2.1)

scala> almostAbs(rs, is)
res6: Array[scala.math.BigDecimal] = Array(43.69, 30.05, 35.77)

scala> res6.map(d => math.sqrt(d.toDouble))
res7: Array[Double] = Array(6.609841147864296, 5.481788029466299, 5.9808026217222725)
11 голосов
/ 07 сентября 2010

Используйте zip и map:

scala> val reals = List(1.0, 2.0, 3.0)
reals: List[Double] = List(1.0, 2.0, 3.0)

scala> val imags = List(1.5, 2.5, 3.5)
imags: List[Double] = List(1.5, 2.5, 3.5)

scala> reals zip imags
res0: List[(Double, Double)] = List((1.0,1.5), (2.0,2.5), (3.0,3.5))

scala> (reals zip imags).map {z => math.sqrt(z._1*z._1 + z._2*z._2)}
res2: List[Double] = List(1.8027756377319946, 3.2015621187164243, 4.6097722286464435)

scala> def abs(reals: List[Double], imags: List[Double]): List[Double] =
     | (reals zip imags).map {z => math.sqrt(z._1*z._1 + z._2*z._2)}
abs: (reals: List[Double],imags: List[Double])List[Double]

scala> abs(reals, imags)
res3: List[Double] = List(1.8027756377319946, 3.2015621187164243, 4.6097722286464435)

UPDATE

Лучше использовать zipped, потому что это позволяет избежать создания временной коллекции:

scala> def abs(reals: List[Double], imags: List[Double]): List[Double] =
     | (reals, imags).zipped.map {(x, y) => math.sqrt(x*x + y*y)}
abs: (reals: List[Double],imags: List[Double])List[Double]

scala> abs(reals, imags)
res7: List[Double] = List(1.8027756377319946, 3.2015621187164243, 4.6097722286464435)
4 голосов
/ 07 сентября 2010

В Java нет простого способа создать общий числовой вычислительный код;библиотек там нет, как вы можете видеть из ответа Oxbow.Коллекции также предназначены для произвольных типов, а это означает, что работа с примитивами требует дополнительных затрат.Таким образом, самый быстрый код (без тщательной проверки границ) это либо:

def abs(re: Array[Double], im: Array[Double]) = {
  val a = new Array[Double](re.length)
  var i = 0
  while (i < a.length) {
    a(i) = math.sqrt(re(i)*re(i) + im(i)*im(i))
    i += 1
  }
  a
}

, либо, с хвостовой рекурсией:

def abs(re: Array[Double], im: Array[Double]) = {
  def recurse(a: Array[Double], i: Int = 0): Array[Double] = {
    if (i < a.length) {
      a(i) = math.sqrt(re(i)*re(i) + im(i)*im(i))
      recurse(a, i+1)
    }
    else a
  }
  recurse(new Array[Double](re.length))
}

Так что, к сожалению, этот код не выглядит супер-хорошим;милость приходит, как только вы упаковываете ее в удобную библиотеку массивов комплексных чисел.

Если окажется, что вам на самом деле не нужен высокоэффективный код, тогда

def abs(re: Array[Double], im: Array[Double]) = {
  (re,im).zipped.map((i,j) => math.sqrt(i*i + j*j))
}

сделает свое делокомпактно и концептуально ясно (как только вы поймете, как работает zipped).Наказание в моих руках - это примерно в 2 раза медленнее.(Использование List делает его в 7 раз медленнее, чем while или tail рекурсия в моих руках; List с zip делает его в 20 раз медленнее; генерики с массивами в 3 раза медленнее даже без вычисления квадратного корня.)

(Изменить: фиксированное время, чтобы отразить более типичный вариант использования.)

1 голос
/ 07 сентября 2010

После редактирования:

ОК, я запустил то, что хотел сделать. Возьмет два Списка любого типа числа и возвратит Массив Двойников.

def abs[A](r:List[A], im:List[A])(implicit numeric: Numeric[A]):Array[Double] = {
  var t = new Array[Double](r.length)
  for( i <- r.indices) {          
    t(i) = math.sqrt(numeric.toDouble(r(i))*numeric.toDouble(r(i))+numeric.toDouble(im(i))*numeric.toDouble(im(i)))
  }
  t
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...