Редактировать: версия без версии с использованием foldBack, добавленная ниже.
Вот некоторый код, который использует списки и хвостовую рекурсию:
//divides a list L into chunks for which all elements match pred
let divide pred L =
let rec aux buf acc L =
match L,buf with
//no more input and an empty buffer -> return acc
| [],[] -> List.rev acc
//no more input and a non-empty buffer -> return acc + rest of buffer
| [],buf -> List.rev (List.rev buf :: acc)
//found something that matches pred: put it in the buffer and go to next in list
| h::t,buf when pred h -> aux (h::buf) acc t
//found something that doesn't match pred. Continue but don't add an empty buffer to acc
| h::t,[] -> aux [] acc t
//found input that doesn't match pred. Add buffer to acc and continue with an empty buffer
| h::t,buf -> aux [] (List.rev buf :: acc) t
aux [] [] L
использование:
> divide pred testlist;;
val it : string list list =
[["*text1"; "*text2"]; ["*text5"; "*text6"; "*text7"]]
Использование списка в качестве структуры данных для буфера означает, что его всегда нужно инвертировать при выводе содержимого. Это не может быть проблемой, если отдельные куски имеют скромный размер. Если скорость / эффективность становится проблемой, вы можете использовать Queue<'a>
или `List <'a>' для буферов, для которых добавление происходит быстро. Но использование этих структур данных вместо списков также означает, что вы потеряете мощное сопоставление с образцом списка. На мой взгляд, возможность сопоставления списков совпадений перевешивает наличие нескольких вызовов List.rev.
Вот потоковая версия, которая выводит результат по одному блоку за раз. Это позволяет избежать использования List.rev на аккумуляторе в предыдущем примере:
let dividestream pred L =
let rec aux buf L =
seq { match L, buf with
| [],[] -> ()
| [],buf -> yield List.rev buf
| h::t,buf when pred h -> yield! aux (h::buf) t
| h::t,[] -> yield! aux [] t
| h::t,buf -> yield List.rev buf
yield! aux [] t }
aux [] L
Эта потоковая версия избегает List.rev
на аккумуляторе. Использование List.foldBack
также может использоваться для предотвращения обращения накопленных кусков.
обновление: вот версия с использованием foldBack
//divides a list L into chunks for which all elements match pred
let divide2 pred L =
let f x (acc,buf) =
match pred x,buf with
| true,buf -> (acc,x::buf)
| false,[] -> (acc,[])
| false,buf -> (buf::acc,[])
let rest,remainingBuffer = List.foldBack f L ([],[])
match remainingBuffer with
| [] -> rest
| buf -> buf :: rest