Вернуть условие в фитлер? - PullRequest
0 голосов
/ 02 июня 2018

У меня есть следующий код для фильтрации 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 голосов
/ 02 июня 2018

Вы можете отделить код регистрации / обработки ошибок от вашей бизнес-логики с помощью декоратора.

Сначала наш регистратор.

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 = "Element hasn 't содержит f; элемент не содержит b; "

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

0 голосов
/ 02 июня 2018

Что я хотел бы сделать, это составить список фильтров и рекурсивную функцию, которая применяет их по одному.Если фильтр, который только что был применен, возвращает пустую последовательность, остановитесь, напечатайте фильтр, который только что опустел ваш ввод, и верните эту пустую последовательность.В противном случае продолжайте циклически повторять рекурсивную функцию, применяя фильтр 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"]

Несколько замечаний по поводу этой версии функции: звучит так, как будто вы хотите, чтобы, если фильтры работали до конца, не опустошая ввод, вы хотитеНИКАКИЕ метки фильтра не возвращаются.Если я вас неправильно понял, замените строку | [] -> input, [] на | [] -> input, labelsSoFar, чтобы получить все метки в выводе.Второе, на что следует обратить внимание, это то, что я изменил «форму» этой функции: вместо возврата seq он возвращает 2-х кортеж (результат seq, список меток фильтра).Список меток фильтров будет пустым, если результат seq не пустой, но если результат seq окажется пустым, то список меток фильтров будет содержать всех примененных фильтров , а не только всефильтры, уменьшающие размер входных данных.

Если вам действительно нужно проверить, не уменьшен ли размер входных данных, и вывести только метки фильтров, отфильтровавших что-то, тогдапосмотрите на ответ Фанка о том, как это проверить, но помните, что Seq.length должен пройти через всю исходную последовательность и применить все фильтры до этой точки, каждый раз .Так что это медленная операция.Если ваш входной набор данных большой, то лучше придерживаться логики Seq.empty.Поиграйте с ним и решите, что лучше всего соответствует вашим потребностям.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...