Last week, we looked at List.tryHead
. If you don’t need the first item in a list, but rather the last item, the counterpart of List.head
is List.last
. Likewise, the counterpart of List.tryHead
is List.tryLast
.
Recall the code example from last week:
type Salesman = { Name : string Sales : decimal } let findTopSalesman salesmen = salesmen |> List.filter (fun s -> s.Sales >= 10000M) |> List.sortBy (fun s -> -s.Sales) // descending |> List.tryHead let sales = [ { Name = "Joe Bob"; Sales = 9500M } { Name = "Sally Jane"; Sales = 18500M } { Name = "Betty Lou"; Sales = 11800M } { Name = "Sammy Joe"; Sales = 6500M } ] let top = findTopSalesman sales // val top : Salesman option = // Some {Name = "Sally Jane"; // Sales = 18500M;}
The List.sortBy
call (highlighted above) sorts the sales records in a descending fashion so that the first record would be the top salesman. What if it makes more sense to you to sort the records in an ascending fashion and take the last record? With List.tryLast
, you can:
type Salesman = { Name : string Sales : decimal } let findTopSalesman salesmen = salesmen |> List.filter (fun s -> s.Sales >= 10000M) |> List.sortBy (fun s -> s.Sales) // ascending |> List.tryLast let sales = [ { Name = "Joe Bob"; Sales = 9500M } { Name = "Sally Jane"; Sales = 18500M } { Name = "Betty Lou"; Sales = 11800M } { Name = "Sammy Joe"; Sales = 6500M } ] let top = findTopSalesman sales // val top : Salesman option = // Some {Name = "Sally Jane"; // Sales = 18500M;}
This is a trivial example: I mean, you can sort the records however you wish; they’re your records! But what if you receive the recordset from elsewhere—an API that is outside your control, for instance—and it is already sorted in an ascending fashion. It is probably better to accept the recordset as is and just take the last item rather than to sort it again. Which brings me to another couple of other points …
This post claims to be about Array.last
and Array.tryLast
. Why all the talk about List.tryLast
?
First, one of the things the F# team did in F# 4.0 was to normalize the sequential collections modules. Now just about any function available in one sequential collection module is available in them all. That is, if there’s a List.last
, for example, then there’s also a Seq.last
and an Array.last
.
Second, I want to point out a potential pitfall of using last
and tryLast
. Both the size and type of the collection can affect the performance of your program or even crash it.
Arrays give you O(1) access to their elements. (In case you’re not familiar with it, that’s called “Big O notation.” It’s a way of expressing how long an algorithm takes to execute.) That is, arrays give you nearly instant access to any element—first, last, somewhere in the middle—doesn’t matter.
Lists and sequences, on the other hand, give you O(n) access to their elements. That is, the more items in the list/sequence, the longer it takes to get to the one you want because the machine always has to start at the first element and iterate through every single one until it gets to the one you want. No big deal if there are only 100 elements, but if there are 10,000,000 elements, fetching the last element will take a while.
Furthermore, sequences can be infinite. If you call Seq.last
or Seq.tryLast
on an infinite sequence, your program will crash:
let init n = n + 1 // This sequence starts at one and just keeps going let ns = Seq.initInfinite init let notGood = ns |> Seq.last // Unhandled Exception: System.InvalidOperationException: // Enumeration based on System.Int32 exceeded // System.Int32.MaxValue.
You don’t have to eschew last
and tryLast
. Just take into account what kind of collection you’re calling them on. Array.last
and Array.tryLast
are perfectly safe. (Well, do remember that Array.last
throws an exception if the array is empty, but with regard to performance, they’re fine.) But before you call last
or tryLast
on a list or a sequence, make sure you know how big it is, or you could, as they say, shoot yourself in the foot.
3 replies on “F# Friday – Array.last and Array.tryLast”
[…] F# Friday – Array.last and Array.tryLast – Brad Collins […]
New to scala.
Plz tell me i want to fetch 1st and second value from RDD or list.help me.
@Scalalearner, I’m not sure what an “RDD” is, but you could get the first & second items from a list this way:
That’s only safe if you know that list has at least two elements, by the way. Otherwise, you’ll get an exception, either by attempting to fetch the head of an empty list, or by attempting the fetch the head of a single-element list’s tail (which is itself an empty list).
One approach you could use to mitigate this risk is pattern matching: