۱۱ - ۱۸تمرین‌های فصل

تستی

۱.

با توجه به نوع‌داده ِ زیر:

data Weekday =
    Monday
  | Tuesday
  | Wednesday
  | Thursday
  | Friday

میشه گفت ‏‎Weekday‎‏:

a)

یه تایپ با پنج داده‌ساز ِه

b)

یه درخت با پنج شاخه هست

c)

یه تایپ ضرب ِه

d)

پنج آرگومان می‌گیره

۲.

باز هم با توجه به نوع‌داده ِ بالا، تایپِ تابعِ زیر چیه؟

f Friday = "Miller Time"

a)

f :: [Char]

b)

f :: String -> String

c)

f :: Weekday -> String

d)

f :: Day -> Beer

۳.

تایپ‌هایی که با کلیدواژه ِ ‏‎data‎‏ تعریف میشن

a)

باید حداقل یک آرگومان داشته باشن

b)

باید با حرف بزرگ شروع بشن

c)

باید پلی‌مورفیک باشن

d)

نمیشه از ماژول‌ها وارد بشن

۴.

تابعِ ‏‎g xs = xs !! (length xs – 1)‎‏

a)

بازگشتی ِه و هیچ وقت خاتمه پیدا نمی‌کنه

b)

سَر ِ ‏‎xs‎‏ رو برمی‌گردونه

c)

المانِ آخرِ ‏‎xs‎‏ رو میده

d)

با ‏‎xs‎‏ تایپِ یکسانی داره

رمزنگارها

در فصلِ لیست‌ها یه رمزنگارِ سزار نوشتین. حالا می‌خوایم اون تمرین رو گسترش بدیم و یه رمزنگارِ ویژنر بنویسیم. رمزنگارِ ویژنر هم یه رمزنگارِ جایگزینی بر پایه‌ی رمزنگارِ سزار ِه. این رمزنگار در واقع یک سری رمزنگارِ سزار ِه که جایگزینی ِ هر حرف بر مبنای یه کلیدواژه ِ ثابت صورت می‌گیره.

مثلاً اگه بخواین پیغامِ meet at dawn (ملاقات در طلوع) رو رمزنگاری کنین، اولین قدم انتخاب یه کلیدواژه هست که تعیین می‌کنه از کدوم رمزنگارِ سزار استفاده بشه. اینجا از کلیدواژه ِ ALLY استفاده می‌کنیم. انقدر این لغت رو تکرار می‌کنیم تا تعداد حروف‌ش برابرِ تعدادِ حروفِ پیغام‌مون بشه:

MEET AT DAWN
ALLY AL LYAL

حالا میزان جابجایی به راستِ هر حرف توسط حرفی از کلیدواژه که باهاش همراستا شده تعیین میشه. حرف A یعنی حرکت به جلو به تعداد صفر حرف، پس M ِ اول M می‌مونه. اما حرف L یعنی ۱۱ حرف بریم جلو، پس E میشه P. و الی آخر، پس meet at dawn با کلیدواژه ِ ALLY تبدیل به پیغامِ MPPR AE OYWY میشه.

مثل رمزنگارِ سزار، منابع زیادی روی اینترنت برای یاد گیری هست، نمونه‌های نوشته شده با هسکل هم وجود دارن. باز هم ترکیبی از توابعِ ‏‎chr‎‏، ‏‎ord‎‏، و ‏‎mod‎‏ رو مدنظر داشته باشین. حل این تمرین احتمالاً خیلی شبیه تمرینِ سزار میشه.

الگوهای as

الگوهای as در هسکل، روش جالبی در کنارِ تطبیق الگو هستن. با این روش میشه علاوه بر تطبیقِ الگو روی بخشی از یه مقدار، کلِ اون مقدار رو هم به یه متغیر انقیاد داد. چند مثال:

f :: Show a => (a, b) -> IO (a, b)
f t@(a, _) = do
  print a
  print t

اینجا روی یه توپل تطبیق الگو انجام دادیم تا مقدار اول‌ش رو برای چاپ بگیریم، اما با استفاده از ‏‎@‎‏، کلِ توپل رو هم به متغیرِ ‏‎t‎‏ انقیاد دادیم.

Prelude> f (1, 2)
1
(1,2)

