状态为

时间:2018-06-17 12:20:01

标签: list f# sequence state

我需要使用文本行过滤长序列中的数据。 文本行形成如下记录:

{  
    BEGINTYPE1    
    VAL1: xxx
    VAL2: yyy
    ENDTYPE1

    // mix of record types including TYPE1
}

我需要在处理过程中保持状态:

  1. 找到记录类型,从而跳过其他文本
  2. 过滤相关值,直到找到记录结束
  3. 继续1
  4. 我只能用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) ""  
    

    }

2 个答案:

答案 0 :(得分:2)

<强>目标
根据示例代码,这可能不是您正在寻找的,但我认为通过序列执行单次迭代并将记录映射到具体类型会很有趣。

<强>描述
此解决方案使用可以位于StartCollecting状态机。在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.headSeq.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) ""