۹ - ۱۲تمرینهای فصل
بخش اول این تمرینها بیشتر جنبهی دورهای داره، اما چندتا چیز جدید هم معرفی میکنه. بخش دوم ممکنه یه کم چالش برانگیز باشه، اما از هیچ مبحث یا گرامر ای که قبلاً نگفته باشیم استفاده نمیکنه. اگه به مشکل خوردین، شاید با دورهی بخش مربوطه مشکلتون برطرف شه.
Data.Char
این چندتا تمرینِ اول سادهاند، اما تابعهای جدیدی از کتابخونهها معرفی، و بعضی از چیزهایی که قبلاً یاد گرفتیم رو دوره میکنن. بعضی از توابعی که اینجا به کار بردیم در Prelude
نیستن و باید از Data.Char
وارد ِشون کنیم. این کار رو با جملهی import Data.Char
میشه انجام داد، چه اولِ یه فایل بنویسین (پیشنهاد ما) چه در REPL. این کار تعدادی تابع که با تایپهای Char
و String
کار میکنن رو واردِ گستره میکنه.
۱.
تایپهای isUpper
و toUpper
رو استعلام کنین.
۲.
با توجه به مثالهای زیر، از کدوم استفاده کنیم تا بتونیم با تابعی، حروف بزرگ رو از یه ورودیِ String
فیلتر کنیم؟ یعنی مثلاً با ورودیِ “HbEfLrLxO”، تابع باید خروجیِ “HELLO” بده.
Prelude Data.Char> isUpper 'J'
True
Prelude Data.Char> toUpper 'j'
'J'
۳.
تابعی بنویسین که حرف اولِ یه نوشته رو بزرگ کنه و نوشته رو تماماً برگردونه. مثلاً با آرگومانِ “julie” جواب “Julie” بشه.
۴.
حالا یه نسخهی بازگشتی از اون تابع رو بنویسین که اگه بهِش ورودیِ “woot” دادین، سَرِتون داد بزنه “WOOT”. تایپ سیگنچر تغییری نمیکنه، اما یه فرمان پایه لازم دارین.
۵.
برای تمرین آخر در این بخش، یه تابع استانداردِ دیگه برای لیستها لازم داریم: head
. تایپش رو استعلام کنین و یه کم باهاش مشغول شین تا طرز کارش رو بشناسین. حالا یه تابعی بنویسین که حرف اول یه نوشته رو بزرگ کنه و فقط اون حرف رو به تنهایی برگردونه.
۶.
آفرین. خیلی خوب. حالا به صورتِ یه تابع ترکیبی بازنویسیش کنین. و بعد از اون، برای سرگرمی، نسخهی بینقطهش رو هم بنویسین.
رمزنگاری
برای این تمرین به استفاده از Data.Char
ادامه میدیم. این تمرینها رو تو یه ماژول به اسمِ Cipher
ذخیره کنین، چون در فصلهای بعد لازمشون داریم. فعلاً یه رمزنگارِ سزار تعریف میکنین، اما فصلهای بعد قابلیتهایی رو پیشنهاد میکنیم که بهش اضافه کنین.
رمزنگارِ سزار، یک رمزنگارِ جایگزینی ِ سادهست که در اون، هر حرف با یه حرفی که فاصلهی ثابتی ازش در الفبا داره جایگزین میشه. حالتهای متنوعی از این رمزنگاری وجود داره – میشه حروف قبلتر یا بعدتر رو با هر تعدادی جایگزین کرد. مثلاً با ۳ حرف جابجایی به راست، ‘A’ تبدیل میشه به ‘D’، و ‘B’ به ‘E’ تبدیل میشه. یا با ۵ حرف جابجایی به چپ، ‘a’ به ‘v’ تبدیل میشه، و الی آخر.
هدف این تمرین نوشتن یک رمزنگارِ سزار ِه که حروف رو به راست منتقل میکنه. میتونین اول مقدار جابجایی رو ثابت در نظر بگیرین، بعد تابعی بنویسین که میزان جابجایی یکی از آرگومانهاش باشه. کمی سختتره اما میشه هر دفعه پیغامهای سِرّیمون رو متفاوت رمزگذاری کنیم.
روی اینترنت رمزنگارهای سزار که با هسکل نوشته شده باشن خیلی زیادن. ما هم چندتا نکته برای راهنمایی میگیم که کمتر وسوسهی دیدن جواب از روی اینترنت بشین. اما بعد از اینکه خودتون حل کردین، بد نیست جوابتون رو با اونهایی که رو اینترنت هستن مقایسه کنین.
خط اول کُدتون باید اینطور باشه:
module Cipher where
import Data.Char
دو تابعِ ord
و chr
در Data.Char
هستن، که معادلِ Int
ِ یک Char
رو بر مبنای سیستمِ یونیکُد، و برعکسش (یعنی معادلِ Char
برای یه Int
) رو پیدا میکنن:
*Cipher> :t chr
chr :: Int -> Char
*Cipher> :t ord
ord :: Char -> Int
استفاده از این دو تابع اختیاریه؛ از راههای دیگهای هم میشه حروف رو منتقل کرد، اما استفاده از chr
و ord
ممکنه حل رو سادهتر کنه.
انتقال حروف در آخرِ الفبا باید برگرده از اول، مثلاً اگه انتقال ۳ حرفی به راست از ‘z’ بدین، باید به حرف ‘c’ برسین، نه یه جا وسطِ جنگلِ یونیکُد. این کار، بسته به اینکه چطور کُدتون رو نوشتین، ممکنه سخت باشه. شاید خوب باشه از یه حرفِ پایه (مثلاً ‘a’) شروع کنین، و با استفاده از mod
مطمئن باشین که فقط بینِ ۲۶ حرف انگلیسی جابجا میشین.
یه تابع هم در کنارِ این تابع تعریف کنین، به اسمِ unCaesar
که نوشتهی ورودی رو رمزگشایی میکنه. در یکی از فصلهای آینده، تستش میکنیم.
توابع استانداردِ خودتون رو بنویسین
اینجا چندتا از توابعِ استاندارد رو توضیح دادیم و شما باید نسخهی خودتون از اونها رو بنویسین. این تمرین کمک میکنه درک عمیقتری از خوداتکایی روی لیستها، و نحوهی افزایشِ انعطافپذیریِ توابع با تنوع دادن به ورودیهاشون پیدا کنین. اگه بگردین جوابِ این تمرینها هم رو اینترنت پیدا میشه، ولی شما اهل این کارا نیستین، چون نمیخواین خودتون رو گول بزنین. مگه نه؟
با یه مثال نشون میدیم که دنبال چی هستیم. تابعِ and
یه لیستِ Bool
میگیره* و اگر و فقط اگر هیچ کدوم از مقادیرِ لیست False
نباشن، True
برمیگردونه. نسخهی خودتون رو میشه اینطور بنویسین:
-- (&&) خوداتکاییِ مستقیم، بدون استفاده از
myAnd :: [Bool] -> Bool
myAnd [] = True
myAnd (x:xs) =
if x == False
then False
else myAnd xs
-- (&&) مستقیم، با استفاده از recursion
myAnd :: [Bool] -> Bool
myAnd [] = True
myAnd (x:xs) = x && myAnd xs
دقت کنین اگه از 7.10 GHCi یا جدیدتر استفاده میکنین، توابعِ and
، any
، و all
انتزاعیتر شدن و علاوه بر لیستها، با هر نوعدادهای که یه نمونه از تایپکلاسِ Foldable
داشته باشه کار میکنن. با خیال راحت ادامه بدین چون بعداً به Foldable
میرسیم.
حالا خوش بگذرونیم:
۱.
تابعِ myOr
، اگه حداقل یک مقدارِ Bool
در لیستِ ورودی True
باشه، باید True
برگردونه.
myOr :: [Bool] -> Bool
myOr = undefined
۲.
تابعِ myAny
در صورتی True
بر میگردونه که از اعمال ِ a -> Bool
به حداقل یکی از المانهای لیست، جوابِ True
بدست بیاد.
myAny :: (a -> Bool) -> [a] -> Bool
myAny = undefined
چندتا مثال برای تست کردنِ myAny
:
Prelude> myAny even [1, 3, 5]
False
Prelude> myAny odd [1, 3, 5]
True
۳.
بعد از نوشتن تابعِ بازگشتی ِ myElem
، یه نسخهی دیگه ازش بنویسین که از any
استفاده کنه. تابعِ elem
که در 7.10 GHCi و جدیدتر تعریف شده، بجای اینکه فقط با لیست کار کنه، با Foldable
کار میکنه. میتونین اون رو نادیده بگیرین و نسخهی خودتون رو فقط برای لیست بنویسین.
myElem :: Eq a => a -> [a] -> Bool
myElem = undefined
Prelude> myElem 1 [1..10]
True
Prelude> myElem 1 [2..10]
False
۴.
تابعِ myReverse
رو تعریف کنین.
myReverse :: [a] -> [a]
myReverse = undefined
Prelude> myReverse "blah"
"halb"
Prelude> myReverse [1..5]
[5,4,3,2,1]
۵.
تابعِ squish
یه لیستِ لیست رو به یک لیست تبدیل میکنه.
squish :: [[a]] -> [a]
squish = undefined
۶.
تابعِ squishMap
یه تابع رو به یک لیست نگاشت میده و نتیجههاشون رو الحاق میکنه.
squishMap :: (a -> [b]) -> [a] -> [b]
squishMap = undefined
Prelude> squishMap (\x -> [1, x, 3]) [2]
Prelude> [1,2,3]
Prelude> :{
Prelude| squishMap
Prelude| (\x -> "WO "++[x]++" HOO ") "123"
Prelude| :}
"WO 1 HOO WO 2 HOO WO 3 HOO "
۷.
تابعِ squishAgain
یه لیستِ لیست رو به یک لیست لِه میکنه. این بار از تابعِ squishMap
استفاده کنین.
squishAgain :: [[a]] -> [a]
squishAgain = undefined
۸.
تابعِ myMaximumBy
یک تابعِ مقایسه و یک لیست میگیره، و بر مبنای آخرین مقداری که از مقایسه خروجیِ GT
میده، بزرگترین المان لیست رو برمیگردونه. اگه maximumBy
رو از Data.List
وارد کنین و تایپش رو استعلام کنین، بجای تایپِ:
(a -> a -> Ordering) -> [a] -> a
چنین تایپی میبینین:
Foldable t
=> (a -> a -> Ordering) -> t a -> a
شما هم میتونین با []
بنویسین:
myMaximumBy :: (a -> a -> Ordering)
-> [a] -> a
myMaximumBy = undefined
Prelude> let xs = [1, 53, 9001, 10]
Prelude> myMaximumBy compare xs
9001
۹.
تابعِ myMinimumBy
یک تابعِ مقایسه و یک لیست میگیره، و بر مبنای آخرین مقداری که از مقایسه خروجی LT
میده، کوچکترین المان لیست رو برمیگردونه.
myMinimumBy :: (a -> a -> Ordering)
-> [a] -> a
myMinimumBy = undefined
Prelude> let xs = [1, 53, 9001, 10]
Prelude> myMinimumBy compare xs
1
۱۰.
با توابعِ myMinimumBy
و myMaximumBy
، تابعهای minimum
و maximum
ِ خودتون رو بنویسین. مثل خیلی از تابعهایی که بالاتر دیدیم، این دو تا تابع هم در 7.10 GHCi یا جدیدتر، بجای لیست، با همهی نوعسازهایی که یه نمونه از تایپکلاسِ Foldable
دارن کار میکنن.
myMaximum :: (Ord a) => [a] -> a
myMaximum = undefined
myMinimum :: (Ord a) => [a] -> a
myMinimum = undefined