21

正規表現でのアクティブパターンの使用に関するこの便利な記事を見つけました:http: //www.markhneedham.com/blog/2009/05/10/f-regular-expressionsactive-patterns/

この記事で使用された元のコードスニペットは次のとおりです。

open System.Text.RegularExpressions

let (|Match|_|) pattern input =
    let m = Regex.Match(input, pattern) in
    if m.Success then Some (List.tl [ for g in m.Groups -> g.Value ]) else None

let ContainsUrl value = 
    match value with
        | Match "(http:\/\/\S+)" result -> Some(result.Head)
        | _ -> None

これにより、少なくとも1つのURLが見つかったかどうか、およびそのURLが何であったかがわかります(スニペットを正しく理解している場合)

次に、コメントセクションで、ジョエルはこの変更を提案しました。

別の方法として、特定のグループが一致する場合とそうでない場合があるため、次のようになります。

List.tail [ for g in m.Groups -> if g.Success then Some g.Value else None ]

または、グループにラベルを付けて、名前でアクセスしたい場合もあります。

(re.GetGroupNames()
 |> Seq.map (fun n -> (n, m.Groups.[n]))
 |> Seq.filter (fun (n, g) -> g.Success)
 |> Seq.map (fun (n, g) -> (n, g.Value))
 |> Map.ofSeq)

これらすべてを組み合わせようとした後、私は次のコードを思いつきました。

let testString = "http://www.bob.com http://www.b.com http://www.bob.com http://www.bill.com"

let (|Match|_|) pattern input =
    let re = new Regex(pattern)
    let m = re.Match(input) in
    if m.Success then Some ((re.GetGroupNames()
                                |> Seq.map (fun n -> (n, m.Groups.[n]))
                                |> Seq.filter (fun (n, g) -> g.Success)
                                |> Seq.map (fun (n, g) -> (n, g.Value))
                                |> Map.ofSeq)) else None

let GroupMatches stringToSearch = 
    match stringToSearch with
        | Match "(http:\/\/\S+)" result -> printfn "%A" result
        | _ -> ()


GroupMatches testString;;

インタラクティブセッションでコードを実行すると、次のように出力されます。

map [("0", "http://www.bob.com"); ("1", "http://www.bob.com")]

私が達成しようとしている結果は、次のようになります。

map [("http://www.bob.com", 2); ("http://www.b.com", 1); ("http://www.bill.com", 1);]

基本的に、見つかった各一意の一致のマッピングと、それに続く特定の一致する文字列がテキストで見つかった回数のカウント。

私がここで間違った道を進んでいると思われる場合は、まったく別のアプローチを提案してください。私はアクティブパターンと正規表現の両方に少し慣れていないので、これを修正するためにどこから始めればよいのかさえわかりません。

また、これは基本的にC#でF#に変換して行うことです。

let testString = "http://www.bob.com http://www.b.com http://www.bob.com http://www.bill.com"

let matches =
    let matchDictionary = new Dictionary<string,int>()
    for mtch in (Regex.Matches(testString, "(http:\/\/\S+)")) do
        for m in mtch.Captures do
            if(matchDictionary.ContainsKey(m.Value)) then
                matchDictionary.Item(m.Value) <- matchDictionary.Item(m.Value) + 1
            else
                matchDictionary.Add(m.Value, 1)
    matchDictionary

これは実行時にこれを返します:

val matches : Dictionary = dict [("http://www.bob.com", 2); ("http://www.b.com", 1); ("http://www.bill.com", 1)]

これは基本的に私が探している結果ですが、これを行うための機能的な方法を学ぼうとしているので、アクティブなパターンを含める必要があると思います。私の最初の試みよりも理にかなっている場合は、これを「機能化」してみてください。

前もって感謝します、

ボブ

4

1 に答える 1

26

興味深いことに、ここで探索していることはすべて有効だと思います。(部分的)正規表現マッチングのアクティブパターンは確かに非常にうまく機能します。特に、複数の代替ケースと照合したい文字列がある場合。より複雑な正規表現アクティブパターンで私が提案する唯一のことは、よりわかりやすい名前を付けて、さまざまな目的でさまざまな正規表現アクティブパターンのコレクションを構築することです。

C#からF#の例では、アクティブなパターンがなくても機能的なソリューションをうまく利用できます。

let testString = "http://www.bob.com http://www.b.com http://www.bob.com http://www.bill.com"

let matches input =
    Regex.Matches(input, "(http:\/\/\S+)") 
    |> Seq.cast<Match>
    |> Seq.groupBy (fun m -> m.Value)
    |> Seq.map (fun (value, groups) -> value, (groups |> Seq.length))

//FSI output:
> matches testString;;
val it : seq<string * int> =
  seq
    [("http://www.bob.com", 2); ("http://www.b.com", 1);
     ("http://www.bill.com", 1)]

アップデート

この特定の例がアクティブなパターンなしで正常に機能する理由は、1)1つのパターンのみをテストしている、2)一致を動的に処理しているためです。

アクティブなパターンの実際の例として、1)複数の正規表現をテストしている場合、2)複数のグループとの1つの正規表現の一致をテストしている場合を考えてみましょう。これらのシナリオでは、次の2つのアクティブなパターンを使用します。これらは、最初に示したアクティブなパターンよりも少し一般的Matchです(一致の最初のグループを破棄せず、値だけでなくグループオブジェクトのリストを返します) --静的正規表現パターンにはコンパイル済み正規表現オプションを使用し、動的正規表現パターンには解釈済み正規表現オプションを使用します)。.NET正規表現APIは非常に機能が充実しているため、アクティブなパターンから返されるものは、実際に役立つものに依存します。listしかし、何かのを返すことは良いことです。なぜなら、そのリストでパターンマッチを行うことができるからです。

let (|InterpretedMatch|_|) pattern input =
    if input = null then None
    else
        let m = Regex.Match(input, pattern)
        if m.Success then Some [for x in m.Groups -> x]
        else None

///Match the pattern using a cached compiled Regex
let (|CompiledMatch|_|) pattern input =
    if input = null then None
    else
        let m = Regex.Match(input, pattern, RegexOptions.Compiled)
        if m.Success then Some [for x in m.Groups -> x]
        else None

これらのアクティブなパターンが、例外をスローするのではなく、nullを不一致と見なす方法にも注意してください。

では、名前を解析したいとしましょう。次の要件があります。

  1. 姓名が必要です
  2. ミドルネームの可能性があります
  3. 最初に、オプションのミドルネームとラストネームをこの順序で1つの空白スペースで区切ります
  4. 名前の各部分は、少なくとも1つ以上の文字または数字の任意の組み合わせで構成できます。
  5. 入力の形式が正しくない可能性があります

まず、次のレコードを定義します。

type Name = {First:string; Middle:option<string>; Last:string}

次に、名前を解析する関数で正規表現アクティブパターンを非常に効果的に使用できます。

let parseName name =
    match name with
    | CompiledMatch @"^(\w+) (\w+) (\w+)$" [_; first; middle; last] ->
        Some({First=first.Value; Middle=Some(middle.Value); Last=last.Value})
    | CompiledMatch @"^(\w+) (\w+)$" [_; first; last] ->
        Some({First=first.Value; Middle=None; Last=last.Value})
    | _ -> 
        None

ここで得られる主な利点の1つは、一般的なパターンマッチングの場合であり、入力が正規表現パターンと一致することを同時にテストし、一致する場合は返されたグループのリストを分解できることです。

于 2011-04-16T03:57:22.077 に答える