برای هر ساختار داده ای از الگوهای as در تطبیق الگو میشه استفاده کرد. یه مثال برای لیست:

doubleUp :: [a] -> [a]
doubleUp [] = []
doubleUp xs@(x:_) = x : xs
Prelude> doubleUp []
[]
Prelude> doubleUp [1]
[1,1]
Prelude> doubleUp [1, 2]
[1,1,2]
Prelude> doubleUp [1, 2, 3]
[1,1,2,3]

در تعریف توابع زیر از الگوهای as استفاده کنین:

۱.

این تابع اگر (و فقط اگر) تمامی مقادیر در لیست اول، در لیست دوم وجود داشته باشن ‏‎True‎‏ برمی‌گردونه. المان‌های لیستِ اول لازم نیست در لیستِ دوم کنارِ هم باشن.

isSubseqOf :: (Eq a)
           => [a]
           -> [a]
           -> Bool

مثال‌های زیر طرزِ کارِ این تابع رو نشون میدن:

Prelude> isSubseqOf "blah" "blahwoot"
True
Prelude> isSubseqOf "blah" "wootblah"
True
Prelude> isSubseqOf "blah" "wboloath"
True
Prelude> isSubseqOf "blah" "wootbla"
False
Prelude> isSubseqOf "blah" "halbwoot"
False
Prelude> isSubseqOf "blah" "blawhoot"
True 

دقت کنین ترتیب حروف باید حفظ بشه!

۲.

یه جمله رو به لغات‌ش تقسیم کنین و بعد هر لغت رو با نسخه‌ای از همون لغت که حرف اول‌ش بزرگ شده در یه توپل قرار بدین.

capitalizeWords :: String
                -> [(String, String)]
Prelude> capitalizeWords "hello world"
[("hello","Hello"), ("world","World")]

تمرین‌های زبان

۱.

تابعی بنویسین که حرفِ اولِ یه کلمه‌ رو بزرگ می‌کنه:

capitalizeWord :: String -> String
capitalizeWord = undefined
Prelude> capitalizeWord "Chortle"
"Chortle"
Prelude> capitalizeWord "chortle"
"Chortle"

۲.

تابعی بنویسین که حرفِ اولِ جملاتِ یه پاراگراف رو بزرگ می‌کنه. این تابع اولِ یه جمله رو با پیدا کردن نقطه تشخیص میده. از تابعِ ‏‎capitalizeWord‎‏ استفاده کنین.

capitalizeParagraph :: String -> String
capitalizeParagraph = undefined
Prelude> let s = "blah. woot ha."
Prelude> capitalizeParagraph s
"Blah. Woot ha."

تمرین تلفن

این تمرین اصالتاً توسطِ geophf برای 1HaskellADay نوشته شده. مرسی که اجازه دادی ازش استفاده کنیم!

یادتون میاد قدیما واسه اس‌ام‌اس دادن باید هر دکمه‌ی موبایل رو چند بار میزدین تا حرف‌های مختلف رو بنویسین؟ شاید هنوز با بعضی دستگاه‌ها این کار لازم باشه. برای این تمرین باید تابع‌هایی بنویسین که سری فشردن دکمه‌ها رو به نوشته، و برعکس‌ش تبدیل کنن.

خوب! اینها دکمه‌های تلفن‌اند:

---------------------------------------
|   1        |   2 ABC    |  3 DEF    |
---------------------------------------
|   4 GHI    |   5 JKL    |  6 MNO    |
---------------------------------------
|   7 PQRS   |   8 TUV    |  9 WXYZ   |
---------------------------------------
|   * ^      |   0 + _    |  # .,     |
---------------------------------------

وقتی به دوستاتون اس‌ام‌اس میدین، کاراکترِ ‏‎*‎‏ حرف بعدی رو بزرگ می‌کنه و ‏‎0‎‏ هم معادلِ فاصله هست. برای نوشتن خود اون عدد هم، یه بار دیگه بیشتر از حروفی که داره باید فشرده بشه. اگر هم بیشتر از اون دکمه رو بزنین، برمی‌گرده از اول. برای مثال:

2     -> 'a'
22    -> 'b'
222   -> 'c'
2222  -> '2'
22222 -> 'a'

ببینیم چطور میشه.

۱.

