有效地将功能应用于所有对

时间:2017-09-11 10:10:09

标签: haskell vector fold

我需要一个二阶函数pairApply,它将二元函数f应用于列表式结构的所有唯一对,然后以某种方式将它们组合在一起。示例/草图:

pairApply (+) f [a, b, c] = f a b + f a c + f b c

一些研究让我相信Data.Vector.Unboxed可能会有很好的表现(我还需要快速访问特定元素);对于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))的Unboxed实例。我已经能够使用Data.Vector.Unboxed.Deriving在其他情况下创建新的未装箱的实例,但我不确定在这种情况下它会如此简单(将其转换为元组对,其中第一个元素是所有内部的连接的向量,第二个是向量的长度,知道如何解包?)

我的问题可分为两部分:

  • 上述实现是否有意义,或者是否有一些快速的库函数魔法等可以更容易实现?
  • 如果是这样,是否有更好的方法来制作一个未装箱的矢量矢量而不是上面描绘的矢量?

请注意,我知道foldl可能不是最佳选择;一旦我完成了实施,我计划用几个不同的折叠进行基准测试。

2 个答案:

答案 0 :(得分:5)

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

原则上可能可以通过仅存储嵌套向量的扁平形式以及额外的索引数组(每个子向量开始的位置)来定义该实例。 I once briefly gave this a try;就可变向量而言,它实际上似乎有点有希望,但G.Vector实例也需要一个可变实现,这对于这种方法是没有希望的(因为任何改变一个子向量中元素数量的突变都需要转移它背后的一切)。

通常,它不值得,因为如果单个元素向量不是很小,那么装箱它们的开销就无关紧要了,即使用B.Vector (U.Vector b)通常是有意义的。

对于您的应用程序,我根本不会这样做 - 没有必要将上层元素选项包装在一个三角形数组中。 (并且这样做会非常糟糕,因为它会使算法采用 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;

答案 1 :(得分:3)

我的回答与左下角的嵌套循环命令式实现基本相同:

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

据我所知,我认为此实现没有任何性能问题。

为了简单起见,非多态。