diff --git a/exercises/concept/family-recipes/.meta/Exemplar.fs b/exercises/concept/family-recipes/.meta/Exemplar.fs new file mode 100644 index 000000000..95e6d4a4a --- /dev/null +++ b/exercises/concept/family-recipes/.meta/Exemplar.fs @@ -0,0 +1,41 @@ +module FamilyRecipes + +type ValidationError = +| EmptyList +| InvalidIngredientQuantity +| MissingIngredientItem + +let parseInt (text: string) : Result = + let success, value = System.Int32.TryParse text + if success then Ok value else Error () + +let splitOnce (text: string) (separator: char) : (string * string) option = + match text.IndexOf(separator) with + | -1 -> None + | index -> Some (text.Substring(0, index), text.Substring(index + 1)) + +let validateIngredient (text: string): Result = + match splitOnce text ' ' with + | Some (quantity, item) -> + match parseInt quantity with + | Ok _ -> + if item.Length = 0 then + Error MissingIngredientItem + else + Ok () + | Error _ -> Error InvalidIngredientQuantity + | _ -> Error MissingIngredientItem + +let validate (input: string) : Result = + let nonblankLines = + input.Split('\n') + |> Array.filter (fun l -> l.Length > 0) + if nonblankLines.Length = 0 then Error EmptyList + else + let firstError = + nonblankLines + |> Array.map validateIngredient + |> Array.tryFind (fun r -> r.IsError) + match firstError with + | Some (Error error) -> Error error + | _ -> Ok input diff --git a/exercises/concept/family-recipes/FamilyRecipes.fs b/exercises/concept/family-recipes/FamilyRecipes.fs new file mode 100644 index 000000000..c09d7effe --- /dev/null +++ b/exercises/concept/family-recipes/FamilyRecipes.fs @@ -0,0 +1,13 @@ +module FamilyRecipes + +type ValidationError = +| EmptyList +| InvalidIngredientQuantity +| MissingIngredientItem + +let parseInt (text: string) : Result = + let success, value = System.Int32.TryParse text + if success then Ok value else Error () + +let validate input = + failwith "Please implement this function" diff --git a/exercises/concept/family-recipes/FamilyRecipes.fsproj b/exercises/concept/family-recipes/FamilyRecipes.fsproj new file mode 100644 index 000000000..2da4fee5b --- /dev/null +++ b/exercises/concept/family-recipes/FamilyRecipes.fsproj @@ -0,0 +1,21 @@ + + + + net9.0 + + false + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/exercises/concept/family-recipes/FamilyRecipesTests.fs b/exercises/concept/family-recipes/FamilyRecipesTests.fs new file mode 100644 index 000000000..0054a1452 --- /dev/null +++ b/exercises/concept/family-recipes/FamilyRecipesTests.fs @@ -0,0 +1,46 @@ +module Tests + +open FsUnit.Xunit +open Xunit + +open FamilyRecipes + +[] +let ``Error on blank list`` () = + let expected: Result = Error EmptyList + validate "" |> should equal expected + +[] +let ``Error on blank line`` () = + let expected: Result = Error EmptyList + validate "\n" |> should equal expected + +[] +let ``Error on non-numeric ingredient quantity`` () = + let expected: Result = Error InvalidIngredientQuantity + validate "foo bar" |> should equal expected + +[] +let ``Error on missing ingredient item`` () = + let expected: Result = Error MissingIngredientItem + validate "24 " |> should equal expected + +[] +let ``Minimal valid list`` () = + let input = """1 cup rice""" + let expected: Result = Ok input + validate input |> should equal expected + +[] +let ``Valid list with multiple ingredients`` () = + let input = """4 ounces of oatmeal +12 ounces of water +1 tablespoon of honey""" + let expected: Result = Ok input + validate input |> should equal expected + +[] +let ``Blank lines within valid list are OK`` () = + let input = "1 foo\n2 bar" + let expected: Result = Ok input + validate input |> should equal expected