یه ساختار داده درست کنین که همون طرح‌بندی تلفن رو ارائه بده. این ساختار داده باید اونقدر گویا باشه که برای توابعِ زیر قابل استفاده بشه.

-- تکمیل کنین
data DaPhone = DaPhone

۲.

مکالمات زیر رو به دکمه‌ها و تعداد دفعات فشرده شدن‌شون تبدیل کنین. تایپ‌ها و توابعی رو برای راهنمایی پیشنهاد کردیم، ولی اگه دوست داشتین با رَوِشِ خودتون پیش برین.

convo :: [String]
convo =
  ["Wanna play 20 questions",
   "Ya",
   "U 1st haha",
   "Lol ok. Have u ever tasted avocado",
   "Lol ya",
   "Wow ur cool haha. Ur turn",
   "Ok. Do u think I am funny Lol",
   "Lol ya",
   "Just making sure rofl ur turn"]

-- ‎‏دکمه‌های قابل قبول‏‎ = "1234567890*#"
type Digit = Char

-- های قابل قبول press ‎‏تعداد‏‎ : ‎‏و بیشتر‏‎ 1
type Presses = Int

reverseTaps :: DaPhone
            -> Char
            -> [(Digit, Presses)]
reverseTaps = undefined
-- با فرض اون تلفنی که کشیدیم
-- 'a' -> [('2', 1)]
-- 'A' -> [('*', 1), ('2', 1)]

cellPhonesDead :: DaPhone
               -> String
               -> [(Digit, Presses)]
cellPhonesDead = undefined

۳.

برای هر پیغام، اعداد باید چند بار فشرده بشن؟

fingerTaps :: [(Digit, Presses)] -> Presses
fingerTaps = undefined

۴.

در هر پیغام کدوم حرف از همه بیشتر استفاده شده بود؟ خرج‌ش چقدر بود؟ برای پیدا کردن تعداد دفعات فشرده شدن که خرج‌ش شده، می‌تونین ‏‎reverseTaps‎‏ و ‏‎fingerTaps‎‏ رو ترکیب کنین. ‏‎reverseTaps‎‏ لیست میده، چون برای حروف بزرگ باید دو تا دکمه رو فشار داد.

mostPopularLetter :: String -> Char
mostPopularLetter = undefined

۵.

در کلِ مکالمه چطور؟ کدوم حرف از همه محبوب‌تر بود؟ محبوب‌ترین لغت کدوم بود؟

coolestLtr :: [String] -> Char
coolestLtr = undefined

coolestWord :: [String] -> String
coolestWord = undefined

تیغِ هاتِن

تیغ هاتن یه زبانِ اکسپرشن ِ ساده برای بیان لفظهای اعداد صحیح و جمع مقادیر اونهاست. "کلَکِ" این زبان بازگشتی بودن‌ه، یعنی بیانیه‌هایی که با هم جمع میشن ممکنه هر کدوم خودشون لفظ باشن، یا باز هم یه عمل جمع دیگه. چنین نوع‌داده‌هایی بین زبان‌های اِکسپرشِنی که در مقالات علمی و مثال‌های تابعی نوشته میشن، خیلی رایج ِه. در هر صورت بیشترِ وقتها در برنامه‌نویسی، یه نوع‌داده رو محاسبه یا فولد می‌کنین.

۱.

اول از همه یه تابعِ ‏‎eval‎‏ (م. مخفف evaluate به معنای محاسبه) بنویسین که یه بیانیه رو به یه مجموع ِ نهایی تبدیل می‌کنه.

data Expr
  = Lit Integer
  | Add Expr Expr

eval :: Expr -> Integer
eval = error "do it to it"

نمونه‌ی خروجی:

Prelude> eval (Add (Lit 1) (Lit 9001))
9002

۲.

یه چاپگر برای اکسپرشن‌ها بنویسین.

printExpr :: Exprt -> String
printExpr = undefined

خروجی مورد نظر:

Prelude> printExpr (Add (Lit 1) (Lit 9001))
"1 + 9001"
Prelude> let a1 = Add (Lit 9001) (Lit 1)
Prelude> let a2 = Add a1 (Lit 20001)
Prelude> let a3 = Add (Lit 1) a2
Prelude> printExpr a3
"1 + 9001 + 1 + 20001"