6

Is there a way to generate a string in FsCheck by selecting just one item from each of a list of strings and then concatenating the result?

I'm just completely stuck and can't seem to figure it out. I've looked at the docs and in the github repo for something similar. And I've done most of my reading on FsCheck from FSharpForFunAndProfit.

This is something like what I would be thinking of:

let rand = System.Random()
let randInt max = rand.Next(0, max)

let selectLetter (string: string) = 
    let whichLettersIndex = String.length string |> randInt
    string.Substring(whichLettersIndex, 1)

let generateOddlySpelledWord listOfStrings = 
    List.map selectLetter listOfStrings
    |> String.concat ""

let usingGenerateOddlySpelledWord =
    generateOddlySpelledWord ["zZ"; "oO0Ò"; "eEê"]

That should generate something like "Z0ê" or "zÒE".

Thomas Sobieck
  • 1,416
  • 1
  • 20
  • 27

1 Answers1

6

Does this do what you want?

open FsCheck

let createGenerators (l : string seq) =
    l |> Seq.map Gen.elements |> Seq.toList

type OddlySpelledWords =
    static member String() =
        ["zZ"; "oO0Ò"; "eEê"]
        |> createGenerators
        |> Gen.sequence
        |> Gen.map (List.map string >> String.concat "")
        |> Arb.fromGen

Ad-hoc test:

open FsCheck.Xunit

[<Property(Arbitrary = [| typeof<OddlySpelledWords> |])>]
let test (s : string) =
    printfn "%s" s

Output (truncated):

  z0ê
  ZÒe
  ZOe
  zoê
  ZÒe
  zoê
  Z0e
  zoê
  z0ê
  ZOe
  zÒê
  z0E
  zoe

Explanation

The createGenerators function has the type seq string -> Gen<char> list, and it creates a Gen from each string using Gen.elements, because a string is also a char seq; Gen.elements creates a Gen that will pick one of these char values from each string.

Then it uses Gen.sequence to convert the Gen<char> list into a Gen <char list>, and then maps from there.


BTW, you can also inline createGenerators:

type OddlySpelledWords =
    static member String() =
        ["zZ"; "oO0Ò"; "eEê"]
        |> List.map Gen.elements
        |> Gen.sequence
        |> Gen.map (List.map string >> String.concat "")
        |> Arb.fromGen
Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
  • +1 Great answer, and also well crafted (as shown from [its edits](http://stackoverflow.com/posts/28798955/revisions))! – How about using `List.reduce (+)` instead of `String.concat ""`? – Nikos Baxevanis Mar 02 '15 at 13:31
  • 2
    The problem with `List.reduce` is that it's fragile, so I tend to avoid it. Try this if you don't believe me: `List.empty |> List.reduce (+)`. – Mark Seemann Mar 02 '15 at 14:15
  • +1 Indeed, out of curiosity, `List.empty |> List.reduce (+)` throws an ArgumentException saying that the input list was empty. – Nikos Baxevanis Mar 02 '15 at 14:40