道歉似乎是一个基本问题。我试图弄清楚如何将Data.Text转换为Char的向量。我能想到的最好的是:
textToVec :: T.Text -> V.Vector Char
textToVec t = T.foldr append V.empty t where
append c vec = V.snoc vec c
这看起来有点复杂(计算效率低下?)是否有更好的方法?
更一般地说,作为一名Haskell新手,我很欣赏任何关于如何为自己搞清楚这种事情的建议。我无法从谷歌或搜索文档中取得很大进展。
答案 0 :(得分:8)
更好是一种主观的。什么使事情变得更好?更少的代码?减少内存消耗?需要的时间更少?
话虽如此,是的,你的算法计算效率低,因为snoc
有O(n),其中n
是你的(中间)向量的长度。因此,您创建了一个O(n * n)算法(n snoc
运算,长度增加,因此1 + 2 + 3 + ... +(n-1)= n *(n-1) / 2)。
根据隐藏在big-O表示法中的常量,人们会期望你的函数在大型列表上运行缓慢。但在我们对它进行基准测试之前,让我们考虑其他选择:
[Char]
通过V.fromList
和T.pack
V.unfoldr
中的单个字符与T.Text
分开来使用T.uncons
展开矢量。因此,让我们使用Criterion来创建一些基准:
module Main where
import Criterion
import Criterion.Main
import Control.Monad (replicateM)
import qualified Data.Text as T
import qualified Data.Vector as V
import System.Random (randomRIO)
setupEnv n = fmap T.pack $ replicateM n (randomRIO (' ','~'))
textToVec :: T.Text -> V.Vector Char
textToVec t = T.foldr append V.empty t where
append c vec = V.snoc vec c
example :: T.Text
example = T.pack "Hello, World"
benchOn :: T.Text -> [Benchmark]
benchOn ~e = map (\(s,f) -> bench s $ whnf f e)
[("textToVec", textToVec)
,("T.foldr (flip V.snoc) V.empty", T.foldr (flip V.snoc) V.empty)
,("V.fromList . T.unpack", V.fromList . T.unpack)
,("V.unfoldr T.uncons", V.unfoldr T.uncons)
]
main = defaultMain [
bgroup "example" $ benchOn example
, env (setupEnv $ 1000) $ \ ~small -> bgroup "1000" $ benchOn small
, env (setupEnv $ 10000) $ \ ~small -> bgroup "10000" $ benchOn small
]
我用lts-5编译它:
stack exec --package random\
--package vector\
--package text\
--resolver=lts-5\
-- ghc -O2 Benchmark.hs
我们首先在“Hello,world”上运行所有变体,然后在随机生成的长度为1000的字符串上运行,然后运行长度为10000的字符串。
benchmarking example/textToVec
time 1.106 us (1.098 us .. 1.117 us)
1.000 R² (0.999 R² .. 1.000 R²)
mean 1.102 us (1.099 us .. 1.107 us)
std dev 12.67 ns (6.277 ns .. 21.00 ns)
benchmarking example/T.foldr (flip V.snoc) V.empty
time 1.225 us (1.222 us .. 1.229 us)
1.000 R² (1.000 R² .. 1.000 R²)
mean 1.229 us (1.226 us .. 1.232 us)
std dev 10.48 ns (7.634 ns .. 16.00 ns)
benchmarking example/V.fromList . T.unpack
time 643.6 ns (640.9 ns .. 646.4 ns)
1.000 R² (1.000 R² .. 1.000 R²)
mean 643.6 ns (641.7 ns .. 645.7 ns)
std dev 6.525 ns (5.573 ns .. 7.860 ns)
benchmarking example/V.unfoldr T.uncons
time 518.1 ns (516.1 ns .. 520.4 ns)
1.000 R² (1.000 R² .. 1.000 R²)
mean 518.0 ns (516.1 ns .. 520.1 ns)
std dev 6.541 ns (4.943 ns .. 8.479 ns)
variance introduced by outliers: 11% (moderately inflated)
正如您所看到的,此时,与您的功能相比,展开或通过列表需要大约一半的时间。展开的方法以及fromList . unpack
都需要半微秒。
benchmarking 1000/textToVec
time 1.311 ms (1.302 ms .. 1.320 ms)
0.999 R² (0.999 R² .. 1.000 R²)
mean 1.342 ms (1.331 ms .. 1.366 ms)
std dev 55.31 us (27.75 us .. 96.80 us)
variance introduced by outliers: 29% (moderately inflated)
benchmarking 1000/T.foldr (flip V.snoc) V.empty
time 6.013 ms (5.953 ms .. 6.081 ms)
0.999 R² (0.997 R² .. 1.000 R²)
mean 6.054 ms (6.028 ms .. 6.097 ms)
std dev 100.8 us (62.45 us .. 180.7 us)
benchmarking 1000/V.fromList . T.unpack
time 26.83 us (26.77 us .. 26.92 us)
1.000 R² (1.000 R² .. 1.000 R²)
mean 26.90 us (26.83 us .. 27.00 us)
std dev 264.1 ns (209.7 ns .. 348.2 ns)
benchmarking 1000/V.unfoldr T.uncons
time 15.05 us (14.93 us .. 15.19 us)
0.999 R² (0.999 R² .. 1.000 R²)
mean 15.02 us (14.95 us .. 15.15 us)
std dev 303.0 ns (206.6 ns .. 428.3 ns)
variance introduced by outliers: 19% (moderately inflated)
这是有趣的地方。你的功能比无点功能更好。人们必须研究GHC的核心,看看瓶颈在哪里。
话虽如此,对于一个大约比我们原来的length "Hello, World" == 12
}长100倍的字符串,我们的两个参赛者V.unfoldr T.uncons
和V.fromList . T.unpack
大约需要30/45倍的时间数据与示例1相比。
另一方面,与前一个1μs相比,您的功能需要1ms。 100次数据的1000倍时间。哦,哦......
benchmarking 10000/textToVec
time 190.9 ms (188.7 ms .. 192.8 ms)
1.000 R² (1.000 R² .. 1.000 R²)
mean 192.5 ms (191.8 ms .. 193.6 ms)
std dev 1.096 ms (684.4 us .. 1.426 ms)
variance introduced by outliers: 14% (moderately inflated)
benchmarking 10000/T.foldr (flip V.snoc) V.empty For a
time 859.3 ms (856.5 ms .. 860.9 ms)
1.000 R² (1.000 R² .. 1.000 R²)
mean 861.9 ms (860.7 ms .. 862.6 ms)
std dev 1.042 ms (0.0 s .. 1.173 ms)
variance introduced by outliers: 19% (moderately inflated)
benchmarking 10000/V.fromList . T.unpack
time 567.8 us (565.5 us .. 570.6 us)
1.000 R² (1.000 R² .. 1.000 R²)
mean 571.7 us (569.8 us .. 574.0 us)
std dev 7.132 us (5.674 us .. 9.545 us)
benchmarking 10000/V.unfoldr T.uncons
time 200.6 us (199.4 us .. 201.8 us)
1.000 R² (1.000 R² .. 1.000 R²)
mean 200.2 us (199.4 us .. 201.2 us)
std dev 2.948 us (2.240 us .. 3.837 us)
我们将数据大小增加了10倍。与上一次运行相比,您的函数花费了200倍的时间,而展开者和fromList
只花费了20倍的时间。
根据基准,V.unfoldr T.uncons
取得了成功。那么,作为一名Haskell新手,你会如何找到这个功能呢?
嗯,起初并不是那么明显。 “显而易见”的方法通常是使用列表,因为它们在Haskell中无处不在,说实话,V.fromList . T.unpack
似乎运行得很好。此外,该技巧适用于提供fromList
(作为目标)或toList (as source). Therefore, it works for any
可折叠instance (
文本`不是一个的每个数据结构。
你可能发现如果您查看文档并同时查看了V.unfoldr
和T.uncons
的类型:
V.unfoldr :: (b -> Maybe (a , b )) -> b -> V.Vector a
T.uncons :: T.Text -> Maybe (Char, T.Text)
V.unfoldr T.uncons :: T.Text -> V.Vector Char
但是现在你知道有解开/解除模式,所以你知道将来如何使用它。