主 - 细节场景。永远都在。好吧,如果不是永远的话,至少我在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)
答案 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 })