递归是在函数式语言中编写循环的基本机制,因此如果您需要迭代字符(就像您在示例中所做的那样),那么递归就是您所需要的。
如果您想改进您的代码,那么您可能应该避免使用line.[2..]
,因为这将是低效的(字符串不是为这种处理而设计的)。最好将字符串转换为列表,然后进行处理:
let convert (line:string) =
let rec loop acc line =
match line with
| ' '::' '::rest -> loop (acc + 1) rest
| _ -> (acc, line)
loop 0 (List.ofSeq line)
您可以使用标准库中的各种函数以更短的方式实现这一点,但它们通常也是递归的(您只是看不到递归!),所以我认为使用类似Seq.unfold
andSeq.fold
的函数仍然是递归的(而且看起来很比你的代码更复杂)。
使用标准库的更简洁的方法是使用该TrimLeft
方法(请参阅注释),或使用标准 F# 库函数,执行以下操作:
let convert (line:string) =
// Count the number of spaces at the beginning
let spaces = line |> Seq.takeWhile (fun c -> c = ' ') |> Seq.length
// Divide by two - we want to count & skip two-spaces only
let count = spaces / 2
// Get substring starting after all removed two-spaces
count, line.[(count * 2) ..]
编辑关于字符串与列表处理的性能,问题是切片分配了一个新字符串(因为这是在.NET平台上表示字符串的方式),而切片列表只会更改引用。这是一个简单的测试:
let rec countList n s =
match s with
| x::xs -> countList (n + 1) xs
| _ -> n
let rec countString n (s:string) =
if s.Length = 0 then n
else countString (n + 1) (s.[1 ..])
let l = [ for i in 1 .. 10000 -> 'x' ]
let s = new System.String('x', 10000)
#time
for i in 0 .. 100 do countList 0 l |> ignore // 0.002 sec (on my machine)
for i in 0 .. 100 do countString 0 s |> ignore // 5.720 sec (on my machine)