在fitler中返回条件?

时间:2018-06-01 23:02:02

标签: f#

我有以下代码来过滤Seq并在没有返回任何内容时返回错误。

let s = nodes
        |> Seq.filter(fun (a, _, _, _) -> if a.ToLower().Contains(key1)) // condition 1
                                          then true
                                          else false // Error message should have Key1
        |> Seq.filter(....) // condition 2
        |> Seq.filter(....) // condition 3
        ..... 
        |> Seq.filter(function // condition N
           | _, Some date, _, _ -> date >= StartPeriod 
           | _ -> false // put StartPeriod in the final message s is not empty before this step
           )

if Seq.isEmpty s
then sprintf "Failed by condition 1 (%s) or condition 2 (%s) .... or condition N (Date > %s)" 
               key1, ...., (StartPeriod.ToShortDateSTring())
else ....

最终错误消息sprintf将包含所有过滤条件。这是让代码只返回(或者只是最后一个)使s为空的方法吗?

根据rmunn的回答,我修改了它以返回导致列表清空的所有过滤器。

let rec filterSeq filterList input msgs =
    match filterList with
    | [] -> input, msgs
    | (label, filter) :: filters ->
        let result = input |> Seq.filter filter
        if result |> Seq.isEmpty then
            printfn "The \"%s\" filter emptied out the input" label
            Seq.empty, (List.append msgs [label])
        else
            filterSeq filters result (List.append msgs [label])

let intFiltersWithLabels = [
    "Odd numbers", fun x -> x % 2 <> 0
    "Not divisible by 3", fun x -> x % 3 <> 0
    "Not divisible by 5", fun x -> x % 5 <> 0
    "Even numbers", fun x -> x % 2 = 0
    "Won't reach here", fun x -> x % 7 <> 0
]

{ 1..20 } |> filterSeq intFiltersWithLabels <| List.empty

2 个答案:

答案 0 :(得分:4)

我要做的是制作一个过滤器列表,以及一次一个地应用它们的递归函数。如果刚刚应用的过滤器返回一个空序列,则停止,打印刚刚清空输入的过滤器,然后返回该空序列。否则继续循环遍历递归函数,依次取列表中的 next 过滤器,直到你最终没有输入或者你已经遍历整个过滤器列表并且在传递后仍然有一些输入所有过滤器。

这里有一些示例代码来说明我的意思。请注意我是如何在每个过滤器函数前放置标签的,因此我看不到像“<fun:filtersWithLabels@4>过滤器清空输入”这样的输出,而是我看到每个过滤器都有一个合理的人类可读标签

let rec filterSeq filterList input =
    match filterList with
    | [] -> input
    | (label, filter) :: filters ->
        let result = input |> Seq.filter filter
        if result |> Seq.isEmpty then
            printfn "The \"%s\" filter emptied out the input" label
            Seq.empty
        else
            filterSeq filters result

let intFiltersWithLabels = [
    "Odd numbers", fun x -> x % 2 <> 0
    "Not divisible by 3", fun x -> x % 3 <> 0
    "Not divisible by 5", fun x -> x % 5 <> 0
    "Even numbers", fun x -> x % 2 = 0
    "Won't reach here", fun x -> x % 7 <> 0
]

{ 1..20 } |> filterSeq filtersWithLabels
// Prints: The "Even numbers" filter emptied out the input

如果要打印所有过滤器直到清空输入的过滤器,那么您只需将该printfn调用向上移动一行,超出if表达式。输入为空时递归停止的事实意味着在清空输入的过滤器之后您将看不到任何printfn调用。

请注意,我编写函数的方式假设您的原始输入不为空。如果您的原始输入为空,则该函数将记入第一个用于清空输入的过滤器,并将打印第一个过滤器的标签。你可以通过在检查空结果之前检查空输入来轻松解决这个问题,但我没有打扰,因为这只是演示代码。如果您的实际输入在实际用例中可能为空,请注意这一点。

更新:如果您需要返回标签列表,而不仅仅是打印它们,那么请将其作为您通过filterSeq功能传递的第二个参数。像这样:

let matchingFilters filterList input =
    let rec filterSeq filterList labelsSoFar input =
        match filterList with
        | [] -> input, []  // Note NO labels returned in this case!
        | (label, filter) :: filters ->
            let result = input |> Seq.filter filter
            if result |> Seq.isEmpty then
                Seq.empty, (label :: labelsSoFar)
            else
                filterSeq filters (label :: labelsSoFar) result
    let result, labels = filterSeq filterList [] input
    result, List.rev labels

let filtersWithLabels = [
    "Odd numbers", fun x -> x % 2 <> 0
    "Not divisible by 3", fun x -> x % 3 <> 0
    "Not divisible by 5", fun x -> x % 5 <> 0
    "Even numbers", fun x -> x % 2 = 0
    "Won't reach here", fun x -> x % 7 <> 0
]

{ 1..20 } |> matchingFilters filtersWithLabels
// Returns: ["Odd numbers"; "Not divisible by 3"; "Not divisible by 5"; "Even numbers"]

关于这个版本的函数需要注意几点:听起来你想要的是如果过滤器一直运行而没有清空输入,那么你想要返回NO过滤器标签。如果我误解了您,请将| [] -> input, []行替换为| [] -> input, labelsSoFar以获取所有输出中的标签。需要注意的第二件事是我改变了这个函数的“形状”:它不返回seq,而是返回2元组(结果seq,过滤器标签列表)。如果结果seq不为空,则过滤器标签列表将为空,但如果结果seq结束为空,则过滤器标签列表将包含已应用的所有过滤器,而不仅仅是所有缩小输入大小的过滤器。

如果您真正需要的是检查输入的大小是否减少并仅打印 过滤掉某些内容的过滤器标签,那么请查看Funk的答案,了解如何检查,但是请注意,Seq.length必须贯穿整个原始序列,然后将所有过滤器应用到该点,每次。所以这是一个缓慢的操作。如果您的输入数据集很大,那么最好坚持使用Seq.empty逻辑。玩弄它并决定最适合您需求的东西。

答案 1 :(得分:2)

您可以使用装饰器将日志记录/错误处理代码与业务逻辑分开。

首先,我们的记录器。

open System.Text

type Logger() =
    let sb = StringBuilder()
    member __.log msg =
        sprintf "Element doesn't contain %s ; " msg |> sb.Append |> ignore
    member __.getMessage() =
        sb.ToString()

现在,我们想要包装Seq.filter,以便每次我们过滤掉一些元素时都会记录。

let filterBuilder (logger:Logger) msg f seq = 
    let res = Seq.filter f seq
    if Seq.length seq > Seq.length res then logger.log msg
    res

结束一个例子。

let logger = Logger()
let filterLog msg f seq = filterBuilder logger msg f seq 

let seq = ["foo" ; "bar"]

let r =
    seq 
    |> filterLog "f"
        (fun s -> s.Contains("f")) 
    |> filterLog "o"
        (fun s -> s.Contains("o")) 
    |> filterLog "b"
        (fun s -> s.Contains("b")) 
    |> filterLog "a"
        (fun s -> s.Contains("a")) 

logger.getMessage()
  

val it:string =“元素不包含f;元素不包含b;”

"bar"立即被过滤掉,产生第一条消息。 "foo"第三次出现。另请注意,该行中的第二个和最后一个管道不记录任何消息。