I live-coded Haskell a couple of times at job interviews for non-Haskell positions where you were free to choose.
It’s fun because the interviewer often doesn’t grok the paradigm. In one case, I was asked to make a password generator, and I went with QuickCheck’s Gen, which lets me write a random generator of things without explicitly defining a single function (they’re all monads).
So you go from somehow describing the thing you want to make to “I’m done.” :-D
I remember I did an Advent of Code day 1 in Haskell a while back.
The meat of the code was a one liner. But opening a file, throwing away the first line and parsing a CSV took a lot more head scratching, especially if you've only ever done puzzles in Haskell.
Makes the hard things easy and the easy things hard
Same. Until I discovered those parser combinator libraries which are THE way to do parsing in Haskell. It makes parsing so pleasant you'll never need a regex again. A parser for that CSV file probably looks like this:
line = decimal `sepBy` char ','
file = line `sepBy` newline
Thanks for sharing! This was in a pre-LLM era and I couldn't be bothered to research the right way to do this. Those parser combinators are excellent, so clean
Always fun to see this kind of Rosetta Code thing, feel like no matter the topic there's something to glean from reading!
That said: if I ever gave the palindrome question to a candidate (I haven't) and they pulled out a `reverse` library func/keyword we'd be encouraging them to do more.
Haskell's default `String` type is a singly-linked list of `Char` values[0] so it takes O(n) time to access the end of the string. Hence I think the solution using `reverse` is probably the best there is, since any attempt to be "smarter" would just be more complicated for no real performance gain. Though I agree that we could ask someone to also write their own reverse function.
Yeah spot on - exactly what I was aiming at too...
Having the intuition to use reverse (& pragmatism to keep it simple) is a great sign, and realistically more valuable [to me] for hiring than implementing rudimentary algorithms, but... still want at least a bit of both abilities
Also knowledge of standard library / language features is useful but (for me) doesn't equate to abilities in complex problem solving.
Typical Haskell coding post with no mention of order or efficiency of the algorithm solutions. The thing is, even in basic coding quizes, if the interviewer is worth their salt, they will ask about how to improve the efficiency of the naive solutions first put forward, like the palindrome answer in this post.
It does discuss performance and points out some places where the Haskell implementation makes the natural naive solutions performant.
> While it's true that sort has an O(nlogn) performance profile, one interesting thing here is that sorting is lazy in Haskell! This means that if our two strings are unequal, they will only be sorted far enough to determine inequality!
I don't understand exactly how this works - don't all O(n log n) sorting algorithms still have to do some work on the other elements of the list before pulling out the smallest one? If you just scanned and pulled the smallest one to the front each time that is O(n^2). And there's an implementation detail here that differences at the start of the sorted string will be caught more quickly than at the end, so real-world behaviour is dependent on your choice of sort order.
Agreed that the performance analysis is quite superficial and that some of the solutions like sum NToTotal must be very sub-optimal.
As much as I find Haskell elegant for many things, I have to agree. It reminds me of the infamous Haskell "quicksort" which isn't actually quicksort because it's not in place.
I think not every piece of code needs to be hyper-optimised and in those cases, higher-level (declarative) languages where you don't specify every detail of the algorithm can give rise to very elegant code. But writing very performant Haskell code is a bit trickier.
In the anagram case, there's a straightforward O(n) solution where you count the occurrence of each letter in a hash map for both words and compare those. That can be written in Haskell too, of course, but it's not going to be as short.
The matchesSum check is constant in the length of the input list. The filter adds an iteration over all results of (combinations n xs). If the complexity of (combinations n) is T(n) in xs, then by analogy of its recursive definition we have two recursive calls that are T(n-1), we have an fmap that is O(1) and a concatenation that is O(1). That sets up the recurrence T(n) = 2T(n-1) making the (combinations n) function O(n) in xs.
Thus the whole thing is O(n²). It's not like this is particularly hard for Haskell. If anything, it's often easier because we can set up the recurrence by analogy with the definition.
can you not narrow it down further to O(k * nCr(n, k)) (n=size of input, k=combination size) since it does k conses for each of the nCr(n, k) combinations?
(the final filter sums k elements nCr(n, k) times as well)
how is combinations O(n)? for n choose 3 generating combinations is going to approach n^3 since the formula is basically (n * n * n) / constant and for sumToAny generating all the combinations is 2^n.
The execution order, or the order of the output list? The latter should be self-evident from the function body (first elements get collected first), and the former holds no tricks either since filters are evaluated from the head first, and concat (<>) allows lazy access to the first list's elements.
This is just a beginner's Haskell post. If an interviewer really meant to test optimization knowledge, I would hope they wouldn't ask me to optimize one O(n) utility function into another.
with N as the length of the subsets and X as the length of input, `combinations n xs` basically walks through list xs and at each element, conses that element onto each combination of size n-1 of later elements in the list then appends those resulting lists together. this is on the order of N * nCr(X, N) aka O(NX!/(N!(X-N)!)) which dominates the overall runtime. the final filter walks through each combination and does O(N) work at each element to find the ones that sum to the desired amount, again O(NX!/(N!(X-N)!))
Well, that fizlle implementation is not a good example of a great answer. It checks for the two conditions twice. At least I would use a where clause to define fizz and buzz.
That doesn't handle all the cases: what about fizz buzz bazz?
Added: here's my version, which maybe could be cleaned up further. The spaces and exclamation point make the problem trickier.
{-# LANGUAGE MonadComprehensions #-}
import Data.Maybe (catMaybes)
import Data.List (intercalate)
fizz :: Int -> String
fizz n = case zub of
[] -> show n
otherwise -> intercalate " " zub ++ "!"
where
f :: Int -> String -> Maybe String
f d msg = [msg | n`rem`d == 0]
zub = catMaybes . map (uncurry f)
$ [(3,"Fizz"), (5,"Buzz"), (7, "Bazz")]
main = mapM_ putStrLn [fizz n | n <- [1..110]]
It’s fun because the interviewer often doesn’t grok the paradigm. In one case, I was asked to make a password generator, and I went with QuickCheck’s Gen, which lets me write a random generator of things without explicitly defining a single function (they’re all monads).
So you go from somehow describing the thing you want to make to “I’m done.” :-D
I also refused to solve a part of the task because it would lead to bad password policy. Instead I property-tested the generator.
The meat of the code was a one liner. But opening a file, throwing away the first line and parsing a CSV took a lot more head scratching, especially if you've only ever done puzzles in Haskell.
Makes the hard things easy and the easy things hard
That said: if I ever gave the palindrome question to a candidate (I haven't) and they pulled out a `reverse` library func/keyword we'd be encouraging them to do more.
[0] https://hackage.haskell.org/package/base-4.21.0.0/docs/Prelu...
Yeah spot on - exactly what I was aiming at too...
Having the intuition to use reverse (& pragmatism to keep it simple) is a great sign, and realistically more valuable [to me] for hiring than implementing rudimentary algorithms, but... still want at least a bit of both abilities
Also knowledge of standard library / language features is useful but (for me) doesn't equate to abilities in complex problem solving.
But honestly could be bias :)
What is the order of sumNToTotal?
> While it's true that sort has an O(nlogn) performance profile, one interesting thing here is that sorting is lazy in Haskell! This means that if our two strings are unequal, they will only be sorted far enough to determine inequality!
I don't understand exactly how this works - don't all O(n log n) sorting algorithms still have to do some work on the other elements of the list before pulling out the smallest one? If you just scanned and pulled the smallest one to the front each time that is O(n^2). And there's an implementation detail here that differences at the start of the sorted string will be caught more quickly than at the end, so real-world behaviour is dependent on your choice of sort order.
Agreed that the performance analysis is quite superficial and that some of the solutions like sum NToTotal must be very sub-optimal.
I think not every piece of code needs to be hyper-optimised and in those cases, higher-level (declarative) languages where you don't specify every detail of the algorithm can give rise to very elegant code. But writing very performant Haskell code is a bit trickier.
In the anagram case, there's a straightforward O(n) solution where you count the occurrence of each letter in a hash map for both words and compare those. That can be written in Haskell too, of course, but it's not going to be as short.
Thus the whole thing is O(n²). It's not like this is particularly hard for Haskell. If anything, it's often easier because we can set up the recurrence by analogy with the definition.
(the final filter sums k elements nCr(n, k) times as well)
This is just a beginner's Haskell post. If an interviewer really meant to test optimization knowledge, I would hope they wouldn't ask me to optimize one O(n) utility function into another.
Deleted Comment
Added: here's my version, which maybe could be cleaned up further. The spaces and exclamation point make the problem trickier.
Well, it's not clear to me why we're terming these as silly. They mostly seem simple, instead, to me.
Deleted Comment