我正在尝试创建一段代码,但无法使其正常工作。我能想到的最简单的例子是解析一些CSV文件。 假设我们有一个CVS文件,但数据以某种层次结构组织在其中。像这样:
Section1;
;Section1.1
;Section1.2
;Section1.3
Section2;
;Section2.1
;Section2.2
;Section2.3
;Section2.4
等
我这样做了:
let input =
"a;
;a1
;a2
;a3
b;
;b1
;b2
;b3
;b4
;b5
c;
;c1"
let lines = input.Split('\n')
let data = lines |> Array.map (fun l -> l.Split(';'))
let sections =
data
|> Array.mapi (fun i l -> (i, l.[0]))
|> Array.filter (fun (i, s) -> s <> "")
我得到了
val sections : (int * string) [] = [|(0, "a"); (4, "b"); (10, "c")|]
现在我想为每个部分创建一个行索引范围列表,如下所示:
[|(1, 3, "a"); (5, 9, "b"); (11, 11, "c")|]
第一个数字是子部分范围的起始行索引,第二个数字是结束行索引。我怎么做?我正在考虑使用折叠功能,但无法创建任何东西。
答案 0 :(得分:5)
据我所知,没有简单的方法可以做到这一点,但这绝对是练习函数式编程技巧的好方法。如果你使用了一些数据的层次表示(例如XML或JSON),情况会容易得多,因为你不必将数据结构从线性(例如列表/数组)转换为层次结构(在这种情况下,列表清单。)
无论如何,解决问题的一个好方法是意识到你需要对数据进行更一般的操作 - 你需要对数组的相邻元素进行分组,当你找到一个有值的行时启动一个新的组在第一栏。
我首先在数组中添加一个行号,然后将其转换为list(通常在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 )
请注意,lambda函数中的模式匹配会给出警告,但我们可以放心地忽略它,因为该组将始终为非空。
摘要:这个答案表明,如果您想编写优雅的代码,您可以实现可重用的高阶函数(例如adjacentGroups
),因为并非F#中的所有内容都可用核心图书馆。如果使用函数列表,则可以使用递归来实现它(对于数组,您将使用命令式编程,如 gradbot 的答案)。一旦你有了一套好的可重用函数,大多数问题都很简单: - )。
答案 1 :(得分:1)
通常,当您只使用数组时,您会强迫自己使用可变和命令式样式代码。我制作了一个通用的Array.splitBy函数来将不同的部分组合在一起。如果您要编写自己的解析器,那么我建议使用List和其他高级构造。
module Question
open System
let splitArrayBy f (array:_[]) =
[|
let i = ref 0
let start = ref 0
let last = ref [||]
while !i < array.Length do
if f array.[!i] then
yield !last, array.[!start .. !i - 1]
last := array.[!i]
start := !i + 1
i := !i + 1
if !start <> !i then
yield !last, array.[!start .. !i - 1]
|]
let input = "a;\n;a1\n;a2\n;a3\nb;\n;b1\n;b2\n;b3\n;b4\n;b5\nc;\n;c1"
let lines = input.Split('\n')
let data = lines |> Array.map (fun l -> l.Split(';'))
let result = data |> splitArrayBy (fun s -> s.[0] <> "")
Array.iter (printfn "%A") result
将输出以下内容。
([||], [||])
([|"a"; ""|], [|[|""; "a1"|]; [|""; "a2"|]; [|""; "a3"|]|])
([|"b"; ""|], [|[|""; "b1"|]; [|""; "b2"|]; [|""; "b3"|]; [|""; "b4"|]; [|""; "b5"|]|])
([|"c"; ""|], [|[|""; "c1"|]|])
以上是对上面的一个小修改,以产生示例输出。
let splitArrayBy f (array:_[][]) =
[|
let i = ref 0
let start = ref 0
let last = ref ""
while !i < array.Length do
if f array.[!i] then
if !i <> 0 then
yield !start, !i - 1, !last
last := array.[!i].[0]
start := !i + 1
i := !i + 1
if !start <> !i then
yield !start, !i - 1, !last
|]
let input = "a;\n;a1\n;a2\n;a3\nb;\n;b1\n;b2\n;b3\n;b4\n;b5\nc;\n;c1"
let lines = input.Split('\n')
let data = lines |> Array.map (fun l -> l.Split(';'))
let result = data |> splitArrayBy (fun s -> s.[0] <> "")
(printfn "%A") result
输出
[|(1, 3, "a"); (5, 9, "b"); (11, 11, "c")|]
答案 2 :(得分:0)
JSON结构似乎是您的理想选择;解析器和转换器已经可用。
在此处阅读:http://msdn.microsoft.com/en-us/library/bb299886.aspx
编辑:由于某种原因,我看到了j#,也许它仍适用于f#..