2010-03-06 25 views
1

主 - 细节场景。一直在附近。好吧,如果不是永远的话,至少我在70年代用穿孔卡做了FORTRAN的主要细节。它仍然存在 - STO的大量细节问题。Master-Detail场景所需的F#优雅

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

这里是我的F# - 新手在F#中做主 - 细节识别器。即:将主/从列表的主/从列表减少为F#列表,其中主从字符串与其详细字符串列表配对。

不寻找代码高尔夫在这里的人。优雅。我本来希望最终得到一些优雅的东西,但下面只是一个简单的递归列表。我的F#新手大脑未能看到如何在这里很好地利用折叠,理解,折叠,地图,活动模式,计算表达式等。

让我们继续讨论在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) 

回答

1

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

这是一个稍微简单的版本,它使用序列表达式来生成外部集合(master-details记录)。内收集积累的参数以通常的方式:

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#函数来实现(我没有找到任何好的方法来实现这一点)。

+0

感谢Tomas。是的,我不喜欢我在帖子中的那两个。 我认为你的版本比稍微简单一些:你撕掉了两个@,撕掉了外部的if-then,将两个匹配合并为一个匹配。增加理解/收益/收益!还有一些当守卫。我的代码路径不需要考虑大约6个代码路径,而是大约4个代码路径。我更喜欢你的。非常感谢您的回复。 是的,它是我正在考虑的.txt文件。在旧相册程序中导出数据以将数据移动到新的Photoshop Elements中。 Voila,每个文件夹的主细节数据的一个txt文件。 – brucer10

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) 
+0

+1,关注度很好的分离。 – gradbot

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 })