۹ - ۶استخراج بخشی از لیستها
در این بخش، نگاهی میندازیم به چندتا تابعِ مفید برای استخراج بخشهایی از یه لیست، و همچنین توابعی برای تقسیمبندی لیستها. سه تابعِ اول تایپ سیگنچرهای مشابه دارن، یه آرگومانِ Int
و یه آرگومانِ لیست میگیرن:
take :: Int -> [a] -> [a]
drop :: Int -> [a] -> [a]
splitAt :: Int -> [a] -> ([a], [a])
در فصلهای قبل چندتا از اینها رو دیده بودیم، اما انقدر رایج و کاربردی هستن که ارزشِ دوره کردن رو دارن.
تابع take
تعداد مشخصی از المانهای یه لیست رو درمیاره و لیستی که فقط حاوی اون مقادیره برمیگردونه. همونطور که میبینید یک آرگومانِ Int
میگیره اون رو به یک آرگومان لیست اعمال میکنه. اینطور کار میکنه:
Prelude> take 7 ['a'..'z']
"abcdefg"
Prelude> take 3 [1..10]
[1,2,3]
Prelude> take 3 []
[]
وقتی بهش لیست خالی دادیم، اون هم یه لیست خالی برگردوند. لیستهایی هم که نوشتیم، با شکر ِ گرامر ِ بازهای لیست میسازن. از take
با یه تابعِ لیستساز مثل enumFrom
هم میتونستیم استفاده کنیم. یادآوری: تابعِ enumFrom
در صورتی که تایپِ ورودیش یه مجموعه ِ بینهایت باشه (مثل Integer
)، یه لیست بینهایت درست میکنه. ولی اگه فقط تعداد مشخصی از المانهاش رو بگیریم، لیست بینهایت درست نمیکنه:
Prelude> take 10 (enumFrom 10)
[10,11,12,13,14,15,16,17,18,19]
تابع drop
مشابه take
هست، ولی تعداد المانهای تعیین شده رو از اول لیست حذف میکنه (میندازه). باز هم میشه از بازهها یا توابعِ لیستساز استفاده کنیم:
Prelude> drop 5 [1..10]
[6,7,8,9,10]
Prelude> drop 8 ['a'..'z']
"ijklmnopqrstuvwz"
Prelude> drop 4 []
[]
Prelude> drop 2 (enumFromTo 10 20)
[12,13,14,15,16,17,18,19,20]
تابعِ splitAt
یه لیست رو، در المانی که با Int
تعیین شده، به دو بخش تقسیم میکنه و یه توپل از دو لیست درست میکنه:
Prelude> splitAt 5 [1..10]
([1,2,3,4,5],[6,7,8,9,10])
Prelude> splitAt 10 ['a'..'z']
("abcdefghij","klmnopqrstuvwxyz")
Prelude> splitAt 5 []
([],[])
Prelude> splitAt 3 (enumFromTo 5 15)
([5,6,7],[8,9,10,11,12,13,14,15])
توابعِ سطح بالا ِ takeWhile
و dropWhile
، همونطور که از تایپ سیگنچر ِشون پیداست، کمی متفاوتاند:
takeWhile :: (a -> Bool) -> [a] -> [a]
dropWhile :: (a -> Bool) -> [a] -> [a]
این دو تابع کارهای take
و drop
رو تا زمانی که یک شرط (به Bool
دقت کنین) برقرار باشه انجام میدن. تابعِ takeWhile
المانهایی که شرط ِ داده شده براشون صادق هست رو برمیداره، و با رسیدن به اولین المانی که شرط رو ارضا نمیکنه متوقف میشه.
المانهایی که کوچکتر از ۳ هستن رو بردار:
Prelude> takeWhile (<3) [1..10]
[1,2]
المانهایی که کوچکتر از ۸ هستن رو بردار:
Prelude> takeWhile (<8) (enumFromTo 5 15)
[5,6,7]
مثال بعدی لیستِ خالی برمیگردونه، چون این تابع به محض رسیدن به المانی که شرط براش صادق نیست متوقف میشه، که در این مثال میشه المانِ اول:
Prelude> takeWhile (>6) [1..10]
[]
در مثال پایین، چرا فقط یک a برمیگردونه؟
Prelude> takeWhile (=='a') "abracadabra"
"a"
احتمالاً بتونین حدس بزنین تابعِ dropWhile
چطور کار میکنه. برای بهتر دیدن تفاوتهاشون، در مثالهای زیر هم از همون آرگومانهایی که برای takeWhile
استفاده کردیم، آوردیم:
Prelude> dropWhile (<3) [1..10]
[3,4,5,6,7,8,9,10]
Prelude> dropWhile (<8) (enumFromTo 5 15)
[8,9,10,11,12,13,14,15]
Prelude> dropWhile (>6) [1..10]
[1,2,3,4,5,6,7,8,9,10]
Prelude> dropWhile (=='a') "abracadabra"
"bracadabra"
تمرینها: تقارن ترسناک
۱.
با استفاده از takeWhile
و dropWhile
تابعی بنویسین که لغاتِ یه جمله رو در یه لیست برگردونه (لغات رو به کمکِ فاصله از هم تشخیص بده)، مثل نمونهی زیر:
Prelude> myWords "sheryl wants fun"
["Sheryl", "wants", "fun"]
۲.
تابعی بنویسین که یه نوشته بگیره و یه لیست از خطهای نوشته ِ ورودی برگردونه (که هر خط با کاراکترِ "خط جدید" یا \n
از خط ِ بعد جدا میشه). تعریف تابع رو بجای undefined
بنویسین:
module PoemLines where
firstSen = "Tyger Tyger, burning bright\n"
secondSen = "In the forests of the night\n"
thirdSen = "What immortal hand or eye\n"
fourthSen = "Could frame thy fearful\
\ symmetry?"
sentences = firstSen ++ secondSen
++ thirdSen ++ fourthSen
-- باید این رو چاپ کنه putStrLn sentences
-- Tyger Tyger, burning bright
-- In the forests of the night
-- What immortal hand or eye
-- Could frame thy fearful symmetry?
-- تابع رو تعریف کنین
myLines :: String -> [String]
myLines = undefined
-- هم myLines sentences جواب
-- باشه shouldEqual باید برابر
shouldEqual =
["Tyger Tyher, burning bright\n"
, "In the forests of the night\n"
, "What immortal hand or eye\n"
, "Could frame thy fearful symmetry?"
]
-- رو فقط برای main تابع
-- تست تابعتون تعریف کردیم.
main :: IO ()
main =
print $
"Are they equal? "
++ show (myLines sentences
== shouldEqual)
۳.
حالا به نقاط مشترک اون توابع نگاه کنیم. سعی کنین یه تابعی بنویسین که اون کاراکترِ جداکنندهی نوشتهها (که در یکی فاصله، و در دومی کاراکترِ خطِ جدید بود) یکی از پارامترهاش باشه. نهایتاً توابعِ myWords
و myLines
رو با استفاده از تابع جدیدتون بازنویسی کنین.