۹ - ۱۲تمرینهای فصل
بخش اول این تمرینها بیشتر جنبهی دورهای داره، اما چندتا چیز جدید هم معرفی میکنه. بخش دوم ممکنه یه کم چالش برانگیز باشه، اما از هیچ مبحث یا گرامر ای که قبلاً نگفته باشیم استفاده نمیکنه. اگه به مشکل خوردین، شاید با دورهی بخش مربوطه مشکلتون برطرف شه.
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 = undefinedPrelude> myElem 1 [1..10]
True
Prelude> myElem 1 [2..10]
False۴.
تابعِ myReverse رو تعریف کنین.
myReverse :: [a] -> [a]
myReverse = undefinedPrelude> 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 = undefinedPrelude> 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 = undefinedPrelude> let xs = [1, 53, 9001, 10]
Prelude> myMaximumBy compare xs
9001۹.
تابعِ myMinimumBy یک تابعِ مقایسه و یک لیست میگیره، و بر مبنای آخرین مقداری که از مقایسه خروجی LT میده، کوچکترین المان لیست رو برمیگردونه.
myMinimumBy :: (a -> a -> Ordering)
-> [a] -> a
myMinimumBy = undefinedPrelude> 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