使用向量进行 Scala 函数式编程

发布于 2024-10-08 15:21:11 字数 359 浏览 0 评论 0原文

我是 Scala 编程新手,对函数式编程和不可变集合有一些困难。我正在尝试将我的数字代码从 C++ 移植到 Scala。

很多时候我的代码都在这样的小向量上运行:

double ytmp[6];

for (int i=0; i<6; i++) {
    ytmp[i] = y[i] + h*(a41*dydx[i] + a42*k2[i] + a43*k3[i]);
}

How do I write like this in effective Scala?我一开始就考虑过使用简单的列表,但是不可变类型有问题,所以我不能像我习惯的 C++ 那样只创建一个空列表 ytmp 并稍后修改它。 感谢您的帮助

I am new to Scala programming and have some difficulty with functional programming and immutable collections. I am trying to port my numeric code from C++ to Scala.

A lot of time I have code operating on small vector like this:

double ytmp[6];

for (int i=0; i<6; i++) {
    ytmp[i] = y[i] + h*(a41*dydx[i] + a42*k2[i] + a43*k3[i]);
}

How do I write something like this in efficient Scala? I thought about using simple lists in the beginning, but have problems with the immutable types, so I can't just create a empty list ytmp and modify it later like I am used to i C++.
Thanks for the help

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

救星 2024-10-15 15:21:11

为了提高效率,您可以以完全相同的方式编写此代码(注意,这不是函数代码),除了使用 while 循环。 For 循环被转换成极其低效(但非常通用和强大)的结构。因此,在这种情况下,您(警告,未经测试):

val ytmp = new Array[Double](6)
var i = 0
while (i < 6) {
  ytmp(i) = y(i) + h*(a41*dydx(i) + a42*k2(i) + a43*k3(i))
  i += 1
}

通常,它的运行速度与您的 C++ 代码一样快。

一旦您将这样的原始内容包装到类中,然后使用 mapforeach 等操作大量这些类,您就会开始从 Scala 中受益。

但对于数字代码,要么编写漂亮但速度慢得令人尴尬的代码(从 C++ 的角度来看),要么编写丑陋但性能不错的代码(是的,与类 C C++ 相比丑陋)。

因为我经常这样做,所以我尝试将这些丑陋但快速的代码放入漂亮的库中,然后尽可能地忘记它并在库启用的更高级别上工作。

For efficiency, you write this exactly the same way (note, this is not functional code), except with a while loop. For loops are converted into incredibly inefficient (yet splendidly general and powerful) constructs. So, in this case, you (warning, untested):

val ytmp = new Array[Double](6)
var i = 0
while (i < 6) {
  ytmp(i) = y(i) + h*(a41*dydx(i) + a42*k2(i) + a43*k3(i))
  i += 1
}

which will run about as fast as your C++ code, typically.

You start to benefit from Scala once you wrap primitive stuff like this into classes, and then operate on large numbers of those classes with map, foreach, and so on.

But for numeric code, either you write pretty but embarrassingly slow code (from a C++ perspective), or ugly but decently-performing code (yes, ugly compared to C-like C++).

Since I do this a lot, I try to put this ugly but fast code in nice libraries and then forget about it as much as possible and work at the higher level enabled by the libraries.

半暖夏伤 2024-10-15 15:21:11

我是这样写的:

// C++
double ytmp[6];

// Scala
val ytmp = new Array[Double](6)


// C++ (elided the computation to make conversion clearer)
for (int i=0; i<6; i++) {
    ytmp[i] = func(i);
}

// Scala
for (i <- ytmp.indices)
    ytmp(i) = func(i)


// C++
double func(int i) {
    return y[i] + h*(a41*dydx[i] + a42*k2[i] + a43*k3[i]);
}

// Scala
def func(i: Int) = y(i) + h * (a41 * dydx(i) + a42 * k2(i) + a43 * k3(i))

所以,这是第一次转换,除非我犯任何错误,否则应该做同样的事情。还有其他注意事项,例如使用不可变集合而不是数组,或者在创建时初始化数组。但是,对于您正在编写的代码类型来说,数组会更快。数组初始化的一个有趣的选项是:

