Haskell 有效地将函数应用于所有对

Haskell 有效地将函数应用于所有对,haskell,vector,fold,Haskell,Vector,Fold,我需要一个二阶函数pairaply,它将一个二进制函数f应用于类似列表结构的所有唯一对,然后以某种方式组合它们。示例/示意图: pairApply (+) f [a, b, c] = f a b + f a c + f b c 一些研究使我相信Data.Vector.unbox可能会有很好的性能(我还需要快速访问特定元素);此外,还需要使用Statistics.Sample,这将在后续工作中派上用场 考虑到这一点,我有以下几点,几乎可以编译: import qualified Data.Vec

我需要一个二阶函数
pairaply
,它将一个二进制函数
f
应用于类似列表结构的所有唯一对,然后以某种方式组合它们。示例/示意图:

pairApply (+) f [a, b, c] = f a b + f a c + f b c
一些研究使我相信
Data.Vector.unbox
可能会有很好的性能(我还需要快速访问特定元素);此外,还需要使用
Statistics.Sample
,这将在后续工作中派上用场

考虑到这一点,我有以下几点,几乎可以编译:

import qualified Data.Vector.Unboxed as U      

pairElement :: (U.Unbox a, U.Unbox b)    
            => (U.Vector a)                    
            -> (a -> a -> b)                   
            -> Int                             
            -> a                               
            -> (U.Vector b)                    
pairElement v f idx el =
  U.map (f el) $ U.drop (idx + 1) v            

pairUp :: (U.Unbox a, U.Unbox b)   
       => (a -> a -> b)                        
       -> (U.Vector a)                         
       -> (U.Vector (U.Vector b))
pairUp f v = U.imap (pairElement v f) v 

pairApply :: (U.Unbox a, U.Unbox b)
          => (b -> b -> b)                     
          -> b                                 
          -> (a -> a -> b)                     
          -> (U.Vector a)                      
          -> b
pairApply combine neutral f v =
  folder $ U.map folder (pairUp f v) where
  folder = U.foldl combine neutral
这不会编译的原因是没有未绑定的
U.Vector(U.Vector a))
实例。我已经能够在其他情况下使用
Data.Vector.unbox.Deriving
创建新的unbox实例,但我不确定在这种情况下会这么容易(将其转换为元组对,其中第一个元素是所有串联的内部向量,第二个是向量的长度,知道如何解包?)

我的问题可以分为两部分:

  • 上面的实现是否有意义,或者是否有一些快速的库函数魔法等可以让它变得更简单
  • 如果是这样的话,有没有比上面画的更好的方法来制作一个未绑定的向量

注意,我知道
foldl
可能不是最佳选择;一旦我对实现进行了分类,我计划用一些不同的方法进行基准测试

无法为
Unbox(U.Vector b)
定义经典实例,因为这需要预先分配一个内存区域,其中每个元素(即每个子向量!)具有相同的固定空间量。但总的来说,它们可能都是任意大的,所以根本不可行

原则上,可以通过只存储嵌套向量的扁平形式加上额外的索引数组(每个子向量的起始位置)来定义该实例;实际上,就不可变向量而言,这似乎有点前途,但一个实例也需要一个可变的实现,而这对于这种方法来说是没有希望的(因为任何改变一个子向量中元素数量的变异都需要改变它背后的一切)

通常,这并不值得,因为如果单个元素向量不是很小,那么装箱它们的开销就无关紧要,也就是说,通常使用
B.Vector(U.Vector B)

但是,对于您的应用程序,我根本不会这样做–没有必要将上面的元素选择封装在一个三角形数组中。(这样做对性能非常不利,因为这会使算法占用O(n²)内存,而不是O(n)内存,而O(n)是所需的全部。)

我只想做以下几件事:

pairApply combine neutral f v
 = U.ifoldl' (\acc i p -> U.foldl' (\acc' q -> combine acc' $ f p q)
                                   acc
                                   (U.drop (i+1) v) )
             neutral v
这相当于明显的嵌套循环命令实现

pairApply(combine, b, f, v):
    for(i in 0..length(v)-1):
        for(j in i+1..length(v)-1):
            b = combine(b, f(v[i], v[j]);
    return b;

我的答案与leftaroundabout的嵌套循环命令式实现基本相同:

pairApply :: (Int -> Int -> Int) -> Vector Int -> Int
pairApply f v = foldl' (+) 0 [f (v ! i) (v ! j) | i <- [0..(n-1)], j <- [(i+1)..(n-1)]]
 where n = length v
pairApply::(Int->Int->Int)->向量Int->Int

配对应用F=Fordl’(+)0 [ f(v!i)(v.j)i ],因为您写的“所有唯一的对”,什么是错误的<代码>(a,a)< /代码>,代码>(b,b)< /代码>,和<代码>(c,c)< /代码>?你认为<代码>(a,b)< /代码>等于,或不同于,<代码>(b,a)
?好吧,假设不同元素的唯一对Swall,我确实看到了这可能会带来巨大的性能问题…即O(n²)在折叠值之前列出您建立的列表。事实上,没有优化。然而,这让我惊讶,使用
ghc-8.2-O2
,它实际上只需要我的60%的时间!ghc在将控制流列表融合到紧密循环方面做得非常好。我仍然不确定我是否更喜欢这种解决方案,因为这种优化不太可靠。啊,而且:当你用替换
时,你的代码更快。经过进一步测试,我认为性能差异主要是由于GHC-8.2中的优化回归。对于GHC-7.10,两个版本(你的
unsafeIndex
)在C参考实现的10%范围内运行。阅读本文,我认为我的代码不会使用
foldl'
建立O(n^2)列表。但我可能错了。无论如何,感谢您的评论和调查。事实上,您的实现不会建立O(n²)列表…或者实际上是任何列表,但这只是因为GHC优化了它。你通常不能依赖于此–列表毕竟是在你的源代码中定义的。充其量,你可以指望垃圾收集器在处理完列表之前回收列表的开头,但这在性能上仍然不是很好。哦,不是吗t注意到存在
ifoldl