从一个数据结构映射到另一个

时间:2016-12-29 00:18:06

标签: haskell

道歉似乎是一个基本问题。我试图弄清楚如何将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新手,我很欣赏任何关于如何为自己搞清楚这种事情的建议。我无法从谷歌或搜索文档中取得很大进展。

1 个答案:

答案 0 :(得分:8)

更好是一种主观的。什么使事情变得更好?更少的代码?减少内存消耗?需要的时间更少?

话虽如此,是的,你的算法计算效率低,因为snoc有O(n),其中n是你的(中间)向量的长度。因此,您创建了一个O(n * n)算法(n snoc运算,长度增加,因此1 + 2 + 3 + ... +(n-1)= n *(n-1) / 2)。

根据隐藏在big-O表示法中的常量,人们会期望你的函数在大型列表上运行缓慢。但在我们对它进行基准测试之前,让我们考虑其他选择:

  • 我们可以查看一个中间数据结构,它为两个数据结构提供转换,例如: [Char]通过V.fromListT.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都需要半微秒。

1000个字符的随机文本

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.unconsV.fromList . T.unpack大约需要30/45倍的时间数据与示例1相比。

另一方面,与前一个1μs相比,您的功能需要1ms。 100次数据的1000倍时间。哦,哦......

10000个字符的随机文本

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.unfoldrT.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

但是现在你知道有解开/解除模式,所以你知道将来如何使用它。