Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Solutions for Lecture 2 exercises #2

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

kpadmasola
Copy link
Owner

Solutions for Lecture 2

cc @chshersh @vrom911

Copy link

@chshersh chshersh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huge congratulations! You've completed the second homework 👏🏼
This homework has some rather difficult exercises but you managed to overcome these challenges! 🥳
Keep going! The first and the most difficult half of the course is done 💪

Comment on lines +54 to +55
lazyProduct [] = 1
lazyProduct list = go 1 list

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks to me that the first case is redundant because your helper function go will return 1 immediately if the given list is empty.

After you remove this case, you can also eta-reduce the top-level function ✂️

Suggested change
lazyProduct [] = 1
lazyProduct list = go 1 list
lazyProduct = go 1

Comment on lines +58 to +60
go 0 _ = 0
go result [] = result
go result (x : xs) = go (result * x) xs

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a very clever solution! 🧠
I'm not sure if you know about BangPatterns or strict evaluations in Haskell (I'm going to cover them tomorrow in Lecture 3) but your solution also has the optimal performance behaviour 😅

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks :-) In the past, I have mostly been reading about Haskell, haven't written any code. I have come across BangPatterns and strict evaluation in my readings, but never applied them as I've not actually written any Haskell code.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

by the way @chshersh curious, how do you get these cool emojis in your comments?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kpadmasola You can start typing : and then the name of an emoji. GitHub has some autocompletion for emoji names 🙂 Like :smile or :+1.

Comment on lines +89 to +90
headPart = take index list
(element, tailPart) = case drop index list of

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a correct implementation 👍🏻
Alternatively, you can use a standard function splitAt instead of using take and drop to avoid traversing the list prefix twice 🔃

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point.

@@ -97,7 +117,8 @@ spaces.

🕯 HINT: look into Data.Char and Prelude modules for functions you may use.
-}
dropSpaces = error "TODO"
dropSpaces :: String -> String
dropSpaces = takeWhile (not . isSpace) . dropWhile isSpace

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perfect 🏆

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks !

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chshersh One doubt I have is, how does this efficiently handle infinite trailing spaces? I noticed that there is a testcase for that. Without looking at all the infinite trailing spaces, how can this work? Is it some kind of fusion or something like that?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no special magic 🙂
takeWhile will just stop on the first space character and won't traverse the list any further. It works due to laziness.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, thanks!


data Treasure
= Gold
| GoldPlusTreasure

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I clarified the task a bit to cover cases so dragons would determine the treasure itself, not just "yes" or "no". It will make the implementation a bit more challenging 😅

But, besides that, the current implementation covers the requirements nicely 👍🏻

Comment on lines +259 to +261
merge (x : xs) (y : ys)
| x < y = x : merge xs (y : ys)
| otherwise = y : merge (x : xs) ys

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolutely correct solution! 💎
You can make it more performant by checking if both heads of the given lists are equal. In that case, you're able to reduce the size of each list by 1 on each recursive call instead of reducing the size of only a single list ✂️

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, thanks!

mergeSort [x] = [x]
mergeSort xs = merge (sort ps) (sort qs)
where
(ps, qs) = splitAt (length xs `div` 2) xs

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use splitAt instead of take + drop to avoid traversing the first half twice.

Additionally, you can avoid using length if you notice that you don't necessarily need to split in the middle. You only need to split it into two lists of approximately the same size.

In that case, you can implement a solution that traverses a list only once and splits its elements into two lists of elements on only even positions and only odd positions.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I need to spend some more thought on this...

Comment on lines +340 to +343
addExpr :: Either EvalError Int -> Either EvalError Int -> Either EvalError Int
addExpr (Left x) _ = Left x
addExpr _ (Left x) = Left x
addExpr (Right x) (Right y) = Right (x + y)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a great helper function! 👏🏻
Once you learn about Monads, you'll be able to reduce this function to a single line ✂️

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

(y1, y2) = extractConstant y
addExpr :: ([Int], [Expr]) -> Expr
addExpr (c, e) = case (c, e) of
([], []) -> error "invalid"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting! You can see that it's actually impossible to have both lists empty at the same time. So here you're handling and impossible situation 🙂

Can you maybe change types a bit to avoid having this error? 😉

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chshersh :-) I know you mean "making invalid states unrepresentable", need to think about this some more ...

Comment on lines +380 to +386
([], ys) -> addVars ys
(xs, ys) -> if sum xs == 0 then addVars ys else Add (Lit (sum xs)) (addVars ys)
addVars :: [Expr] -> Expr
addVars [] = error "Invalid"
addVars [x] = x
addVars [x, y] = Add x y
addVars (x : y : rest) = Add (Add x y) (addVars rest)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can get rid of handling this impossible case too. In the pair you already pattern matched on the list, so you know that it's not empty. So yo can pass it's first element and the rest to the function and continue doing this recursively to avoid throwing error

Suggested change
([], ys) -> addVars ys
(xs, ys) -> if sum xs == 0 then addVars ys else Add (Lit (sum xs)) (addVars ys)
addVars :: [Expr] -> Expr
addVars [] = error "Invalid"
addVars [x] = x
addVars [x, y] = Add x y
addVars (x : y : rest) = Add (Add x y) (addVars rest)
([], y : ys) -> addVars y ys
(xs, ys) -> if sum xs == 0 then addVars ys else Add (Lit (sum xs)) (addVars ys)
addVars :: Expr -> [Expr] -> Expr
addVars e [] = e
addVars e (x : xs) = ...

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @chshersh , will need to think about this some more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants