在Haskell中将函数短接到下三角(ish)数组:速度导致丑陋的代码

时间:2014-06-24 00:16:27

标签: haskell vector

我在一个名为maybeProduceValue i j的最小示例中有一个函数,该函数仅在i>时有效。 j。请注意,在我的实际代码中,j不是统一的,因此只有的数据类似于三角矩阵,我不知道这个数学名称是什么。

我想要我的代码,它循环遍历ij并基本上返回(js已排序)

[maximum [f i j | j <- js, j < i] | i <- [0..iMax]]

一旦失败,不再检查j。在类C语言中,这很简单,如

if (j >= i) {break;}

我试图在Haskell中重新创建这种行为。我在下面有两个实现:

  • 试图通过使用takeWhile只检查最多一个未通过测试的值(i)并返回Nothing;

  • 会记住上一个j工作的i的数量,因此对i+1来说,它不会做任何安全检查,直到它超过这个号码。

后者的功能是我的基准测试速度的两倍多,但实际上是一团糟 - 我试图让人们相信Haskell更简洁安全,同时仍然具有合理的性能,这里有一些密集的快速代码,混乱,并做了一堆不安全的操作。

是否有解决方案,可能使用Cont,Error或Exception,可以达到我想要的行为?

n.b。我尝试过使用Traversable.mapAccumL和Vector.unfoldrN而不是State,它们的速度和清晰度大致相同。它仍然是解决这个问题的一种非常复杂的方式。

import Criterion.Config
import Criterion.Main
import Control.DeepSeq
import Control.Monad.State
import Data.Maybe
import qualified Data.Traversable as T
import qualified Data.Vector as V

main = deepseq inputs $ defaultMainWith (defaultConfig{cfgSamples = ljust 10}) (return ()) [
    bcompare [
        bench "whileJust" $ nf whileJust js,
        bench "memoised"  $ nf memoisedSection js
    ]]

iMax = 5000
jMax = 10000

-- any sorted vector
js :: V.Vector Int
js = V.enumFromN 0 jMax

maybeProduceValue :: Int -> Int -> Maybe Float
maybeProduceValue i j | j < i = Just (fromIntegral (i+j))
                      | otherwise = Nothing

unsafeProduceValue :: Int -> Int -> Float
-- unsafeProduceValue i j | j >= i = error "you fool!"
unsafeProduceValue i j = fromIntegral (i+j)

whileJust, memoisedSection
    :: V.Vector Int -> V.Vector Float

-- mean: 389ms
-- short circuits properly
whileJust inputs' = V.generate iMax $ \i ->
    safeMax . V.map fromJust . V.takeWhile isJust $ V.map (maybeProduceValue i) inputs'
    where safeMax v = if V.null v then 0 else V.maximum v

-- mean: 116ms
-- remembers the (monotonically increasing) length of the section of
-- the vector that is safe. I have tested that this doesn't violate the condition that j < i
memoisedSection inputs' = flip evalState 0 $ V.generateM iMax $ \i -> do
    validSection <- state $ \oldIx ->
            let newIx = oldIx + V.length (V.takeWhile (< i) (V.unsafeDrop oldIx inputs'))
            in (V.unsafeTake newIx inputs', newIx)
    return $ V.foldl' max 0 $ V.map (unsafeProduceValue i) validSection

4 个答案:

答案 0 :(得分:1)

这是使用Applicatives解决问题的一种简单方法,只要您在遇到问题时不需要保留列表的其余部分:

import Control.Applicative

memoizeSections :: Ord t => [(t, t)] -> Maybe [t]
memoizeSections []          = Just []
memoizeSections ((x, y):xs) = (:) <$> maybeProduceValue x y <*> memoizeSections xs 

这相当于:

import Data.Traversable

memoizeSections :: Ord t => [(t, t)] -> Maybe [t]    
memoizeSections = flip traverse (uncurry maybeProduceValue)

并在第一次出现故障时返回Nothing。请注意,我不知道这有多快,但它确实简洁,可以说很清楚(特别是第一个例子)。

答案 1 :(得分:1)

一些小评论:

-- any sorted vector
js :: V.Vector Int
js = V.enumFromN 0 jMax

如果您有Int s(或Float s等)的向量,则需要使用Data.Vector.Unboxed

maybeProduceValue :: Int -> Int -> Maybe Float
maybeProduceValue i j | j < i = Just (fromIntegral (i+j))
                      | otherwise = Nothing

由于Just在其唯一字段中是惰性的,因此这将为计算fromIntegral (i+j)创建一个thunk。您几乎总是希望像{<1}}那样应用

Just

还有更多的东西:

maybeProduceValue i j | j < i = Just $! fromIntegral (i+j)

即你想:

memoisedSection inputs' = flip evalState 0 $ V.generateM iMax $ \i -> do
    validSection <- state $ \oldIx ->
            let newIx = oldIx + V.length (V.takeWhile (< i) (V.unsafeDrop oldIx inputs'))
            in (V.unsafeTake newIx inputs', newIx)
    return $ V.foldl' max 0 $ V.map (unsafeProduceValue i) validSection

因为这对在其字段中是懒惰的并且

            let !newIx = oldIx + V.length (V.takeWhile (< i) (V.unsafeDrop oldIx inputs'))
                !v = V.unsafeTake newIx inputs'
            in (v, newIx)

因为状态monad中的 return $! V.foldl' max 0 $ V.map (unsafeProduceValue i) validSection 在值中是惰性的。

答案 2 :(得分:0)

您可以在单一列表理解中使用警卫:

[f i j | j <- js, i <- is, j < i]

答案 3 :(得分:0)

如果您尝试获得与

相同的结果
[foo i j | i <- is, j <- js, j < i]

如果您知道js正在增加,请写下

[foo i j | i <- is, j <- takeWhile (< i) js]

没有必要为此Maybe而烦恼。请注意,使输入列表全局化具有可能不幸的效果:不是将输入列表的生成与其转换和最终消耗融合在一起,而是强制实际构造列表然后将其保留在内存中。很有可能将列表从内存中拉入缓存需要更长的时间,而不是一点一点地生成它!