我需要帮助翻译IndexOfAny for string的自定义扩展,因为现有框架没有匹配字符串值的IndexOfAny。已经翻译了我自己的。但是我不知道如何通过返回值突破循环。任何想法如何摆脱循环或更好的解决方案。 以下是我的翻译。
C#
public static int IndexOfAnyCSharp(this string str, string[] anyOff) {
if (str != null && anyOff != null)
foreach (string value in anyOff) {
int index = str.IndexOf(value);
if (index > -1) return index;
}
return -1;
}
F#
[<Extension>]
static member public IndexOfAnyFSharp(str:string, anyOff:string[]) =
match str <> null && anyOff <> null with
| true ->
let mutable index = -1
for value in anyOff do
if index = -1 then
index <- str.IndexOf(value)
index
| false -> -1
答案 0 :(得分:7)
Seq.tryFind
是你的朋友。一个基本的构建块就像是
let IndexOfAny (s: string, manyStrings: string seq) =
manyStrings
|> Seq.map (fun m -> s.IndexOf m)
|> Seq.tryFind (fun index -> index >= 0)
如果没有匹配,这将返回None
- 这比返回-1更加惯用F#:编译器将强制您考虑没有匹配的情况。
更新:您可能更喜欢:
let IndexOfAny (s: string, manyStrings: string seq) =
manyStrings
|> Seq.tryPick (fun m ->
match s.IndexOf m with
| -1 -> None
| i -> Some i
)
答案 1 :(得分:1)
使用Array.tryFind
或Seq
是惯用的F#,但也具有与C#循环不同的性能特征。特别是Result<int*int, Unit>
模块在性能和内存开销方面存在问题。这有时很重要。
正如其他人在F#中所指出的那样,你无法从for循环中提前退出。我曾经为此烦恼,但不再是因为F#支持尾调用消除,允许我们将循环实现为尾递归函数。
下面是一个关于如何使用尾递归的示例。下面的代码应该与C#循环大致相似。我并没有完全实现C#的语义,而是返回Result
。我使用option
而不是Result
因为// If our function is callable from C# we can use active patterns as a neat way to protect our
// F# code from null values
// Functions that are only callable from F# code we don't need to protect as long as we protect
// the entry points
let inline (|DefaultTo|) dv v = if System.Object.ReferenceEquals (v, null) then dv else v
let inline (|NotNull|) v = if System.Object.ReferenceEquals (v, null) then raise (System.NullReferenceException ()) else v
let emptySet : string [] = [||]
let indexOfSet (DefaultTo "" str) (DefaultTo emptySet set) : Result<int*int, unit> =
// In F# tail recursion is used as a more powerful looping construct
// F# suppports tail call elimination meaning under the hood this is
// implemented as an efficient loop
// Note: I pass str and set as argument in order to make F# doesn't
// create new lambda object that closes over them (reduces GC load)
let rec loop (str : string) (set : string []) i =
if i < set.Length then
let index = str.IndexOf set.[i]
if index = -1 then loop str set (i + 1)
else Ok (i, index)
else
Error ()
loop str set 0
printfn "%A" <| indexOfSet null null
printfn "%A" <| indexOfSet null [| "abc"; "ab"; "a" |]
printfn "%A" <| indexOfSet "" [| "abc"; "ab"; "a" |]
printfn "%A" <| indexOfSet "a" [| "abc"; "ab"; "a" |]
printfn "%A" <| indexOfSet "ab" [| "abc"; "ab"; "a" |]
printfn "%A" <| indexOfSet "abc" [| "abc"; "ab"; "a" |]
printfn "%A" <| indexOfSet "da" [| "abc"; "ab"; "a" |]
printfn "%A" <| indexOfSet "dab" [| "abc"; "ab"; "a" |]
printfn "%A" <| indexOfSet "dabc" [| "abc"; "ab"; "a" |]
没有添加GC压力,因为它是结构类型。
还包括一个巧妙的方法IMO,以保护F#代码免受空值的危害。
{{1}}