Master-Detail场景需要F#优雅

时间:2010-03-06 23:26:16

标签: f#

主 - 细节场景。永远都在。好吧,如果不是永远的话,至少我在70年代的打卡上用FORTRAN做了大师级细节。它仍然存在 - 关于STO的大量详细问题。

我找了一个很好的方法在F#中做一个主细节识别器,没找到它。抱歉,如果我错过了,如果是这样,有人可以回复sto线程或网址吗?感谢

这是我的F#-newbie在F#中做一个主细节识别器。即:将原始细节字符串的原始/平面列表减少为F#-records列表,其中主字符串与其详细字符串列表配对。

不在这里寻找高尔夫代码。优雅。我曾希望最终得到优雅的东西,但下面只是一个直接的递归列表步行。我的F#-newbie大脑未能看到如何在这里好好利用折叠,理解,折叠,地图,活动模式,计算表达式等。

让我们保持在F#中可以做到的事情。如果在.Net中有一个预先构建的平面文件主 - 详细XML数据加载器,它可以在一行.Net调用中将master-detail .txt文件转换为.Net XML,这非常有趣,因为它可以用于F#。

作为一个有着长期命令式编程历史的人,我试图坚持使用不可变的F#进行练习。但是如果在F#中使用命令式或可变代码真的是最好的方法,请解释一下。输出可以是元组列表,记录序列,元组数组等。

任何评论/反馈......谢谢

let testInput =
    ["master Homer"    ; "Doh.."; "Doh!!" ;
     "master Has none" ;
     "master JoyJoyJoy"; "Yaa!" ; "Yaa!!!"; "Yaa!!!!!!"]

type md = {m: string; d: string list}
            member x.addDetail newd = {m = x.m; d = x.d @ [newd]}
            static member  noMaster = {m = "" ; d =           []}  // master records can never be null-strings, so "" works here
            static member  isMaster (L:string) = L.StartsWith("master ")
            static member  isDetail (L:string) = not (md.isMaster L) // There is no third kind of record - if not a master then it is a detail

let rec masterDetails flatList currentMaster =
    if           md.noMaster = currentMaster then
        match flatList with
        | []     -> [] // If no master and no more input: input list was empty and the empty list is the overall result
        | h :: t -> if md.isMaster h then // If no master, then head becomes the first master of the run
                                          masterDetails t {m = h; d = []}
                    else
                        failwith "Bad input: First record must be a master record"
    else
        match flatList with
        | []     ->     [currentMaster]   // End of input; return current master as a one-entry-list
        | h :: t -> if md.isMaster h then // Head will now replace the current master as the new master
                        [currentMaster] @ masterDetails t {m = h; d = []}
                    else                  // Keep current master; and add detail record to current master's detail list
                                          masterDetails t (currentMaster.addDetail h)

let testSolution = // Required: 1) Preserve order of the master sets. 2) Preserve sort order of details-within-masters.
    [{m = "master Homer"    ; d = ["Doh.."; "Doh!!"             ]}; 
     {m = "master Has none" ; d = [                             ]};
     {m = "master JoyJoyJoy"; d = ["Yaa!"; "Yaa!!!"; "Yaa!!!!!!"]} ]

let            tryIt = masterDetails testInput md.noMaster
let testTry = (tryIt = testSolution)

3 个答案:

答案 0 :(得分:5)

这听起来像是takeDrop的工作。

// split a list into a prefix of elements that all 
// meet predicate 'p', and the suffix remainder
let takeDrop p l =
    let rec loop acc l =
        match l with
        | h::t when p h -> loop (h::acc) t
        | _ -> List.rev acc, l
    loop [] l

let rec masterDetail input =
    [match input with
     | [] -> ()
     | h::t ->
        assert(md.isMaster h)
        let det, rest = takeDrop (not << md.isMaster) t
        yield { m = h; d = det }
        yield! masterDetail rest]

下面的完整测试代码。

let testInput = 
    ["master Homer"    ; "Doh.."; "Doh!!" ; 
     "master Has none" ; 
     "master JoyJoyJoy"; "Yaa!" ; "Yaa!!!"; "Yaa!!!!!!"] 

type md = {m: string; d: string list} 
            static member  isMaster (s:string) = s.StartsWith("master ") 

let testSolution = // Required: 1) Preserve order of the master sets. 
                   // 2) Preserve sort order of details-within-masters. 
    [{m = "master Homer"    ; d = ["Doh.."; "Doh!!"             ]};  
     {m = "master Has none" ; d = [                             ]}; 
     {m = "master JoyJoyJoy"; d = ["Yaa!"; "Yaa!!!"; "Yaa!!!!!!"]} ] 

// split a list into a prefix of elements that all 
// meet predicate 'p', and the suffix remainder
let takeDrop p l =
    let rec loop acc l =
        match l with
        | h::t when p h -> loop (h::acc) t
        | _ -> List.rev acc, l
    loop [] l

let rec masterDetail input =
    [match input with
     | [] -> ()
     | h::t ->
        assert(md.isMaster h)
        let det, rest = takeDrop (not << md.isMaster) t
        yield { m = h; d = det }
        yield! masterDetail rest]

let briSol = masterDetail testInput
printfn "%A" (briSol = testSolution)

答案 1 :(得分:1)

据我所知,没有内置函数会以这种方式自动拆分列表。在现实世界中,您可能首先使用不同的数据表示,因此您不需要解决此问题(从XML加载数据时,您已经具有层次结构,并且在使用数据分组时LINQ,您还可以获得分层数据)。但是,可能仍需要您的功能,例如从文本文件加载数据时。

这是一个稍微简单的版本,它使用序列表达式来生成外部集合(主要详细信息记录)。内部集合以通常的方式累积在参数中:

let rec groupMasterDetails l acc master = seq {
  match l with 
  // No master found yet, if the first element isn't master, we throw
  | x::xs when not (md.isMaster x) && master = None ->
    failwith "The first element must be master"
  // Starting a new group, yield the previous group
  | x::xs when md.isMaster x ->
    if master <> None then yield { m = master.Value; d = List.rev acc }
    yield! groupMasterDetails xs [] (Some x)
  // Continue the current group
  | x:: xs ->
    yield! groupMasterDetails xs (x::acc) master
  // End of processing, yield the last group
  | [] -> 
    if master <> None then yield { m = master.Value; d = List.rev acc } }

let masterDetails l = l [] None

请注意,元素以相反的顺序累积(与使用[el]@rest相反,然后反转,因为这样效率更高 - 使用@涉及复制整个列表,所以它经常使用它是一种不好的做法。这也意味着实施不需要您的addDetail成员。

但是,这仍然是一段相对较长的代码 - 我很想知道是否可以通过编写标准的F#函数来实现(我没有找到任何好的方法)。

答案 2 :(得分:0)

这是一个以Brain的答案为基础的例子,它将分离过多,但它确实显示了函数式编程的强大功能。

let takeDrop p l =
    let rec loop acc l =
        match l with
        | h::t when p h -> loop (h::acc) t
        | _ -> List.rev acc, l
    loop [] l

let rec listSplit spliter neo l =
    [match l with
     | [] -> ()
     | h::t ->
        let det, rest = spliter t
        yield neo h det
        yield! listSplit spliter neo rest]

let masterDetail = 
    listSplit 
        (takeDrop (not << md.isMaster)) 
        (fun h det -> { m = h; d = det })