Связанная функция списка списков и обратные результаты
Я написал эту функцию F #, чтобы разбить список до определенной точки и не дальше - как перекресток между takeWhile
и partition
.
let partitionWhile c l =
let rec aux accl accr =
match accr with
| [] -> (accl, [])
| h::t ->
if c h then
aux (h::accl) t
else
(accl, accr)
aux [] l
Единственная проблема в том, что "взятые" элементы меняются на противоположные:
> partitionWhile ((>=) 5) [1..10];;
val it : int list * int list = ([5; 4; 3; 2; 1], [6; 7; 8; 9; 10])
Помимо использования вызова rev
, существует ли способ, которым эта функция могла бы быть записана, чтобы первый список был в правильном порядке?
Ответы
Ответ 1
Здесь версия на основе продолжения. Он возвращает хвост и возвращает список в исходном порядке.
let partitionWhileCps c l =
let rec aux f = function
| h::t when c h -> aux (fun (acc, l) -> f ((h::acc), l)) t
| l -> f ([], l)
aux id l
Вот несколько тестов, которые следует обсудить после ответа Брайана (и версии аккумулятора для справки):
let partitionWhileAcc c l =
let rec aux acc = function
| h::t when c h -> aux (h::acc) t
| l -> (List.rev acc, l)
aux [] l
let test =
let l = List.init 10000000 id
(fun f ->
let r = f ((>) 9999999) l
printfn "%A" r)
test partitionWhileCps // Real: 00:00:06.912, CPU: 00:00:07.347, GC gen0: 78, gen1: 65, gen2: 1
test partitionWhileAcc // Real: 00:00:03.755, CPU: 00:00:03.790, GC gen0: 52, gen1: 50, gen2: 1
Cps
усредненный ~ 7s, Acc
~ 4s. Короче говоря, продолжения вы ничего не покупаете для этого упражнения.
Ответ 2
Я ожидаю, что вы сможете использовать продолжения, но вызов List.rev
в конце - лучший способ пойти.
Ответ 3
Я обычно предпочитаю "Последовательности над списком", поскольку они ленивы, и вы получили функции List.toSeq
и Seq.toList
для преобразования между ними. Ниже приведена реализация вашей функции partitionWhile
с использованием последовательностей.
let partitionWhile (c:'a -> bool) (l:'a list) =
let fromEnum (e:'a IEnumerator) =
seq { while e.MoveNext() do yield e.Current}
use e = (l |> List.toSeq).GetEnumerator()
(e |> fromEnum |> Seq.takeWhile c |> Seq.toList)
,(e |> fromEnum |> Seq.toList)
Ответ 4
Вы можете переписать эту функцию следующим образом:
let partitionWhile c l =
let rec aux xs =
match xs with
| [] -> ([], [])
| h :: t ->
if c h then
let (good, bad) = aux t in
(h :: good, bad)
else
([], h :: t)
aux l
Да, поскольку Брайан отметил, что он больше не является хвостом рекурсивным, но он отвечает на вопрос, как указано. Кстати, span
в Haskell реализована точно так же в Hugs:
span p [] = ([],[])
span p [email protected](x:xs')
| p x = (x:ys, zs)
| otherwise = ([],xs)
where (ys,zs) = span p xs'
Хорошей причиной для предпочтения этой версии в Haskell является лень: в первой версии все хорошие элементы посещаются до того, как список будет отменен. Во второй версии первый хороший элемент может быть немедленно возвращен.
Ответ 5
Я не думаю, что я единственный, кто многому научился (борясь с) решением Daniel CPS. Пытаясь понять это, это помогло мне изменить несколько потенциально (для начинающих) двусмысленных списков ссылок, например:
let partitionWhileCps cond l1 =
let rec aux f l2 =
match l2 with
| h::t when cond h -> aux (fun (acc, l3) -> f (h::acc, l3)) t
| l4 -> f ([], l4)
aux id l1
(Обратите внимание, что "[]" в совпадении l4 - это начальное значение acc.) Мне нравится это решение, потому что он чувствует себя меньше, не имея необходимости использовать List.rev, сверляя до конца первого списка и создавая второй список назад. Я думаю, что другим основным способом избежать .rev было бы использовать рекурсию хвоста с помощью операции cons. Некоторые языки оптимизируют "хвостовую рекурсию мод-минус" так же, как правильная рекурсия хвоста (но Дон Симе сказал, что это не произойдет в F #).
Таким образом, это не является хвостовым рекурсивным безопасным в F #, но это делает мой ответ ответом и избегает List.rev(это уродливо иметь доступ к двум элементам кортежа и будет более подходящим параллельно с подходом cps иначе, Я думаю, как если бы мы только вернули первый список):
let partitionWhileTrmc cond l1 =
let rec aux acc l2 =
match l2 with
| h::t when cond h -> ( h::fst(aux acc t), snd(aux acc t))
| l3 -> (acc, l3)
aux [] l1