简体   繁体   中英

function 'startsWithVowel' in F#

Given a list of vowels, I have written the function startsWithVowel to investigate if a word starts with a vowel. As you can see I use exception as controlflow, and that's not ideal. How to implement this better?

let vowel = ['a'; 'e'; 'i'; 'o'; 'u']

let startsWithVowel(str :string) = 
    try
        List.findIndex (fun x -> x = str.[0]) vowel
        true
    with
        | :? System.Collections.Generic.KeyNotFoundException -> false

UPDATE : tx to all : once again I experience : never hesitate to ask a newbee question. I see a lot of very useful remarks, keep them coming :-)

try using the exists method instead

let vowel = ['a'; 'e'; 'i'; 'o'; 'u']

let startsWithVowel(str :string) = List.exists (fun x -> x = str.[0]) vowel

exists returns true if any element in the list returns true for the predicate and false otherwise.

Use sets for efficient lookup

let vowels = Set.ofList ['a'; 'e'; 'i'; 'o'; 'u']

let startsWithVowel(str : string) = vowels |> Set.mem (str.[0])

Yet another alternative, tryFindIndex returns Some or None rather than throwing an exception:

> let vowel = ['A'; 'E'; 'I'; 'O'; 'U'; 'a'; 'e'; 'i'; 'o'; 'u']

let startsWithVowel(str :string) = 
    match List.tryFindIndex (fun x -> x = str.[0]) vowel with
    | Some(_) -> true
    | None -> false;;

val vowel : char list = ['A'; 'E'; 'I'; 'O'; 'U'; 'a'; 'e'; 'i'; 'o'; 'u']
val startsWithVowel : string -> bool

> startsWithVowel "Juliet";;
val it : bool = false
> startsWithVowel "Omaha";;
val it : bool = true

I benchmarked a few approaches mentioned in this thread (Edit: added nr. 6).

  1. The List.exists approach (~0.75 seconds)
  2. The Set.contains approach (~0.51 seconds)
  3. String.IndexOf (~0.25 seconds)
  4. A non-compiled regex (~5 - 6 seconds)
  5. A compiled regex (~1.0 seconds)
  6. Pattern matching (why did I forget this the first time?) (~0.17 seconds)

I filled a list with 500000 random words and filtered it through various startsWithVowel functions, repeated 10 times.

Test code:

open System.Text.RegularExpressions

let startsWithVowel1 =
    let vowels = ['a';'e';'i';'o';'u']
    fun (s:string) -> vowels |> List.exists (fun v -> s.[0] = v)

let startsWithVowel2 =
    let vowels = ['a';'e';'i';'o';'u'] |> Set.ofList
    fun (s:string) -> Set.contains s.[0] vowels

let startsWithVowel3 (s:string) = "aeiou".IndexOf(s.[0]) >= 0

let startsWithVowel4 str = Regex.IsMatch(str, "^[aeiou]")

let startsWithVowel5 = 
    let rex = new Regex("^[aeiou]",RegexOptions.Compiled)
    fun (s:string) -> rex.IsMatch(s)

let startsWithVowel6 (s:string) =
    match s.[0] with
    | 'a' | 'e' | 'i' | 'o' | 'u' -> true
    | _ -> false  

//5x10^5 random words
let gibberish = 
    let R = new System.Random()
    let (word:byte[]) = Array.zeroCreate 5
    [for _ in 1..500000 -> 
        new string ([|for _ in 3..R.Next(4)+3 -> char (R.Next(26)+97)|])
    ]

//f = startsWithVowelX, use #time in F# interactive for the timing
let test f =
    for _ in 1..10 do
        gibberish |> List.filter f |> ignore

My humble conclusion: EDIT: The imperative IndexOf F# pattern match wins the speed contest.

The Set.contains approach wins the beauty contest.

Note also that a number of exception-throwing functions have non-exception equivalents that return option rather than throwing - these typically have a 'try' prefix in the function name.

List.tryFindIndex:

http://msdn.microsoft.com/en-us/library/ee340224(VS.100).aspx

See also

http://lorgonblog.spaces.live.com/blog/cns!701679AD17B6D310!181.entry

Using regular expressions:

open System.Text.RegularExpressions

let startsWithVowel str = Regex.IsMatch(str, "^[AEIOU]", RegexOptions.IgnoreCase)
let startsWithVowel (word:string) = 
    let vowels = ['a';'e';'i';'o';'u']
    List.exists (fun v -> v = word.[0]) vowels

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM