我有以下代码来过滤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
答案 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"
第三次出现。另请注意,该行中的第二个和最后一个管道不记录任何消息。