Skip to content

Instantly share code, notes, and snippets.

@swlaschin
Last active January 22, 2021 23:57
Show Gist options
  • Save swlaschin/8409306 to your computer and use it in GitHub Desktop.
Save swlaschin/8409306 to your computer and use it in GitHub Desktop.
System.IO.Directory.SetCurrentDirectory __SOURCE_DIRECTORY__
// The following DLLs were fetched using NuGet
#r @".\packages\FsCheck.0.9.2.0\lib\net40-Client\FsCheck.dll"
#r @".\packages\NUnit.2.6.3\lib\nunit.framework.dll"
open FsCheck
open NUnit.Framework
// ======================================
// Port of the ruby test code
// ======================================
module PortOfRubyCode =
let arabicToRoman arabic =
"" // implementation to do
[<Test>]
let ``For certain inputs, expect certain outputs``() =
let testpairs = [
(1,"I")
(2,"II")
(4,"IV")
(5,"V")
(9,"IX")
(10,"X")
// etc
(900,"CM")
(1000,"M")
(3497,"MMMCDXCVII")
]
for (arabic,expectedRoman) in testpairs do
let roman = arabicToRoman arabic
Assert.AreEqual(expectedRoman, roman)
// ======================================
// Testing all inputs
// ======================================
module TestingAllInputs =
let arabicToRoman arabic =
"" // implementation to do
let assertMaxRepetition str count input =
() // implementation to do
[<Test>]
let ``For all valid inputs, there must be a max of four "I"s in a row``() =
for i in [1..4000] do
let roman = arabicToRoman i
roman |> assertMaxRepetition "I" 4
let assertSuffix suffix input =
() // implementation to do
let assertNonSuffix suffix input =
() // implementation to do
[<Test>]
let ``For all valid inputs with two in the units, there must be exactly two "I"s at the end``() =
for i in [2..10..4000] do // 2 to 4000 by steps of 10
let roman = arabicToRoman i
roman |> assertSuffix "II"
roman |> assertNonSuffix "III"
// ======================================
// Property based testing example
// ======================================
module PropertyBasedTestingExample =
let arabicToRoman arabic =
"" // implementation to do
let assertMaxRepetition str count input =
() // implementation to do
// Define a property that should be true for all inputs
let ``has max rep of four Is`` arabic =
let roman = arabicToRoman arabic
roman |> assertMaxRepetition "I" 4
// Explicitly enumerate all inputs...
[<Test>]
let ``For all valid inputs, there must be a max of four "I"s``() =
for i in [1..4000] do
//check that the property holds
``has max rep of four Is`` i
// ...Or use FsCheck to generate inputs for you
do
let isInRange i = (i >= 1) && (i <= 4000)
// input is in range implies has max of four Is
let prop i = isInRange i ==> ``has max rep of four Is`` i
// check all inputs for this property
Check.Quick prop
let assertMaxOccurs str max input =
() // implementation to do
// Define a property that should be true for all inputs
let ``if arabic has 4 tens then roman has one XL otherwise none`` arabic =
let roman = arabicToRoman arabic
let has4Tens = (arabic % 100 / 10) = 4
if has4Tens then
assertMaxOccurs "XL" 1 roman
else
assertMaxOccurs "XL" 0 roman
// Explicitly enumerate all inputs...
[<Test>]
let ``For all valid inputs, check the XL substitution``() =
for i in [1..4000] do
``if arabic has 4 tens then roman has one XL otherwise none`` i
// ...Or again use FsCheck to generate inputs for you
do
let isInRange i = (i >= 1) && (i <= 4000)
let prop i = isInRange i ==> ``if arabic has 4 tens then roman has one XL otherwise none`` i
Check.Quick prop
// ======================================
// Complete property based testing using CustomAssert
// ======================================
module Test_Using_CustomAssert =
// the implementation to use is set later
let mutable arabicToRoman' : int -> string =
fun arabic -> failwith "Must be set for testing"
// ======================================================
// helper functions
// ======================================================
let inputRange() = [1..4000]
let assertTrue(b,msg) =
if not b then printfn "FAIL: %s" msg
let assertMaxRepetition str count (input:string) =
let find = String.replicate (count+1) str
let b = input.Contains find |> not
let msg = sprintf "assertMaxRepetition %s %i %s" str count input
assertTrue(b,msg)
let assertOccurs (str:string) count (input:string) =
// if it occurs once, then the string will be split into two, etc.
let b = input.Split( [| str |],System.StringSplitOptions.None).Length = count + 1
let msg = sprintf "assertOccurs %s %i %s" str count input
assertTrue(b,msg)
let replace (oldVal,newVal) (s:string) =
s.Replace(oldValue=oldVal,newValue=newVal)
let removeAbbreviations (s:string) =
s
|> replace("IV","IIII")
|> replace("IX","VIIII")
|> replace("XL","XXXX")
|> replace("XC","LXXXX")
|> replace("CD","CCCC")
|> replace("CM","DCCCC")
let assertSortedHighToLow (s:string) =
let unsorted =
s
|> removeAbbreviations
|> replace("I","1")
|> replace("V","2")
|> replace("X","3")
|> replace("L","4")
|> replace("C","5")
|> replace("D","6")
|> replace("M","7")
|> List.ofSeq
let sortedHighToLow = unsorted |> List.sort |> List.rev
//assert they are the same
let b = unsorted = sortedHighToLow
let msg = sprintf "assertSortedHighToLow %s" s
assertTrue(b,msg)
// ======================================================
// Properties
// ======================================================
let maxRepetitionProperty str count =
inputRange() |> List.iter (fun arabic ->
let roman = arabicToRoman' arabic
roman |> assertMaxRepetition str count
)
printfn "Completed maxRepetitionProperty %s %i" str count
let foursProperty place str =
inputRange() |> List.iter (fun arabic ->
let roman = arabicToRoman' arabic
let has4s = (arabic % (place*10) / place) = 4
if has4s then
assertOccurs str 1 roman
else
assertOccurs str 0 roman
)
printfn "Completed foursProperty %i %s" place str
let ninesProperty place str =
inputRange() |> List.iter (fun arabic ->
let roman = arabicToRoman' arabic
let has9s = (arabic % (place*10) / place) = 9
if has9s then
assertOccurs str 1 roman
else
assertOccurs str 0 roman
)
printfn "Completed ninesProperty %i %s" place str
let ``has max rep of three Is``() =
maxRepetitionProperty "I" 3
let ``has max rep of one V``() =
maxRepetitionProperty "V" 1
let ``has max rep of three Xs``() =
maxRepetitionProperty "X" 3
let ``has max rep of one L``() =
maxRepetitionProperty "L" 1
let ``has max rep of three Cs``() =
maxRepetitionProperty "C" 3
let ``has max rep of one D``() =
maxRepetitionProperty "D" 1
let ``has max rep of four Ms``() =
maxRepetitionProperty "M" 4
let ``if arabic has 4 ones then roman has one IV otherwise none``() =
foursProperty 1 "IV"
let ``if arabic has 9 ones then roman has one IX otherwise none``() =
ninesProperty 1 "IX"
let ``if arabic has 4 tens then roman has one XL otherwise none``() =
foursProperty 10 "XL"
let ``if arabic has 9 tens then roman has one XC otherwise none``() =
ninesProperty 10 "XC"
let ``if arabic has 4 hundreds then roman has one CD otherwise none``() =
foursProperty 100 "CD"
let ``if arabic has 9 hundreds then roman has one CM otherwise none``() =
ninesProperty 100 "CM"
let ``is sorted from M to I``() =
inputRange() |> List.iter (fun arabic ->
let roman = arabicToRoman' arabic
roman |> assertSortedHighToLow
)
printfn "Completed is sorted from M to I"
let checkAllProperties() =
``has max rep of three Is``()
``has max rep of one V``()
``has max rep of three Xs``()
``has max rep of one L``()
``has max rep of three Cs``()
``has max rep of one D``()
``has max rep of four Ms``()
``if arabic has 4 ones then roman has one IV otherwise none``()
``if arabic has 9 ones then roman has one IX otherwise none``()
``if arabic has 4 tens then roman has one XL otherwise none``()
``if arabic has 9 tens then roman has one XC otherwise none``()
``if arabic has 4 hundreds then roman has one CD otherwise none``()
``if arabic has 9 hundreds then roman has one CM otherwise none``()
``is sorted from M to I``()
// ======================================
// Complete property based testing using FsCheck
// ======================================
module Test_Using_FsCheck =
// the implementation to use is set later
let mutable arabicToRoman' : int -> string =
fun arabic -> failwith "Must be set for testing"
// ======================================================
// helper functions
// ======================================================
let inputRange = Gen.choose(1,4000) |> Arb.fromGen
let assertMaxRepetition str count (input:string) =
let find = String.replicate (count+1) str
input.Contains find |> not
let assertOccurs (str:string) count (input:string) =
// if it occurs once, then the string will be split into two, etc.
input.Split( [| str |],System.StringSplitOptions.None).Length = count + 1
let replace (oldVal,newVal) (s:string) =
s.Replace(oldValue=oldVal,newValue=newVal)
let removeAbbreviations (s:string) =
s
|> replace("IV","IIII")
|> replace("IX","VIIII")
|> replace("XL","XXXX")
|> replace("XC","LXXXX")
|> replace("CD","CCCC")
|> replace("CM","DCCCC")
let assertSortedHighToLow (s:string) =
let unsorted =
s
|> removeAbbreviations
|> replace("I","1")
|> replace("V","2")
|> replace("X","3")
|> replace("L","4")
|> replace("C","5")
|> replace("D","6")
|> replace("M","7")
|> List.ofSeq
let sortedHighToLow = unsorted |> List.sort |> List.rev
//assert they are the same
unsorted = sortedHighToLow
// ======================================================
// Properties
// ======================================================
type ImplementationProperties() =
static let maxRepetitionProperty str count =
Prop.forAll inputRange (fun arabic ->
let roman = arabicToRoman' arabic
roman |> assertMaxRepetition str count
)
static let foursProperty place str =
Prop.forAll inputRange (fun arabic ->
let roman = arabicToRoman' arabic
let has4s = (arabic % (place*10) / place) = 4
if has4s then
assertOccurs str 1 roman
else
assertOccurs str 0 roman
)
static let ninesProperty place str =
Prop.forAll inputRange (fun arabic ->
let roman = arabicToRoman' arabic
let has9s = (arabic % (place*10) / place) = 9
if has9s then
assertOccurs str 1 roman
else
assertOccurs str 0 roman
)
static member ``has max rep of three Is`` =
maxRepetitionProperty "I" 3
static member ``has max rep of one V`` =
maxRepetitionProperty "V" 1
static member ``has max rep of three Xs`` =
maxRepetitionProperty "X" 3
static member ``has max rep of one L`` =
maxRepetitionProperty "L" 1
static member ``has max rep of three Cs`` =
maxRepetitionProperty "C" 3
static member ``has max rep of one D`` =
maxRepetitionProperty "D" 1
static member ``has max rep of four Ms`` =
maxRepetitionProperty "M" 4
static member ``if arabic has 4 ones then roman has one IV otherwise none`` =
foursProperty 1 "IV"
static member ``if arabic has 9 ones then roman has one IX otherwise none`` =
ninesProperty 1 "IX"
static member ``if arabic has 4 tens then roman has one XL otherwise none`` =
foursProperty 10 "XL"
static member ``if arabic has 9 tens then roman has one XC otherwise none`` =
ninesProperty 10 "XC"
static member ``if arabic has 4 hundreds then roman has one CD otherwise none`` =
foursProperty 100 "CD"
static member ``if arabic has 9 hundreds then roman has one CM otherwise none`` =
ninesProperty 100 "CM"
static member ``is sorted from M to I`` =
Prop.forAll inputRange (fun arabic ->
let roman = arabicToRoman' arabic
roman |> assertSortedHighToLow
)
// ======================================
// Tally-based implementation -- version 1
// ======================================
module TallyImplementation_V1 =
let arabicToRoman arabic =
String.replicate arabic "I"
module TallyImplementation_V1_Demo =
open TallyImplementation_V1
let testArabicToRoman i =
printfn "%i ==> %s" i (arabicToRoman i)
testArabicToRoman 1 // "I"
testArabicToRoman 4 // "IIII"
testArabicToRoman 5 // "IIIII"
testArabicToRoman 6 // "IIIIII"
testArabicToRoman 10 // "IIIIIIIIII"
testArabicToRoman 12 // "IIIIIIIIIIII"
testArabicToRoman 16 // "IIIIIIIIIIIIIIII"
testArabicToRoman 40 // "IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII"
// ======================================
// Tally-based implementation -- version 2
// ======================================
module TallyImplementation_V2 =
let arabicToRoman arabic =
(String.replicate arabic "I")
.Replace("IIIII","V")
module TallyImplementation_V2_Demo =
open TallyImplementation_V2
let testArabicToRoman i =
printfn "%i ==> %s" i (arabicToRoman i)
testArabicToRoman 1 // "I"
testArabicToRoman 4 // "IIII"
testArabicToRoman 5 // "V"
testArabicToRoman 6 // "VI"
testArabicToRoman 10 // "VV"
testArabicToRoman 12 // "VVII"
testArabicToRoman 16 // "VVVI"
testArabicToRoman 40 // "VVVVVVVV"
// ======================================
// Tally-based implementation -- version 3
// ======================================
module TallyImplementation_V3 =
let arabicToRoman arabic =
(String.replicate arabic "I")
.Replace("IIIII","V")
.Replace("VV","X")
module TallyImplementation_V3_Demo =
open TallyImplementation_V3
let testArabicToRoman i =
printfn "%i ==> %s" i (arabicToRoman i)
testArabicToRoman 1 // "I"
testArabicToRoman 4 // "IIII"
testArabicToRoman 5 // "V"
testArabicToRoman 6 // "VI"
testArabicToRoman 10 // "X"
testArabicToRoman 12 // "XII"
testArabicToRoman 16 // "XVI"
testArabicToRoman 40 // "XXXX"
// ======================================
// Tally-based implementation -- V4
// ======================================
module TallyImplementation_V4 =
let arabicToRoman arabic =
(String.replicate arabic "I")
.Replace("IIIII","V")
.Replace("VV","X")
.Replace("XXXXX","L")
.Replace("LL","C")
.Replace("CCCCC","D")
.Replace("DD","M")
// optional substitutions
.Replace("IIII","IV")
.Replace("VIV","IX")
.Replace("XXXX","XL")
.Replace("LXL","XC")
.Replace("CCCC","CD")
.Replace("DCD","CM")
module TallyImplementation_V4_Demo =
open TallyImplementation_V4
let testArabicToRoman i =
printfn "%i ==> %s" i (arabicToRoman i)
testArabicToRoman 1 // "I"
testArabicToRoman 4 // "IV"
testArabicToRoman 5 // "V"
testArabicToRoman 6 // "VI"
testArabicToRoman 10 // "X"
testArabicToRoman 12 // "XII"
testArabicToRoman 16 // "XVI"
testArabicToRoman 40 // "XL"
testArabicToRoman 946 // "CMXLVI"
testArabicToRoman 3497 // "MMMCDXCVII"
// ======================================
// Tally-based implementation -- complete with property based tests
// ======================================
module TallyImplementation_Complete =
let arabicToRoman' arabic =
(String.replicate arabic "I")
.Replace("IIIII","V")
.Replace("VV","X")
.Replace("XXXXX","L")
.Replace("LL","C")
.Replace("CCCCC","D")
.Replace("DD","M")
// optional substitutions
.Replace("IIII","IV")
.Replace("VIV","IX")
.Replace("XXXX","XL")
.Replace("LXL","XC")
.Replace("CCCC","CD")
.Replace("DCD","CM")
let arabicToRoman arabic =
if arabic < 0 then None
else if arabic > 4000 then None
else if arabic = 0 then Some ""
else Some (arabicToRoman' arabic)
module TallyImplementation_Complete_Demo =
open TallyImplementation_Complete
let testArabicToRoman i =
printfn "%i ==> %A" i (arabicToRoman i)
testArabicToRoman -1 // None
testArabicToRoman 0 // Some ""
testArabicToRoman 1 // Some "I"
testArabicToRoman 4 // Some "IV"
testArabicToRoman 5 // Some "V"
testArabicToRoman 6 // Some "VI"
testArabicToRoman 10 // Some "X"
testArabicToRoman 12 // Some "XII"
testArabicToRoman 16 // Some "XVI"
testArabicToRoman 40 // Some "XL"
testArabicToRoman 946 // Some "CMXLVI"
testArabicToRoman 3497 // Some "MMMCDXCVII"
testArabicToRoman 10000 // None
module TallyImplementation_Complete_Test_Using_CustomAssert =
// set the implementation to use
Test_Using_CustomAssert.arabicToRoman' <- TallyImplementation_Complete.arabicToRoman'
// check all the properties
Test_Using_CustomAssert.checkAllProperties()
module TallyImplementation_Complete_Test_Using_FsCheck =
// set the implementation to use
Test_Using_FsCheck.arabicToRoman' <- TallyImplementation_Complete.arabicToRoman'
// check all the properties
Check.All<Test_Using_FsCheck.ImplementationProperties>({Config.Quick with MaxTest = 4000})
// ======================================
// place-based implementation without abbreviations
// ======================================
module PlaceBasedImplementation_WithoutAbbreviation =
let biQuinaryDigits place (unit,five) arabic =
let digit = arabic % (10*place) / place
match digit with
| 0 -> ""
| 1 -> unit
| 2 -> unit + unit
| 3 -> unit + unit + unit
| 4 -> unit + unit + unit + unit
| 5 -> five
| 6 -> five + unit
| 7 -> five + unit + unit
| 8 -> five + unit + unit + unit
| 9 -> five + unit + unit + unit + unit
| _ -> failwith "Expected 0-9 only"
let arabicToRoman' arabic =
let units = biQuinaryDigits 1 ("I","V") arabic
let tens = biQuinaryDigits 10 ("X","L") arabic
let hundreds = biQuinaryDigits 100 ("C","D") arabic
let thousands = biQuinaryDigits 1000 ("M","?") arabic
thousands + hundreds + tens + units
let arabicToRoman arabic =
if arabic < 0 then None
else if arabic > 4000 then None
else if arabic = 0 then Some ""
else Some (arabicToRoman' arabic)
module PlaceBasedImplementation_WithoutAbbreviation_Demo =
open PlaceBasedImplementation_WithoutAbbreviation
let testArabicToRoman i =
printfn "%i ==> %s" i (arabicToRoman' i)
testArabicToRoman 1 // "I"
testArabicToRoman 4 // "IIII"
testArabicToRoman 5 // "V"
testArabicToRoman 6 // "VI"
testArabicToRoman 10 // "X"
testArabicToRoman 12 // "XII"
testArabicToRoman 16 // "XVI"
testArabicToRoman 40 // "XXXX"
testArabicToRoman 946 // "DCCCCXXXXVI"
testArabicToRoman 3497 // "MMMCCCCLXXXXVII"
// ======================================
// place-based implementation
// ======================================
module PlaceBasedImplementation =
let biQuinaryDigits place (unit,five,ten) arabic =
let digit = arabic % (10*place) / place
match digit with
| 0 -> ""
| 1 -> unit
| 2 -> unit + unit
| 3 -> unit + unit + unit
| 4 -> unit + five // changed to be one less than five
| 5 -> five
| 6 -> five + unit
| 7 -> five + unit + unit
| 8 -> five + unit + unit + unit
| 9 -> unit + ten // changed to be one less than ten
| _ -> failwith "Expected 0-9 only"
let arabicToRoman' arabic =
let units = biQuinaryDigits 1 ("I","V","X") arabic
let tens = biQuinaryDigits 10 ("X","L","C") arabic
let hundreds = biQuinaryDigits 100 ("C","D","M") arabic
let thousands = biQuinaryDigits 1000 ("M","?","?") arabic
thousands + hundreds + tens + units
let arabicToRoman arabic =
if arabic < 0 then None
else if arabic > 4000 then None
else if arabic = 0 then Some ""
else Some (arabicToRoman' arabic)
module PlaceBasedImplementation_Demo =
open PlaceBasedImplementation
let testArabicToRoman i =
printfn "%i ==> %s" i (arabicToRoman' i)
testArabicToRoman 1 // "I"
testArabicToRoman 4 // "IV"
testArabicToRoman 5 // "V"
testArabicToRoman 6 // "VI"
testArabicToRoman 10 // "X"
testArabicToRoman 12 // "XII"
testArabicToRoman 16 // "XVI"
testArabicToRoman 40 // "XL"
testArabicToRoman 946 // "CMXLVI"
testArabicToRoman 3497 // "MMMCDXCVII"
testArabicToRoman 4000 // "M?"
module PlaceBasedImplementation_Test_Using_CustomAssert =
// set the implementation to use
Test_Using_CustomAssert.arabicToRoman' <- PlaceBasedImplementation.arabicToRoman'
// check all the properties
Test_Using_CustomAssert.checkAllProperties()
// Note that some of the tests fail when the input is 4000.
// This is because the output is "M?" rather than "MMMM"
module PlaceBasedImplementation_Test_Using_FsCheck =
// set the implementation to use
Test_Using_FsCheck.arabicToRoman' <- PlaceBasedImplementation.arabicToRoman'
// check all the properties
Check.All<Test_Using_FsCheck.ImplementationProperties>({Config.Quick with MaxTest = 4000})
// Note that some of the tests fail when the input is 4000.
// This is because the output is "M?" rather than "MMMM"
@badmotorfinger
Copy link

That's a lot of code for such a small application :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment