۲۷ - ۷سیکوئِنس یا Sequence

‏‎Sequence‎‏ نوع‌داده ِ خوبیه که برپایه‌ی درخت‌های انگشتی ساخته شده؛ متأسفانه توی این کتاب درخت‌های انگشتی رو توضیح نمیدیم، اما پیشنهاد می‌کنیم خودتون یه نگاهی بهشون بندازین.* با ‏‎Sequence‎‏ میشه هم از ابتدا و هم از انتها append ِ ارزون داشت. یکی از مشکلاتِ رایجِ لیست‌ها اینه که append فقط از ابتدا ارزون‌ه.

*

مثلاً این خوبه:

Finger Trees: A Simple General-purpose Data Structures by Ralf Hinze and Ross Paterson

این نوع‌داده ِ ‏‎Sequence‎‏ ِه:

newtype Seq a = Seq (FingerTree (Elem a))

-- چیز مهمی نیست، فقط نقش Elem
-- این رو داره که در تایپ‌ها (موقع 
-- استفاده) المان‌ها و گره‌ها رو
-- ‎‏از هم جدا کنه.‏‎
newtype Elem a = Elem { getElem :: a }

data FingerTree a
  = Empty
  | Single a
  | Deep {-# UNPACK #-} !Int !(Digit a)
         (FingerTree (Node a)) !(Digit a)

چه چیزی با ‏‎Sequence‎‏ سریع‌تره؟

cons و append در دو انتهای ساختارِ داده و الحاق کارهایی‌اند که ‏‎Sequence‎‏ به خاطرشون شناخته شده‌ست. البته اکثرِ مواقع، لیست گزینه‌ی بهتری نسبت به ‏‎Sequence‎‏ ِه. دسترسی به دُم با ‏‎Sequence‎‏ به صرفه‌تر از لیست‌ه. این یه مثال از حالتی‌ه که چون لیست یه کم بزرگه، ‏‎Sequence‎‏ بهتر عمل می‌کنه:

module Main where

import Criterion.Main 
import qualified Data.Sequence as S

lists :: [[Int]]
lists = replicate 10 [1..100000]

seqs :: [S.Seq Int]
seqs =
  replicate 10 (S.fromList [1..100000]) 

main :: IO ()
main = defaultMain
  [ bench "concatenate lists" $
      nf mconcat lists
  , bench "concatenate sequences" $
      nf mconcat seqs
  ]

دقت کنین که اینجا، زمانِ خیلی زیادی در بنچمارک صرفِ پیمایش ِ ساختارِ داده میشه، چون برای اطمینان از اینکه همه‌ی کار انجام بشه داریم تا حالت معمولی محاسبه می‌کنیم. برحسب اتفاق، به خاطر پیمایش (یا اندیس‌گیری) ِ سریع‌تر در ‏‎Sequence‎‏، این کار تفاوت بینِ لیست و ‏‎Sequence‎‏ هم برجسته‌تر می‌کنه. این رو در نظر بگیرین:

module Main where

import Criterion.Main 
import qualified Data.Sequence as S

lists :: [Int]
lists = [1..100000]

seqs :: S.Seq Int
seqs = S.fromList [1..100000]

main :: IO ()
main = defaultMain
  [ bench "indexing list" $
      whnf (\xs -> xs !! 9001) lists
  , bench "indexing sequence" $
      whnf (flip S.index 9001) seqs
  ]

وقتی ما این بنچمارک رو اجرا کردیم، اختلاف بین لیست و ‏‎Sequence‎‏ در مقیاسِ ۱۰۰ بود.

چه چیزی با ‏‎Sequence‎‏ کُندتَره؟

‏‎Sequence‎‏ هم مثل ‏‎Map‎‏ یه ساختارِ داده ِ پایا هست، پس تراکم حافظه‌ش به خوبیِ ‏‎Vector‎‏ نیست (الان میرسیم). اندیس‌گیری با ‏‎Int‎‏ هم، برای ‏‎Vector‎‏ سریع‌تره. در بعضی موارد، اگه لیست کوچیک باشه، تایپِ لیست برای cons کردن و الحاق سریع‌تر عمل می‌کنه. وقتی می‌خواین به انتهای یه لیستِ طولانی، apped ِ کم هزینه داشته باشین، خوبه ‏‎Sequence‎‏ هم امتحان کنین، اما اگه عملکرد براتون مهمه، تصمیمِ نهایی رو بر مبنای داده‌ها ِ بنچمارکینگ بگیرین.