【问题标题】:Idiomatic Vector Algebra in HaskellHaskell 中的惯用向量代数
【发布时间】:2015-06-04 03:28:04
【问题描述】:

作为在 Haskell 中练习向量库的一种方式,我正在尝试重写我之前用 C 编写的 Nelder-Mead 最小化算法。 到目前为止,我在翻译一些惯用的向量操作时遇到了一些麻烦。

例如,考虑一个函数,它从 n+1 的列表中找到 n 个向量的质心(过滤掉一个索引),

在 C 中,这可以写成

static void get_centroid(double **s, int n, int iz,
                         double *C)
{
  for (int i = 0; i < n+1; i++) {
    if (i != iz) {
      for (int j = 0; j < n; j++)
        C[j] += s[i][j];
    }
  }
  for (int j = 0; j < n; j++)
    C[j] /= n;
}

我尝试将其翻译成 Haskell,结果如下

import Data.Vector
import qualified Data.Vector as V

type Node = Vector Double

type Simplex = Vector Node

centroid :: Simplex -> Int -> Node
centroid s iz = V.map (/ (fromIntegral $ V.length s)) $ V.zipWith (-) v (s ! iz) 
    where v = V.foldl go V.empty s 
        where go a b = V.zipWith (+) a b

我发现这段代码很不优雅,因为它没有捕捉到正在发生的向量代数的本质(而且效率也更低,因为我正在加减 S[iz])。

一种解决方案是实现某种向量空间类型类或使用更具体的线性代数库,但由于这些是常见的操作,我想知道是否有更惯用的“直接”解决方案。

【问题讨论】:

  • 为什么要使用Data.Vector?一个更具体的库几乎肯定是要走的路。但是,如果您想尝试使用这些向量,您应该 1. 切换到“未装箱”版本以提高速度,以及 2. 查看向量切片操作以跳过行/列。

标签: haskell


【解决方案1】:

我会从 dfeuer 的 +1 开始;一个更具体的库几乎肯定会更干净、更高效。

但是,如果您正在寻找的是您的 centroid 函数的更惯用的实现,我喜欢这个:

centroid' :: Simplex -> Int -> Node
centroid' s iz = let t = foldl1 (V.zipWith (+)) (V.drop iz s)
                     n = fromIntegral (V.length t - 1)
                 in V.map (/ n) t

对您的版本的一般评论:创建“只写”Haskell 代码非常容易。您的第一行内容太多,难以解析。您的 where 块是朝着正确方向迈出的一步,但我会进一步分解概念组件。

另外,Hoogle。我不知道有一个函数drop,但我知道如果它存在,它将一个Int 和一个Vector 放到一个新的Vector 上。 Hoogle 不索引Vector,但向量的API 与列表的API 非常相似。我搜索了“[a] -> Int -> [a]”和“Int -> [a] -> [a]”,找到了drop

Stackage 确实索引Vector,因此在此处搜索“Int -> Vector a -> Vector a”有效)

【讨论】:

  • 谢谢!这就是我一直在寻找的答案。我认为 foldl 和 map 抓住了每个向量操作的本质,我可以想象这如何推广到更复杂的算法。
猜你喜欢
  • 1970-01-01
  • 2013-03-16
  • 1970-01-01
  • 2015-09-11
  • 2011-07-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多