2016-08-11 3 views
4

Я изучаю F #, и я начал играть с обеими последовательностями и выражениями match.Соответствие с пустой последовательностью

Я пишу веб-скребок, который просматривает HTML, аналогичный следующему и беря последний URL-адрес в родительском <span> с классом paging.

<html> 
<body> 
    <span class="paging"> 
     <a href="http://google.com">Link to Google</a> 
     <a href="http://TheLinkIWant.com">The Link I want</a> 
    </span> 
</body> 
</html> 

Моя попытка получить последний URL выглядит следующим образом:

type AnHtmlPage = FSharp.Data.HtmlProvider<"http://somesite.com"> 

let findMaxPageNumber (page:AnHtmlPage)= 
    page.Html.Descendants() 
    |> Seq.filter(fun n -> n.HasClass("paging")) 
    |> Seq.collect(fun n -> n.Descendants() |> Seq.filter(fun m -> m.HasName("a"))) 
    |> Seq.last 
    |> fun n -> n.AttributeValue("href") 

Однако я бегу в проблемы, когда класс Я ищу отсутствует на странице. В частности, я получаю ArgumentExceptions с сообщением: Additional information: The input sequence was empty.

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

let findUrlOrReturnEmptyString (span:seq<HtmlNode>) = 
    match span with 
    | Seq.empty -> String.Empty  // <----- This is invalid 
    | span -> span 
    |> Seq.collect(fun (n:HtmlNode) -> n.Descendants() |> Seq.filter(fun m -> m.HasName("a"))) 
    |> Seq.last 
    |> fun n -> n.AttributeValue("href") 

let findMaxPageNumber (page:AnHtmlPage)= 
    page.Html.Descendants() 
    |> Seq.filter(fun n -> n.HasClass("paging")) 
    |> findUrlOrReturnEmptyStrin 

Моя проблема заключается в том, что в настоящее время Seq.Empty не является буквальным и не могут быть использованы в шаблоне. Большинство примеров с сопоставлением с образцом задают пустые списки [] в своих шаблонах, поэтому мне интересно: как использовать подобный подход и сопоставлять пустые последовательности?

+0

Просто используйте 'if .. else' здесь; «матч» просто усложняет ситуацию. ('if Seq.isEmpty span then" "else ...') – ildjarn

+0

Пример был упрощен, в моем конвейере есть несколько мест, когда мне нужно будет добавлять 'if-else'. Поскольку я новичок в F #, я в основном задаюсь вопросом, есть ли подходящий способ сопоставления пустых последовательностей, поскольку кажется, что это соответствует пустым последовательностям. – JoshVarty

+2

Если это распространено в коде _your_, и вы твердо придерживаетесь 'match', тогда создайте для него активный шаблон. – ildjarn

ответ

8

Предположение, что Илдъярн дал в комментариях хороший один: если вы чувствуете, что с помощью match бы создать более читаемый код, а затем сделать активный шаблон для проверки пустых seqs:

let (|EmptySeq|_|) a = if Seq.isEmpty a then Some() else None 

let s0 = Seq.empty<int> 

match s0 with 
| EmptySeq -> "empty" 
| _ -> "not empty" 

Ран, что в F # interactive, и результат будет "empty".

+0

Спасибо, я не знаком с активными шаблонами, но это похоже на довольно разумное решение. – JoshVarty

+0

FYI: https://msdn.microsoft.com/visualfsharpdocs/conceptual/active-patterns-[fsharp] – FuleSnabel

7

Вы можете использовать when охранника далее квалифицировать случай:

match span with 
| sequence when Seq.isEmpty sequence -> String.Empty 
| span -> span 
|> Seq.collect(fun (n:HtmlNode) -> n.Descendants() |> Seq.filter(fun m -> m.HasName("a"))) 
|> Seq.last 
|> fun n -> n.AttributeValue("href") 

Илдъярна правильно в том, что в этом случае if...then...else может быть более читаемой альтернативой, хотя.

+1

Это не должно компилироваться. 'span' имеет тип' seq ', и если были определены' String.Empty', что не так хорошо, как я знаю, это, безусловно, должно быть типа string. Возможно, вы имели в виду отступы трубопровода? В противном случае используйте однострочное ранжирование, например 'if Seq.isEmpty span then" "else', чтобы разрешить открытие ветки, не требуя более глубокого отступа. – Vandroiy

+0

Это правильно; он не компилируется. Я знал, что без настройки всего, включая поставщика HTML-типов, он все равно не будет компилироваться, поэтому я не предпринял никаких усилий для его исправления. Было бы лучше иметь, по крайней мере, выражение выражения соответствия, да. – TeaDrivenDev

+0

[String.Empty] (https://msdn.microsoft.com/en-us/library/system.string.empty%28v=vs.110%29.aspx) был определен с .NET 1.0. Конечно, вы открываете систему. –

2

Основываясь на ответе от @rmunn, вы можете сделать более общий шаблон равенства равенства последовательностей.

let (|Seq|_|) test input = 
    if Seq.compareWith Operators.compare input test = 0 
     then Some() 
     else None 

match [] with 
| Seq [] -> "empty" 
| _ -> "not empty" 
+0

Кстати, вы можете подумать, что именование активного шаблона 'Seq' будет конфликтовать с модулем' Seq', но он выиграл «т. Вы все равно сможете использовать 'Seq.append' и другие функции из модуля' Seq'; компилятор это выяснит. Внутри шаблона соответствия имя 'Seq' будет ссылаться на активный шаблон; за пределами шаблона имя 'Seq' будет продолжать ссылаться на модуль. – rmunn

Смежные вопросы