我在一个名为maybeProduceValue i j
的最小示例中有一个函数,该函数仅在i
>时有效。 j
。请注意,在我的实际代码中,j
不是统一的,因此只有的数据类似于三角矩阵,我不知道这个数学名称是什么。
我想要我的代码,它循环遍历i
和j
并基本上返回(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
答案 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
而烦恼。请注意,使输入列表全局化具有可能不幸的效果:不是将输入列表的生成与其转换和最终消耗融合在一起,而是强制实际构造列表然后将其保留在内存中。很有可能将列表从内存中拉入缓存需要更长的时间,而不是一点一点地生成它!