Насколько я знаю, не существует простого способа сделать это, но это, безусловно, хороший способ практиковать навыки функционального программирования. Если бы вы использовали какое-то иерархическое представление данных (например, XML или JSON), ситуация была бы намного проще, потому что вам не пришлось бы преобразовывать структуру данных из линейной (например, список / массив) в иерархическую (в данном случае, список списков).
В любом случае, хороший способ решения этой проблемы состоит в том, чтобы понять, что вам нужно выполнить более общую операцию с данными - вам нужно сгруппировать смежные элементы массива, начиная новую группу, когда вы найдете строку со значением в первом столбце.
Я начну с добавления номера строки в массив, а затем преобразую его в список (с которым обычно проще работать в F #):
let data = lines |> Array.mapi (fun i l ->
i, l.Split(';')) |> List.ofSeq
Теперь мы можем написать многократно используемую функцию, которая группирует смежные элементы списка и запускает новую группу каждый раз, когда указанный предикат f
возвращает 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
Теперь реализовать ваш конкретный алгоритм относительно просто. Сначала нам нужно сгруппировать элементы, начиная новую группу каждый раз, когда первый столбец имеет какое-либо значение:
let groups = data |> adjacentGroups (fun (ln, cells) -> cells.[0] <> "")
На втором шаге нам нужно выполнить некоторую обработку для каждой группы. Мы берем первый элемент (и выбираем название группы), а затем находим минимальный и максимальный номер строки среди оставшихся элементов:
groups |> List.map (fun ((_, firstCols)::lines) ->
let lineNums = lines |> List.map fst
firstCols.[0], List.min lineNums, List.max lineNums )
Обратите внимание, что сопоставление с образцом в лямбда-функции выдаст предупреждение, но мы можем смело игнорировать это, поскольку группа всегда будет непустой.
Резюме: Этот ответ показывает, что если вы хотите написать элегантный код, вы можете реализовать многократно используемую функцию более высокого порядка (например, adjacentGroups
), потому что не все доступно в основных библиотеках F #. Если вы используете функциональные списки, вы можете реализовать их с помощью рекурсии (для массивов вы будете использовать императивное программирование, как в ответе gradbot ). Если у вас есть хороший набор повторно используемых функций, большинство проблем легко решаются: -).