Max*_*Max 4 f# functional-programming
我正在尝试创建一段代码,但无法使其正常工作.我能想到的最简单的例子是解析一些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")|]
第一个数字是子部分范围的起始行索引,第二个数字是结束行索引.我怎么做?我在考虑使用折叠功能,但无法创建任何东西.
据我所知,没有简单的方法可以做到这一点,但它绝对是练习函数式编程技巧的好方法.如果你使用了一些数据的层次表示(例如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的答案).一旦你有了一套好的可重用函数,大多数问题都很简单:-).