def func(i: Int) = y(i) + h * (a41 * dydx(i) + a42 * k2(i) + a43 * k3(i))
val ytmp = Array.tabulate[Double](6)(func)

但是,我不确定 tabulate 是否会为您提供相同的性能。或许。

This is how I'd write it:

// C++
double ytmp[6];

// Scala
val ytmp = new Array[Double](6)


// C++ (elided the computation to make conversion clearer)
for (int i=0; i<6; i++) {
    ytmp[i] = func(i);
}

// Scala
for (i <- ytmp.indices)
    ytmp(i) = func(i)


// C++
double func(int i) {
    return y[i] + h*(a41*dydx[i] + a42*k2[i] + a43*k3[i]);
}

// Scala
def func(i: Int) = y(i) + h * (a41 * dydx(i) + a42 * k2(i) + a43 * k3(i))

So, this is a first transformation, which, barring any mistakes on my part, should do the same thing. There are other considerations like using an immutable collection instead of Array, or initializing the array at the time of creation. However, arrays will be faster for the kind of code you are writing. One interesting option for array initialization would be this:

def func(i: Int) = y(i) + h * (a41 * dydx(i) + a42 * k2(i) + a43 * k3(i))
val ytmp = Array.tabulate[Double](6)(func)

However, I'm not sure tabulate will give you the same performance. Maybe.

温柔一刀 2024-10-15 15:21:11

这是另一种可能性 - 不知道它是否高性能等:

ytmp.zipWithIndex map ((yi) => yi._1 + h*(a41*dydx(yi._2) + a42*k2(yi._2) + a43*k(yi._2)))

它返回一个数组

Here's another possibility - no idea if its performant etc:

ytmp.zipWithIndex map ((yi) => yi._1 + h*(a41*dydx(yi._2) + a42*k2(yi._2) + a43*k(yi._2)))

which returns an array

在巴黎塔顶看东京樱花 2024-10-15 15:21:11

您可以尝试类似的

val v = for(i <- 1 to 10) yield y(i) + h*(a41*dydx(i) + a42*k2(i) + a43*k(i))

值 v 将是使用 Vector 实现的 IndexedSeq,Vector 是为索引查找构建的不可变类型。

You could try something like

val v = for(i <- 1 to 10) yield y(i) + h*(a41*dydx(i) + a42*k2(i) + a43*k(i))

The value v will be an IndexedSeq implemented using a Vector, which is an immutable type built for indexed lookups.

许一世地老天荒 2024-10-15 15:21:11

这是另一套班轮,但我怀疑性能会很差:

Array(ytmp, dydx, k2, k3).transpose.map(l => l(0) + h*(a41*l(1) + a42*l(2) + a43*l(3)))

Here is another one liner but I suspect the performance will be bad:

Array(ytmp, dydx, k2, k3).transpose.map(l => l(0) + h*(a41*l(1) + a42*l(2) + a43*l(3)))
抚你发端 2024-10-15 15:21:11

这个问题有很多不同的解决方案,非常感谢。特别是对于基准数字。所以我想我会坚持使用“.view.zipWithIndex”解决方案并希望速度足够好,否则我必须回到 while 循环。

我面临的另一个小问题。
我需要这样的东西:

List results
while(step<nsteps) {
    so something
    if successful
        results.append(result)
}

如果我理解正确,我应该总是在列表前面添加。
所以这就是我要做的:

var list: List[Double] =  Nil
while(step<nsteps) {
    var result = 4.0
    list =  result :: list
}
list = list.reverse

这是一个好主意,还是有更好的性能解决方案?

puh there are a lot of different solutions to this problem, thanks very much. Especially for the benchmark numbers. So I think I will stick with the ".view.zipWithIndex" solution and hope the speed is good enough, otherwise I have to go back to while loops.

Another short problem I face.
I need something like this:

List results
while(step<nsteps) {
    so something
    if successful
        results.append(result)
}

If i understand it right I should always prepend to a list.
So this is how I would do it:

var list: List[Double] =  Nil
while(step<nsteps) {
    var result = 4.0
    list =  result :: list
}
list = list.reverse

Is this a good idea, or is there a better solution for the performance ?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文