我需要使用文本行过滤长序列中的数据。 文本行形成如下记录:
{
BEGINTYPE1
VAL1: xxx
VAL2: yyy
ENDTYPE1
// mix of record types including TYPE1
}
我需要在处理过程中保持状态:
我只能用List执行此操作,因为序列似乎 在一个表达式中读到最后。 它“似乎”你不能处理一个序列的一部分并继续在另一个表达式中,序列“指针”在它停止的位置? 所以我用了一个清单。 我的问题是,这个处理可以用序列来完成 使用标准函数,如Skip,filter ...等?
我的列表解决方案:
let patLst = [
"VAL1:" ;
"VAL2:" ;
// ..
]
let BeginRecord1 = "BEGINTYPE1"
let EndRecord1 = "ENDTYPE1"
let filter (lines:seq<string>) =
let llines = Seq.toList lines
let matchLine inp =
let rec loop pat =
match pat with
| [] -> None
| h::t ->
let m = Regex.Match(inp, h)
match m.Success with
| true -> Some (h)
| _ -> loop t
loop patLst
let rec findItem i l =
match l with
| [] -> []
| h::t -> if h=i then t
else findItem i t
let findItemsUntil u a l =
let rec loop a l =
match l with
| [] -> ([],a)
| h::t when h=u -> (t , ""::a)
| h::t -> match matchLine h with
| Some(m) -> loop (m::a) t
| None -> loop a t
loop a l
let rec loop a l =
match findItem BeginRecord1 l with
| [] -> List. rev a
| l2 -> let (l3,a) = findItemsUntil EndRecord1 a l2
loop a l3
llines |> loop [""] |> List.fold (fun a x -> a + "\n" + x) ""
}
答案 0 :(得分:2)
<强>目标强>
根据示例代码,这可能不是您正在寻找的,但我认为通过序列执行单次迭代并将记录映射到具体类型会很有趣。
<强>描述强>
此解决方案使用可以位于Start
或Collecting
的状态机。在Start
中,它会指望“BEGINTYPEx”。当发现它将进入Collecting
状态,将属性收集到Map
时。当收集状态命中“ENDTYPEx”时,它使用映射函数创建实例并将其添加到Aggregate list
,返回Start
状态。
<强>实施强>
为记录定义一些类型,包括Discriminated Union和折叠的州类型:
type Type1 = {
val1:string
val2:string
}
type Type2 = {
val1:string
val2:string
}
type Aggregate =
| T1 of Type1
| T2 of Type2
type State =
| Start of Aggregate list
| Collecting of Aggregate list * string * (Map<string,string> -> Aggregate) * Map<string,string>
定义一些映射函数以将Map
映射到记录类型:
let mapType1 (dic:Map<string,string>) =
Aggregate.T1
{
val1 = dic.["VAL1"]
val2 = dic.["VAL2"]
}
let mapType2 (dic:Map<string,string>) =
Aggregate.T2
{
val1 = dic.["VAL1"]
val2 = dic.["VAL2"]
}
接下来,我们有一些活动模式可以轻松决定匹配:
let (|Begin|_|) input =
match input with
| "BEGINTYPE1" -> Some ("TYPE1", mapType1)
| "BEGINTYPE2" -> Some ("TYPE2", mapType2)
| _ -> None
let (|Prop|_|) input =
if(String.IsNullOrEmpty(input)) then None
else
if(input.Contains(":")) then
let split = input.Split(":")
let pName = split.[0].Trim()
let pValue = split.[1].Trim()
Some (pName,pValue)
else None
let (|End|_|) (l,label,f,m) input =
match input with
| "ENDTYPE1" -> Some (List.append l ([f m]), label)
| "ENDTYPE2" -> Some (List.append l ([f m]), label)
| _ -> None
从一种状态移动到下一种状态的实际文件夹功能:
let folder state line =
match state with
| Start xs ->
match line with
| Begin (label, f) -> Collecting (xs, label, f, Map.empty<string,string>)
| _ -> failwithf "Should start with a BEGINTYPEx, intead was %s" line
| Collecting (xs, label, f, m) ->
match line with
| Prop (k,v) -> Collecting (xs, label, f, Map.add k v m)
| End(xs, label, f, m) (ys, s) -> Start ys
| _ -> failwithf "Expecting property or ENDTYPEx, instead was %s" line
一个简单的帮助方法,可以帮助轻松提取列表:
let extractTypeList state =
match state with
| Start xs -> xs
| Collecting (xs, _,_,_) -> xs
最后,用法:
let lines = seq {
yield "BEGINTYPE1"
yield "VAL1: xxx"
yield "VAL2: yyy"
yield "ENDTYPE1"
yield "BEGINTYPE2"
yield "VAL1: xxx"
yield "VAL2: yyy"
yield "ENDTYPE2"
}
let extractTypes lines =
lines
|> Seq.fold folder (Start [])
|> extractTypeList
|> List.iter (fun a -> printfn "%A" a)
extractTypes lines |> ignore
一些有用的链接:
了解Active Patterns。
了解fold。
答案 1 :(得分:1)
您可以使用与列表几乎相同的方式处理序列,您只需使用Seq.head
和Seq.tail
函数,而不是可用于列表的方便的模式匹配语法。使用内置函数,您的解决方案将如下所示:
open System.Text.RegularExpressions
let patLst = [
"VAL1:" ;
"VAL2:" ;
// ..
]
let BeginRecord1 = "BEGINTYPE1"
let EndRecord1 = "ENDTYPE1"
let filter (lines:seq<string>) =
let matchLine inp =
let rec loop pat =
match pat with
| [] -> None
| h::t ->
match Regex.Match(inp, h) with
| m when m.Success -> Some (h)
| _ -> loop t
loop patLst
let rec findItem i l =
if l |> Seq.isEmpty
then Seq.empty
else let h = l |> Seq.head
let t = l |> Seq.tail
if h=i
then t
else findItem i t
let findItemsUntil u a l =
let rec loop a l =
if l |> Seq.isEmpty
then (Seq.empty,a)
else let h = l |> Seq.head
let t = l |> Seq.tail
if h=u
then (t , ""::a)
else match matchLine h with
| Some(m) -> loop (m::a) t
| None -> loop a t
loop a l
let rec loop a l =
match findItem BeginRecord1 l with
| s when s |> Seq.isEmpty -> List.rev a
| l2 -> let (l3,a) = findItemsUntil EndRecord1 a l2
loop a l3
lines |> loop [""] |> List.fold (fun a x -> a + "\n" + x) ""
现在,如果您想简化逻辑,可以编写自己的Active Pattern来执行与列表的head :: tail
模式相同的操作。活动模式本身看起来像这样:
let (|HT|Empty|) s =
match s |> Seq.tryHead with
| Some head -> HT (head, s |> Seq.tail)
| None -> Empty
然后,您的实现可能与基于列表的版本几乎完全相同,只需交换此活动模式并使用Seq.empty
替换空列表:
let filter (lines:seq<string>) =
let matchLine inp =
let rec loop pat =
match pat with
| Empty -> None
| HT (h,t) ->
let m = Regex.Match(inp, h)
match m.Success with
| true -> Some (h)
| _ -> loop t
loop patLst
let rec findItem i l =
match l with
| Empty -> Seq.empty
| HT (h,t) -> if h=i then t
else findItem i t
let findItemsUntil u a l =
let rec loop a l =
match l with
| Empty -> (Seq.empty,a)
| HT (h,t) when h=u -> (t , ""::a)
| HT (h,t) -> match matchLine h with
| Some(m) -> loop (m::a) t
| None -> loop a t
loop a l
let rec loop a l =
match findItem BeginRecord1 l with
| Empty -> List.rev a
| l2 -> let (l3,a) = findItemsUntil EndRecord1 a l2
loop a l3
lines |> loop [""] |> List.fold (fun a x -> a + "\n" + x) ""