F# 在fitler中返回条件?

F# 在fitler中返回条件?,f#,F#,我有下面的代码来过滤Seq,如果没有返回,则返回错误 let s = nodes |> Seq.filter(fun (a, _, _, _) -> if a.ToLower().Contains(key1)) // condition 1 then true else false // Error m

我有下面的代码来过滤
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
让rec filterSeq filterList输入MSG=
将过滤器列表与
|[]->输入,msgs
|(标签,过滤器)::过滤器->
让结果=输入|>顺序过滤器
如果结果|>序号为空,则
printfn“过滤器\%s\”清空了输入“标签
Seq.empty,(List.append-msgs[标签])
其他的
filterSeq筛选结果(List.append msgs[标签])
让intFiltersWithLabels=[
“奇数”,乐趣x->x%2 0
“不能被3整除”,乐趣x->x%3 0
“不能被5整除”,乐趣x->x%5 0
“偶数”,乐趣x->x%2=0
“无法到达这里”,乐趣x->x%7 0
]

{1..20}|>filterSeq intFiltersWithLabels我要做的是创建一个过滤器列表,以及一个递归函数,一次应用一个过滤器。如果刚刚应用的筛选器返回空序列,则停止,打印刚刚清空输入的筛选器,然后返回空序列。否则,继续循环递归函数,依次使用列表中的下一个过滤器,直到最后没有输入,或者运行了整个过滤器列表,并且在通过所有过滤器后仍有一些输入

这里有一些示例代码来说明我的意思。请注意,我是如何将标签放在每个过滤器函数的前面的,这样我就不会看到像“过滤器清空了输入”这样的输出,而是看到每个过滤器都有一个合理的可读标签

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
关于这个版本的函数,有几点需要注意:听起来您想要的是,如果过滤器一直运行,而没有清空输入,那么您就不希望返回任何过滤器标签。如果我误解了您的意思,请将
|[]->input、[]
行替换为
|[]->input、labelsofar
,以获取输出中的所有标签。第二件需要注意的事情是,我改变了这个函数的“形状”:它不返回seq,而是返回一个2元组(result seq,过滤器标签列表)。如果结果序列不为空,则筛选器标签列表将为空,但如果结果序列最终为空,则筛选器标签列表将包含应用的所有筛选器,而不仅仅是减小输入大小的所有筛选器


如果您真正需要的是检查输入的大小是否减小,并且只打印过滤掉某些内容的过滤器的标签,那么请查看Funk的答案以了解如何检查,但请注意,
Seq.length
必须在整个原始序列中运行,并每次应用所有过滤器。所以这是一个缓慢的操作。如果您的输入数据集很大,那么最好使用
Seq.empty
逻辑。使用它并决定什么最适合您的需要。

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

首先,我们的记录器

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 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”
第三次出现。还要注意,行中的第二个和最后一个管道不要记录任何消息。

问题的最后一句没有意义。我把它通读了好几遍,但无法理解它的意思。你能试着重新表述一下这个问题吗?过滤器可能会逐渐删除一些元素。当s为空时,我希望错误消息显示一个筛选器(或一个筛选器之前的所有文件)使s从非空变为空。有趣的是,您的解决方案看起来确实更实用:),尽管使用其他高阶函数进行编写似乎很难。如果您可能需要其他类型的高阶函数(假设您需要
Seq.filter
Seq.map
)然后更改列表中的内容。不要让列表包含过滤器,而是让它包含“步骤”,其中步骤是
Seq
函数:
Seq.filter(fun x->x%20)
将是一个步骤,而
Seq.map(fun x->x+1)
也将是一个步骤。然后,您将执行
let result=input |>Seq.filter filter
,而不是
let result=input |>step
。当然,但这会增加不必要的复杂性,并失去灵活性。例如,您将在Seq.map之后检查Seq.isEmpty。或者,添加更多的复杂性,将其挑选出来。。。@Funk-您的解决方案也是一个很好的解决方案:一个简单的日志包装器是一个非常有用的工具,可以保存在您的工具箱中。顺便说一句,将一个函数包装在另一个函数周围也是非常有用的,所以不要觉得您的答案“功能不如我的”。使用
List.append labelsofar[label]
是O(N)因为F#list是单链表,你需要做N次,所以总共是O(N^2)。添加到F#list的头上是O(1),你做了N次,所以总共是O(N)。然后,因为你一直在添加到头上,所以列表是最新的第一个,你希望最不最近的第一个,所以你要做最后的
list.rev
(一个O(N)操作)一次。所以我这样做的方式是O(N),而你所问的
List.append
方法是O(N^2);这就是区别。我喜欢围绕工作函数的包装方法。我不喜欢这里的一件事是重复使用
Seq.length
,这是做两个额外的O(N)操作
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()