问题描述
当第一项符合条件时,我想停止遍历列表。
let list1 = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]
let list2 = [1; 2; 9]
list1 |> List.iter (fun item ->
match List.contains item list2 with
| false -> printfn " Not Present %A" item
| true -> printfn "%A" item)
我只想打印此处不存在的第一个项目(在这种情况下为3)。在编写功能代码时该怎么做?
解决方法
函数世界中迭代的最终基础是递归:您的函数计算出一些东西,然后调用自身进行下一步。或者,它决定停止,然后不调用自身。很简单。
您的特殊情况非常适合此范例:
let rec loop list =
match list with
| [] -> () // no more list - just return
| head::tail ->
if List.contains head list2
then loop tail // deciding to call myself
else printfn " Not Present %A" head // deciding not to call myself
loop list1 // Kick off the process
但是iter
呢?好吧,来自List
模块的所有这些奇特功能-包括iter
-都是建立在相同的递归方案之上的。他们也决定在每一个步骤上都打电话给自己或不打电话。对于某些参数,您可以通过参数影响决策,而对于其他参数,则不能。
碰巧的是,iter
不能。 iter
将始终调用自身进行下一步,因此它将始终遍历整个列表。您无能为力。
但是您可以通过 来影响其他功能,以决定是否要递归。其中有很多,但是出于您的目的,我认为List.tryFind
最合适。它使用另一个函数作为参数,该函数应返回true
或false
。如果返回true
,则tryFind
将停止迭代并返回当前元素。正是您所需要的。
list1 |> List.tryFind (fun x ->
if List.contains x list2
then false
else printfn " Not Present %A"; true
)
,
您可以在此处使用tryPick。 此实现的一个优点是IO功能(printfn)与算法保持独立。
let notPresent =
list1
|> List.tryPick
(fun item ->
if List.contains item list2
then None
else Some item)
match notPresent with
| None -> ()
| Some x -> printfn " Not Present %A" x
,
您可以利用F# Sequence来懒惰地评估元素。在序列表达式中,使用旧的for
循环来查找所需的元素并yield
。
要“模拟”“中断迭代”,您只需使用内置功能,例如Seq.tryHead
或Seq.take
或Seq.truncate
。
如您所见,这种方法的好处是可以很容易地扩展以找到两个或三个符合要求的第一个元素或所有元素。
let notIn list x = list |> List.contains x |> not // just a helper function
seq { for x in list1 do
if x |> notIn list2 then yield x }
|> Seq.tryHead // or Seq.take 2 or Seq.truncate 3
|> printfn "%A"
有时,使用类似for
的“类似命令式”的方法可以使代码更易于理解(据我观察,包括我在内的大多数程序员都具有命令式背景)。请注意,上面的代码仍然可以正常工作。