Решение F # без мутации

Ради интереса я прочитал эти вопросы интервью и попытался найти решения в обоих C # и F #, и я изо всех сил пытаюсь сделать следующее на идиоматическом F # без изменения логического значения или использования регулярного выражения:

Вам дана строка слов, содержащая один или несколько символов $, например: "foo bar foo $ bar $ foo bar $" Вопрос: Как удалить второе и третье вхождения $ из заданной строки ?

Мое императивное решение F # с мутацией:

let input = "foo bar foo $ bar $ foo bar $ "
let sb = new StringBuilder()
let mutable first = true

let f c=
    if c='$' && first then first<-false
    else sb.Append(c) |> ignore

input |> Seq.iter f

(И С # один):

var input = "foo bar foo $ bar $ foo bar $ ";
var sb = new StringBuilder();
bool first = true;
input.ForEach(c => {
    switch (c)
    {
        case '$' when first: first = false; break;
        default: sb.Append(c);break;
    };
});

person zmaten    schedule 16.10.2018    source источник


Ответы (2)


let f (s:string) =
    s.Split('$')
    |> Array.toList
    |> function
        | [] -> ""
        | [ a ] -> a
        | [ a; b ] -> a + "$" + b
        | a :: b :: c :: rest -> a + "$" + b + c + (rest |> String.concat "$")

f "foo bar foo $ bar $ foo bar $ "
// "foo bar foo $ bar  foo bar  "

f "1 $ 2 $ 3 $ 4 $ 5 $"
//"1 $ 2  3  4 $ 5 $"

Обратите внимание, что это решение удаляет только второй и третий экземпляры $. Если вы хотите удалить все, кроме первого, замените String.concat "$" на String.concat ""

person TheQuickBrownFox    schedule 16.10.2018
comment
Это правильно! Я играл с сопоставлением, но не мог этого сделать. Ваше решение действительно заставило меня понять, что я неправильно прочитал вопрос и удалял первый вместо других, так что спасибо вам за это. - person zmaten; 16.10.2018

let f (s:string) =
    s.Split('$')
    |> Seq.mapi (fun i t -> (if i > 3 || i = 1 then "$" else "") + t)
    |> String.concat ""

а вот еще один, который сканирует каждое char с помощью рекурсии tail и seq вычислительного выражения:

let f (s:string) =
    let rec chars n input = seq {
        match Seq.tryHead input with
        | Some '$' ->   if not(n = 1 || n = 2) then yield  '$'
                        yield! Seq.tail input |> chars (n+1)
        | Some c   ->   yield  c
                        yield! Seq.tail input |> chars n
        | None     ->   ()
    }
    chars 0 s
    |> fun cs -> new string(Seq.toArray cs)

Он может быть длиннее , но, вероятно, более эффективен, чем первый.

Изменить: Нет, это не более эффективно и не является хвостовой рекурсией, вероятно, потому, что это происходит внутри вычислительного выражения.

person AMieres    schedule 16.10.2018
comment
Ваше второе решение на самом деле на несколько порядков медленнее при использовании исходного примера ввода. Кажется, выделяется намного больше, потому что создано очень много _1 _ / _ 2_ объектов. Обычно я стараюсь избегать смешивания рекурсии и seq как для удобочитаемости, так и для производительности. - person TheQuickBrownFox; 17.10.2018
comment
Ты прав. Фактически, он даже не считает это хвостовой рекурсией, вероятно, потому, что это вычислительное выражение. Спасибо! - person AMieres; 17.10.2018