As far as I know, there is no easy way to do this, but it is certainly a good way to practice functional programming skills. If you used some hierarchical representation of the data (for example, XML or JSON), the situation would be much simpler because you would not have to convert the data structure from linear (for example, list / array) to hierarchical (in this case, a list of lists).
In any case, a good way to approach the problem is to understand that you need to perform a more general data operation - you need to group adjacent elements of the array, starting a new group when you find a row with a value in the first column.
I'll start by adding the line number to the array, and then convert it to a list (which is usually easier to work with in F #):
let data = lines |> Array.mapi (fun il -> i, l.Split(';')) |> List.ofSeq
Now we can write a reusable function that groups adjacent list items and starts a new group every time the specified predicate f returns true :
let adjacentGroups f list = // Utility function that accumulates the elements of the current // group in 'current' and stores all groups in 'all'. The parameter // 'list' is the remainder of the list to be processed let rec adjacentGroupsUtil current all list = match list with // Finished processing - return all groups | [] -> List.rev (current::all) // Start a new group, add current to the list | x::xs when f(x) -> adjacentGroupsUtil [x] (current::all) xs // Add element to the current group | x::xs -> adjacentGroupsUtil (x::current) all xs // Call utility function, drop all empty groups and // reverse elements of each group (because they are // collected in a reversed order) adjacentGroupsUtil [] [] list |> List.filter (fun l -> l <> []) |> List.map List.rev
Now implementing your particular algorithm is relatively simple. First, we need to group the elements, starting a new group every time the first column has a value:
let groups = data |> adjacentGroups (fun (ln, cells) -> cells.[0] <> "")
In the second step, we need to do some processing for each group. We take its first element (and select the name of the group), and then find the minimum and maximum number of lines among the remaining elements:
groups |> List.map (fun ((_, firstCols)::lines) -> let lineNums = lines |> List.map fst firstCols.[0], List.min lineNums, List.max lineNums )
Please note that pattern matching in a lambda function will give a warning, but we can safely ignore this, because the group will always be non-empty.
Summary:. This answer shows that if you want to write elegant code, you can implement your higher-order reuse function (e.g. adjacentGroups ), since not everything is available in the main F # libraries. If you use function lists, you can implement it with recursion (for arrays you should use imperative programming, as in gradbot answer). When you have a good set of reusable features, most problems are easy :-).