Math-y List Operations
Earlier this month we explored some functions related to booleans and lists. Today we'll consider a few simple helpers related to lists of numbers.
As in Python, Haskell allows you to easily take the product and sum of a list. These functions are very straightforward:
sum :: (Num a) => [a] -> a
product :: (Num a) => [a] -> a
...
>> sum [1, 2, 3, 4]
10
>> sum [-1, 1]
0
>> product [1, 2, 3, 4]
24
>> product [5, 6, -2]
-60
As with our boolean functions, I originally gave the type signatures in terms of lists, but they actually work for any "Foldable" type. So both the inner and outer type of our input are parameterized.
sum :: (Foldable t, Num a) => t a -> a
product :: (Foldable t, Num a) => t a -> a
...
>> sum $ Set.fromList [1, 2, 3]
6
Both of these functions also work with empty lists, since these operations have an identity to start with.
>> sum []
0
>> product []
1
The same cannot be said for the functions minimum
and maximum
. These require non-empty lists, or they will throw an error!
minimum :: (Foldable t, Ord a) => t a -> a
maximum :: (Foldable t, Ord a) => t a -> a
...
>> maximum [5, 6, -1, 4]
6
>> minimum [5, 6, -1, 4]
-1
>> maximum []
*** Exception: Prelude.maximum: empty list
And while they can be used with numbers, as above, they can also be used with any orderable type, such as strings.
>> maximum ["hello", "world"]
"world"
Remember what we mentioned a while ago with groupBy
and how many functions have another version with By
? This applies for our min and max functions. These allow us to provide a custom comparison operation.
maximumBy :: (Foldable t) => (a -> a -> Ordering) -> t a -> a
minimumBy :: (Foldable t) => (a -> a -> Ordering) -> t a -> a
As an example, let's consider that when comparing tuples, the first element is used to sort them. Only if there is a tie to we go to the second element.:
>> maximum [(1, 4), (2, 1), (2, 3)]
(2, 3)
However, we can instead use the second element as the primary comparison like so:
>> let f (x1, x2) (y1, y2) = if x2 == y2 then x1 `compare` y1 else x2 `compare` y2
>> maximumBy f [(1, 4), (2, 1), (2, 3)]
(1, 4)
>> minimumBy f [(1, 4), (2, 1), (2, 3)]
(2, 1)
This approach is also very helpful when you're dealing with more complex types, but you only care about comparing a single field.
As a final note, sort
and sortBy
follow this same pattern. The first one assumes an ordering property, the second allows you a custom ordering.
sort :: (Ord a) => [a] -> [a]
sortBy :: (a -> a -> Ordering) -> [a] -> [a]
For more tricks like this, keep following the Monday Morning Haskell blog every Monday and Thursday! You can subscribe to get our monthly newsletter as a summary. This will also get you access to subscriber resources like our Beginners Checklist.