Here's more idiomatic way to do that. Actually, it's one-liner; I've just aligned it for better readability.
let Input = [ "Lorem"; "ipsum"; "dolor"; "set"; "amet"; "consectetuer";
"adipiscing"; "elit"; "Aenean"; "commodo"; "ligula"; "eget";
"dolor"; "Aenean"; "massa" ]
// Short solution that does not support more than two values
let Output1 =
Input
|> List.fold
(fun (i, l1, l2) x ->
if i=4 then 0, None, (l1.Value, x)::l2
elif i=1 then i+1, Some x, l2
else i+1, l1, l2
)
(0, None, [])
|> fun (_, _, elem) -> elem
|> List.rev
Idea
The general idea is based on three steps:
- Splitting the list into a
List
of tuples, taking 2nd and 5th strings. WARNING If the original data length is not a multiplier of 5, the trailing element will be lost.
- Filtering out temporary data from a triple by taking the third element, which is our primary goal;
- Reversing the list.
Explanation
The first line is the hardest one.
Let's define our state. It will be a triple of sequential number, an string option
that contains strings ##2, 7, etc and an "outer" (string*string) list
that is added once we meet elements ##5, 10, etc.
The function will place the 2nd, 7th, etc elements to the "inner" string option
, or, if i
equals to 5, 10, etc., form a tuple and add it to the "outer" List
(dropping the inner value for sake of clarity).
We use List.fold
, and therefore the final list is to be reversed.
An initial state is a triple of (0, None, []). More info on
List.fold` in MSDN.
The second line just takes the third element from a triple. I've made it a function to allow chain binding.
The third line reverses the List
due to the nature of ::
operator.
As per length of the initial list. If it has found the "2nd" element but did not reach the "5th", the second element of a triple has the value. You may detect the erroneous situation by verifying it:
...
|> fun (_, temp, elem) ->
if temp.IsSome
then failwith "Data length must be a multiplier of 5"
else elem
...
Here's a bit longer code that supports more than two elements:
let Output2 =
Input
|> List.foldBack
(fun x (i, l1, l2) ->
if i = 4
then 0, [], (x::l1)::l2
else i+1, x::l1, l2
)
<| (0, [], [])
|> fun (_, _, elem) -> elem
|> List.choose
(function
| [_; first; _; _; second] -> Some (first, second)
| _-> None
)
Note this variant does not drop elements during the first call, so you may retrieve more than two items.
IMPORTANT: The list is processed in the reverse order, and so the item index is calculated from the end of input. You may change it to List.fold
in cost or further reversing the list as in Output1
.
Mind the reverse binding operator <|
due to signature of List.foldBack
.
You may check for errors in a similar way: by testing if the "inner" list is not empty.