FsCheck Generators by Selecting From Pools of Possibilities
Asked Answered
A

1

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".

Assay answered 1/3, 2015 at 19:8 Comment(1)
Write the function you want that generates a string seq then apply Gen.elements.Swenson
B
5

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
Barrel answered 1/3, 2015 at 20:55 Comment(3)
+1 Great answer, and also well crafted (as shown from its edits)! – How about using List.reduce (+) instead of String.concat ""?Bettyannbettye
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<string> |> List.reduce (+).Barrel
+1 Indeed, out of curiosity, List.empty<string> |> List.reduce (+) throws an ArgumentException saying that the input list was empty.Bettyannbettye

© 2022 - 2024 — McMap. All rights reserved.