Сопоставление регулярных выражений F # с активными шаблонами - PullRequest
19 голосов
/ 16 апреля 2011

Я нашел эту полезную статью об использовании активных шаблонов с регулярными выражениями: 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

Что даст вам знать, если будет найден хотя бы один 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)]

Это в основном результат, который я ищу, но я пытаюсь изучить функциональный способ сделать это, и я думаю, что он должен включать активные шаблоны. Не стесняйтесь пытаться "функционализировать" это, если это имеет больше смысла, чем моя первая попытка.

Заранее спасибо,

Bob

1 Ответ

24 голосов
/ 16 апреля 2011

Интересные вещи, я думаю, что все, что вы здесь исследуете, действительно.(Частичные) активные шаблоны для сопоставления регулярных выражений работают очень хорошо.Особенно, когда у вас есть строка, которую вы хотите сопоставить с несколькими альтернативными случаями.Единственное, что я хотел бы предложить для более сложных активных шаблонов регулярных выражений, - это дать им более описательные имена, возможно, создав коллекцию различных активных шаблонов регулярных выражений с разными целями.

Что касается вашего примера с 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)вы тестируете только один шаблон, 2) вы динамически обрабатывает совпадения.

Для реального примера активных шаблонов рассмотрим случай, когда 1) мы тестируем несколько регулярных выражений, 2) мы тестируем дляодно регулярное выражение совпадает с несколькими группами.Для этих сценариев я использую следующие два активных шаблона, которые немного более общие, чем первый активный шаблон Match, который вы показали (я не отбрасываю первую группу в совпадении, и я возвращаю список объектов Group, а нетолько их значения - один использует параметр скомпилированного регулярного выражения для статических шаблонов регулярных выражений, другой использует интерпретированный параметр регулярного выражения для динамических шаблонов регулярных выражений).Поскольку API .NET regex настолько наполнен функциями, то, что вы возвращаете из своего активного шаблона, действительно соответствует тому, что вы считаете полезным.Но возвращать 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

Обратите также внимание на то, что эти активные шаблоны считают нуль несоответствием, вместо этогобросить исключение.

ОК, так что давайте предположим, что мы хотим разобрать имена.У нас есть следующие требования:

  1. Должны иметь имя и фамилию
  2. Может иметь отчество
  3. Имя, отчество и фамилия отделены однимпробел в указанном порядке
  4. Каждая часть имени может состоять из любой комбинации по крайней мере одной или нескольких букв или цифр
  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

Обратите внимание на одно из ключевых преимуществмы получаем здесь, что в случае с сопоставлением с образцом в целом, то, что мы можем одновременно проверить, что вход соответствует шаблону регулярных выражений, и разложить возвращенный список групп, если это так.

...