Подтвердить что ты не робот

F # Отображение регулярных выражений с активными шаблонами

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

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

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

Я также придумал это, что в основном я бы сделал в С#, переведенном в 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)]

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

Спасибо заранее,

Боб

4b9b3361

Ответ 1

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

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

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

  • Должно иметь имя и фамилию
  • Может иметь среднее имя
  • Во-первых, необязательное среднее и фамильное имя разделяются одним пробелом в этом порядке
  • Каждая часть имени может состоять из любой комбинации по меньшей мере одной или нескольких букв или цифр.
  • Ввод может быть неверным

Сначала мы определим следующую запись:

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

Затем мы можем эффективно использовать наш активный шаблон regex в функции для разбора имени:

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

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