F# 基于谓词将列表拆分为列表列表
(我知道,但它与序列有关,这不是我的问题) 给定此输入(例如): 我希望能够调用F# 基于谓词将列表拆分为列表列表,f#,F#,(我知道,但它与序列有关,这不是我的问题) 给定此输入(例如): 我希望能够调用MyFunc pred testlist并获得以下输出: [ ["*text1";"*text2"]; ["*text5";"*text6";"*text7"] ] 这是我当前的解决方案,但我不太喜欢嵌套的List.revs(忽略它将Seq作为输入的事实) 编辑:使用下面添加的折叠版本 下面是一些使用列表和尾部递归的代码: //divides a list L into chunks for whi
MyFunc pred testlist
并获得以下输出:
[
["*text1";"*text2"];
["*text5";"*text6";"*text7"]
]
这是我当前的解决方案,但我不太喜欢嵌套的List.revs(忽略它将Seq作为输入的事实)
编辑:使用下面添加的折叠版本
下面是一些使用列表和尾部递归的代码:
//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',因为附加速度很快。但是使用这些数据结构而不是列表也意味着失去了强大的列表模式匹配。在我看来,能够对匹配列表进行模式化比几个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
也可以用来避免对累积的块进行反转
更新:这里有一个使用折叠的版本
F#core库中有一个List.partition
函数(如果您想实现它只是为了让它工作,而不是为了自己学习如何编写递归函数)。使用此函数,您可以编写以下内容:
> testlist |> List.partition (fun s -> s.StartsWith("*"))
val it : string list * string list =
(["*text1"; "*text2"; "*text5"; "*text6"; "*text7"], ["text3"; "text4"])
请注意,此函数返回一个元组,而不是返回列表列表。这与您想要的有点不同,但是如果谓词只返回true或false,那么这就更有意义了
返回元组的partition
函数的实现也稍微简单一些,因此它可能对学习有用:
let partition pred list =
// Helper function, which keeps results collected so
// far in 'accumulator' arguments outTrue and outFalse
let rec partitionAux list outTrue outFalse =
match list with
| [] ->
// We need to reverse the results (as we collected
// them in the opposite order!)
List.rev outTrue, List.rev outFalse
// Append element to one of the lists, depending on 'pred'
| x::xs when pred x -> partitionAux xs (x::outTrue) outFalse
| x::xs -> partitionAux xs outTrue (x::outFalse)
// Run the helper function
partitionAux list [] []
分流的另一个版本
:
let shunt pred lst =
let rec tWhile pred lst =
match lst with
| [] -> [], []
| hd :: tl when pred hd -> let taken, rest = tWhile pred tl
(hd :: taken), rest
| lst -> [], lst
let rec collect = function
| [] -> []
| lst -> let taken, rest = tWhile pred lst
taken :: (collect (snd (tWhile (fun x -> not (pred x)) rest)))
collect lst
这一个避免了
List.rev
,但它不是尾部递归的-因此只适用于小列表。只需先将列表反转一次,然后轻松地按顺序构建结构:
let Shunt p l =
let mutable r = List.rev l
let mutable result = []
while not r.IsEmpty do
let mutable thisBatch = []
while not r.IsEmpty && not(p r.Head) do
r <- r.Tail
while not r.IsEmpty && p r.Head do
thisBatch <- r.Head :: thisBatch
r <- r.Tail
if not thisBatch.IsEmpty then
result <- thisBatch :: result
result
让我们分路=
设可变r=List.rev l
让可变结果=[]
而不是r。我是空的
让可变thisBatch=[]
而不是r.IsEmpty&¬(p.r.Head)做什么
还有一个
let partition pred lst =
let rec trec xs cont =
match xs with
| [] -> ([],[]) |> cont
| h::t when pred h -> (fun (y,n) -> h::y,n) >> cont |> trec t
| h::t -> (fun (y,n) -> y,h::n) >> cont |> trec t
trec lst id
然后我们可以定义分流:
let shunt pred lst = lst |> partition pred |> (fun (x,y) -> [x;y])
对不起,我的错误,它实际上应该只是一个字符串列表,这会简化您的答案吗?它消除了我所采取的额外的展平步骤。分割(流)算法保持不变。我会编辑我的答案。很好,两个答案合一,两个答案都“更快”(基于我的最小并排),功能性和“直通”。我想我更喜欢divide2,短,甜,并且没有堆栈溢出的风险(我相信)。我无法抗拒对divide2的赞扬。我在火车上,忍不住笑了起来,真是太美了。谢谢你的喜悦。我对函数式代码还不熟悉,所以我想问一下,这个命令式代码如何优于Petricek的函数式版本?好吧,我的解决方案符合你的要求,而他的没有。我想这一定是我问问题的方式,我有几个类似的“分组”答案,但没有回答我的问题。对你的命令式解决方案感到惊讶,不是因为你错了,只是因为我太习惯于用“F#方式”来做,以至于我甚至没有想到要朝那个方向看。顺便说一句,考虑到评论在那里是禁用的,我会用这个空间对你博客()上的一元解析器组合器系列表示赞许,我刚刚“完成”了C部分。你说卢卡的版本更好,但你的版本更容易理解。你的波特率又对了:)不,对不起,我想把与pred相对应的每一组行分开,然后丢弃其他的。不,对不起,我想把与pred相对应的每一组行分开,然后丢弃其他的。迟来的附言:这个问题的标题写得很糟糕,所以有几个答案更适合这个问题。
let Shunt p l =
let mutable r = List.rev l
let mutable result = []
while not r.IsEmpty do
let mutable thisBatch = []
while not r.IsEmpty && not(p r.Head) do
r <- r.Tail
while not r.IsEmpty && p r.Head do
thisBatch <- r.Head :: thisBatch
r <- r.Tail
if not thisBatch.IsEmpty then
result <- thisBatch :: result
result
let partition pred lst =
let rec trec xs cont =
match xs with
| [] -> ([],[]) |> cont
| h::t when pred h -> (fun (y,n) -> h::y,n) >> cont |> trec t
| h::t -> (fun (y,n) -> y,h::n) >> cont |> trec t
trec lst id
let shunt pred lst = lst |> partition pred |> (fun (x,y) -> [x;y])