diff --git a/config.json b/config.json index b62d984d..57b1d955 100644 --- a/config.json +++ b/config.json @@ -979,9 +979,9 @@ "difficulty": 3 }, { - "slug": "binary-search", + "slug": "binary-chop", "name": "Binary Search", - "uuid": "ea3a619c-0d03-4688-821a-22379470d7e1", + "uuid": "49cc7b5c-d2fc-4332-89a5-9890c340f25f", "practices": [ "recursion" ], @@ -993,6 +993,15 @@ ], "difficulty": 3 }, + { + "slug": "binary-search", + "name": "Binary Search (deprecated)", + "uuid": "ea3a619c-0d03-4688-821a-22379470d7e1", + "practices": [], + "prerequisites": [], + "difficulty": 3, + "status": "deprecated" + }, { "slug": "bob", "name": "Bob", diff --git a/exercises/practice/binary-chop/.docs/instructions.md b/exercises/practice/binary-chop/.docs/instructions.md new file mode 100644 index 00000000..12f4358e --- /dev/null +++ b/exercises/practice/binary-chop/.docs/instructions.md @@ -0,0 +1,29 @@ +# Instructions + +Your task is to implement a binary search algorithm. + +A binary search algorithm finds an item in a list by repeatedly splitting it in half, only keeping the half which contains the item we're looking for. +It allows us to quickly narrow down the possible locations of our item until we find it, or until we've eliminated all possible locations. + +~~~~exercism/caution +Binary search only works when a list has been sorted. +~~~~ + +The algorithm looks like this: + +- Find the middle element of a _sorted_ list and compare it with the item we're looking for. +- If the middle element is our item, then we're done! +- If the middle element is greater than our item, we can eliminate that element and all the elements **after** it. +- If the middle element is less than our item, we can eliminate that element and all the elements **before** it. +- If every element of the list has been eliminated then the item is not in the list. +- Otherwise, repeat the process on the part of the list that has not been eliminated. + +Here's an example: + +Let's say we're looking for the number 23 in the following sorted list: `[4, 8, 12, 16, 23, 28, 32]`. + +- We start by comparing 23 with the middle element, 16. +- Since 23 is greater than 16, we can eliminate the left half of the list, leaving us with `[23, 28, 32]`. +- We then compare 23 with the new middle element, 28. +- Since 23 is less than 28, we can eliminate the right half of the list: `[23]`. +- We've found our item. diff --git a/exercises/practice/binary-chop/.docs/introduction.md b/exercises/practice/binary-chop/.docs/introduction.md new file mode 100644 index 00000000..03496599 --- /dev/null +++ b/exercises/practice/binary-chop/.docs/introduction.md @@ -0,0 +1,13 @@ +# Introduction + +You have stumbled upon a group of mathematicians who are also singer-songwriters. +They have written a song for each of their favorite numbers, and, as you can imagine, they have a lot of favorite numbers (like [0][zero] or [73][seventy-three] or [6174][kaprekars-constant]). + +You are curious to hear the song for your favorite number, but with so many songs to wade through, finding the right song could take a while. +Fortunately, they have organized their songs in a playlist sorted by the title — which is simply the number that the song is about. + +You realize that you can use a binary search algorithm to quickly find a song given the title. + +[zero]: https://en.wikipedia.org/wiki/0 +[seventy-three]: https://en.wikipedia.org/wiki/73_(number) +[kaprekars-constant]: https://en.wikipedia.org/wiki/6174_(number) diff --git a/exercises/practice/binary-chop/.meta/config.json b/exercises/practice/binary-chop/.meta/config.json new file mode 100644 index 00000000..df36c870 --- /dev/null +++ b/exercises/practice/binary-chop/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "keiravillekode" + ], + "files": { + "solution": [ + "binary-chop/binary-chop.factor" + ], + "test": [ + "binary-chop/binary-chop-tests.factor" + ], + "example": [ + ".meta/example.factor" + ] + }, + "blurb": "Implement a binary search algorithm.", + "source": "Wikipedia", + "source_url": "https://en.wikipedia.org/wiki/Binary_search_algorithm" +} diff --git a/exercises/practice/binary-chop/.meta/example.factor b/exercises/practice/binary-chop/.meta/example.factor new file mode 100644 index 00000000..c6c21334 --- /dev/null +++ b/exercises/practice/binary-chop/.meta/example.factor @@ -0,0 +1,18 @@ +USING: combinators kernel locals math sequences ; +IN: binary-chop + +ERROR: value-not-in-array ; + +:: search-range ( array value low high -- index ) + low high > [ value-not-in-array ] [ + low high + 2 /i :> mid + mid array nth :> probe + { + { [ probe value = ] [ mid ] } + { [ probe value < ] [ array value mid 1 + high search-range ] } + [ array value low mid 1 - search-range ] + } cond + ] if ; + +: find ( array value -- index ) + over length 1 - [ 0 ] dip search-range ; diff --git a/exercises/practice/binary-chop/.meta/generator.jl b/exercises/practice/binary-chop/.meta/generator.jl new file mode 100644 index 00000000..9d30f269 --- /dev/null +++ b/exercises/practice/binary-chop/.meta/generator.jl @@ -0,0 +1,13 @@ +module BinarySearch + +function gen_test_case(case) + array = format_int_array(case["input"]["array"]) + value = to_int_str(case["input"]["value"]) + expected = case["expected"] + if expected isa AbstractDict && haskey(expected, "error") + return """[ $(array) $(value) find ] [ value-not-in-array? ] must-fail-with""" + end + return "{ $(to_int_str(expected)) }\n[ $(array) $(value) find ] unit-test" +end + +end diff --git a/exercises/practice/binary-chop/.meta/tests.toml b/exercises/practice/binary-chop/.meta/tests.toml new file mode 100644 index 00000000..61e2b068 --- /dev/null +++ b/exercises/practice/binary-chop/.meta/tests.toml @@ -0,0 +1,43 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[b55c24a9-a98d-4379-a08c-2adcf8ebeee8] +description = "finds a value in an array with one element" + +[73469346-b0a0-4011-89bf-989e443d503d] +description = "finds a value in the middle of an array" + +[327bc482-ab85-424e-a724-fb4658e66ddb] +description = "finds a value at the beginning of an array" + +[f9f94b16-fe5e-472c-85ea-c513804c7d59] +description = "finds a value at the end of an array" + +[f0068905-26e3-4342-856d-ad153cadb338] +description = "finds a value in an array of odd length" + +[fc316b12-c8b3-4f5e-9e89-532b3389de8c] +description = "finds a value in an array of even length" + +[da7db20a-354f-49f7-a6a1-650a54998aa6] +description = "identifies that a value is not included in the array" + +[95d869ff-3daf-4c79-b622-6e805c675f97] +description = "a value smaller than the array's smallest value is not found" + +[8b24ef45-6e51-4a94-9eac-c2bf38fdb0ba] +description = "a value larger than the array's largest value is not found" + +[f439a0fa-cf42-4262-8ad1-64bf41ce566a] +description = "nothing is found in an empty array" + +[2c353967-b56d-40b8-acff-ce43115eed64] +description = "nothing is found when the left and right bounds cross" diff --git a/exercises/practice/binary-chop/binary-chop/binary-chop-tests.factor b/exercises/practice/binary-chop/binary-chop/binary-chop-tests.factor new file mode 100644 index 00000000..4b4a5e96 --- /dev/null +++ b/exercises/practice/binary-chop/binary-chop/binary-chop-tests.factor @@ -0,0 +1,43 @@ +USING: binary-chop exercism-tools io kernel tools.test unicode ; +IN: binary-chop.tests + +"finds a value in an array with one element" description +{ 0 } +[ { 6 } 6 find ] unit-test + +STOP-HERE + +"finds a value in the middle of an array" description +{ 3 } +[ { 1 3 4 6 8 9 11 } 6 find ] unit-test + +"finds a value at the beginning of an array" description +{ 0 } +[ { 1 3 4 6 8 9 11 } 1 find ] unit-test + +"finds a value at the end of an array" description +{ 6 } +[ { 1 3 4 6 8 9 11 } 11 find ] unit-test + +"finds a value in an array of odd length" description +{ 9 } +[ { 1 3 5 8 13 21 34 55 89 144 233 377 634 } 144 find ] unit-test + +"finds a value in an array of even length" description +{ 5 } +[ { 1 3 5 8 13 21 34 55 89 144 233 377 } 21 find ] unit-test + +"identifies that a value is not included in the array" description +[ { 1 3 4 6 8 9 11 } 7 find ] [ value-not-in-array? ] must-fail-with + +"a value smaller than the array's smallest value is not found" description +[ { 1 3 4 6 8 9 11 } 0 find ] [ value-not-in-array? ] must-fail-with + +"a value larger than the array's largest value is not found" description +[ { 1 3 4 6 8 9 11 } 13 find ] [ value-not-in-array? ] must-fail-with + +"nothing is found in an empty array" description +[ { } 1 find ] [ value-not-in-array? ] must-fail-with + +"nothing is found when the left and right bounds cross" description +[ { 1 2 } 0 find ] [ value-not-in-array? ] must-fail-with diff --git a/exercises/practice/binary-chop/binary-chop/binary-chop.factor b/exercises/practice/binary-chop/binary-chop/binary-chop.factor new file mode 100644 index 00000000..cf4cc72b --- /dev/null +++ b/exercises/practice/binary-chop/binary-chop/binary-chop.factor @@ -0,0 +1,7 @@ +USING: kernel ; +IN: binary-chop + +ERROR: value-not-in-array ; + +: find ( array value -- index ) + "unimplemented" throw ; diff --git a/exercises/practice/binary-chop/exercism-tools/exercism-tools.factor b/exercises/practice/binary-chop/exercism-tools/exercism-tools.factor new file mode 100644 index 00000000..df5f6920 --- /dev/null +++ b/exercises/practice/binary-chop/exercism-tools/exercism-tools.factor @@ -0,0 +1,35 @@ +USING: accessors command-line continuations debugger io kernel + lexer namespaces prettyprint.config sequences + source-files.errors.debugger system tools.test vocabs + vocabs.loader ; +IN: exercism-tools + +SYNTAX: STOP-HERE + lexer get [ text>> length ] keep line<< ; + +SYNTAX: TASK: + lexer get next-line ; + +! Label the test that follows with its description. +: description ( str -- ) + "###DESC### " write print ; + +! Print one failure block in a stable, parser-friendly form. +:: print-failure ( failure -- ) + "###FAIL_BEGIN###" print + failure error-location print + [ failure error>> [ error. ] [ 2drop ] recover ] without-limits + "###FAIL_END###" print + flush ; + +: print-failures ( -- ) + test-failures get [ print-failure ] each ; + +: run-exercism-tests ( -- ) + vocab-roots [ "." prefix ] change-global + command-line get first + [ require ] [ test ] bi + test-failures get empty? + [ 0 exit ] [ print-failures 1 exit ] if ; + +MAIN: run-exercism-tests diff --git a/generator/generate.jl b/generator/generate.jl index a5a24805..bb2e3702 100644 --- a/generator/generate.jl +++ b/generator/generate.jl @@ -4,14 +4,28 @@ using JSON3, TOML const GENERATOR_DIR = @__DIR__ const TRACK_DIR = dirname(GENERATOR_DIR) -function read_canonical_data(exercise) +function specification_for(slug) + # A Practice Exercise may set an optional `specification` key in the track + # `config.json` to sync from a differently-named `problem-specifications` + # exercise (e.g. slug `binary-chop` <- specification `binary-search`). + # When the key is absent, the slug doubles as the specification name. + config = JSON3.read(read(joinpath(TRACK_DIR, "config.json"), String)) + for ex in config.exercises.practice + if String(ex.slug) == slug + return haskey(ex, :specification) ? String(ex.specification) : slug + end + end + return slug +end + +function read_canonical_data(specification) prefix = "Using cached 'problem-specifications' dir: " info = readchomp(Cmd(`$(joinpath(TRACK_DIR, "bin", "configlet")) info -o -v d`)) lines = split(info, '\n') cache_lines = filter(l -> startswith(l, prefix), lines) length(cache_lines) == 1 || error("Could not determine 'problem-specifications' dir") cache_dir = cache_lines[1][length(prefix)+1:end] - path = joinpath(cache_dir, "exercises", exercise, "canonical-data.json") + path = joinpath(cache_dir, "exercises", specification, "canonical-data.json") return JSON3.read(read(path, String)) end @@ -123,7 +137,9 @@ function render_test_file(mod, cases, slug) end function generate(exercise) - data = read_canonical_data(exercise) + # The canonical data is read using the `specification` (which may differ + # from the slug); everything else stays keyed on the track `exercise` slug. + data = read_canonical_data(specification_for(exercise)) cases = flatten_cases(data) cases = filter_by_toml(cases, exercise) extra = load_extra_cases(exercise)