diff --git a/config.json b/config.json index 33fdabf905801..a13a004e8dda9 100644 --- a/config.json +++ b/config.json @@ -113,6 +113,17 @@ "strings" ] }, + { + "slug": "binary-search", + "uuid": "325385bb-2a70-4c7a-8b4e-e6297fc69872", + "core": false, + "unlocked_by": "pangram", + "difficulty": 3, + "topics": [ + "arrays", + "searching" + ] + }, { "slug": "rotational-cipher", "uuid": "79ea05f1-3f27-4b92-bc47-0cebcd8d5a6f", diff --git a/exercises/binary-search/.meta/description.md b/exercises/binary-search/.meta/description.md new file mode 100644 index 0000000000000..ac9fc45ef3cd2 --- /dev/null +++ b/exercises/binary-search/.meta/description.md @@ -0,0 +1,37 @@ +Implement a binary search algorithm. + +Searching a sorted collection is a common task. A dictionary is a sorted +list of word definitions. Given a word, one can find its definition. A +telephone book is a sorted list of people's names, addresses, and +telephone numbers. Knowing someone's name allows one to quickly find +their telephone number and address. + +If the list to be searched contains more than a few items (a dozen, say) +a binary search will require far fewer comparisons than a linear search, +but it imposes the requirement that the list be sorted. + +In computer science, a binary search or half-interval search algorithm +finds the position of a specified input value (the search "key") within +an array sorted by key value. + +In each step, the algorithm compares the search key value with the key +value of the middle element of the array. + +If the keys match, then a matching element has been found and the range of indices that equal the search key value are returned. + +Otherwise, if the search key is less than the middle element's key, then +the algorithm repeats its action on the sub-array to the left of the +middle element or, if the search key is greater, on the sub-array to the +right. + +If the remaining array to be searched is empty, then the key cannot be +found in the array and a special "not found" indication is returned. Search methods in Julia typically return an empty range located at the insertion point in this case. + +A binary search halves the number of items to check with each iteration, +so locating an item (or determining its absence) takes logarithmic time. +A binary search is a dichotomic divide and conquer search algorithm. + +**For simplification, you can assume that all elements of the list to be searched are unique.** Feel free to implement a solution that works on lists with non-unique elements as a bonus task. + +## Bonus task +Implement keyword arguments `by`, `lt` and `rev` so that `by` specifies a transformation applied to all elements of the list, `lt` specifies a comparison and `rev` specifies if the list is ordered in reverse. diff --git a/exercises/binary-search/.meta/metadata.yml b/exercises/binary-search/.meta/metadata.yml new file mode 100644 index 0000000000000..9d9bc55518e1e --- /dev/null +++ b/exercises/binary-search/.meta/metadata.yml @@ -0,0 +1,4 @@ +--- +blurb: "Implement a binary search algorithm." +source: "Wikipedia [http://en.wikipedia.org/wiki/Binary_search_algorithm](http://en.wikipedia.org/wiki/Binary_search_algorithm)\n\nSome phrases above and the bonus tasks are taken from the [Julia base documentation (MIT license)](https://docs.julialang.org/en/v1/base/sort/#Base.Sort.searchsorted) of `searchsorted`." +source_url: "" diff --git a/exercises/binary-search/README.md b/exercises/binary-search/README.md new file mode 100644 index 0000000000000..e98ec567a1899 --- /dev/null +++ b/exercises/binary-search/README.md @@ -0,0 +1,52 @@ +# Binary Search + +Implement a binary search algorithm. + +Searching a sorted collection is a common task. A dictionary is a sorted +list of word definitions. Given a word, one can find its definition. A +telephone book is a sorted list of people's names, addresses, and +telephone numbers. Knowing someone's name allows one to quickly find +their telephone number and address. + +If the list to be searched contains more than a few items (a dozen, say) +a binary search will require far fewer comparisons than a linear search, +but it imposes the requirement that the list be sorted. + +In computer science, a binary search or half-interval search algorithm +finds the position of a specified input value (the search "key") within +an array sorted by key value. + +In each step, the algorithm compares the search key value with the key +value of the middle element of the array. + +If the keys match, then a matching element has been found and the range of indices that equal the search key value are returned. + +Otherwise, if the search key is less than the middle element's key, then +the algorithm repeats its action on the sub-array to the left of the +middle element or, if the search key is greater, on the sub-array to the +right. + +If the remaining array to be searched is empty, then the key cannot be +found in the array and a special "not found" indication is returned. Search methods in Julia typically return an empty range located at the insertion point in this case. + +A binary search halves the number of items to check with each iteration, +so locating an item (or determining its absence) takes logarithmic time. +A binary search is a dichotomic divide and conquer search algorithm. + +**For simplification, you can assume that all elements of the list to be searched are unique.** Feel free to implement a solution that works on lists with non-unique elements as a bonus task. + +## Bonus task +Implement keyword arguments `by`, `lt` and `rev` so that `by` specifies a transformation applied to all elements of the list, `lt` specifies a comparison and `rev` specifies if the list is ordered in reverse. + +## Source + +Wikipedia [http://en.wikipedia.org/wiki/Binary_search_algorithm](http://en.wikipedia.org/wiki/Binary_search_algorithm) + +Some phrases above and the bonus tasks are taken from the [Julia base documentation (MIT license)](https://docs.julialang.org/en/v1/base/sort/#Base.Sort.searchsorted) of `searchsorted`. + + +## Version compatibility +This exercise has been tested on Julia versions >=1.0. + +## Submitting Incomplete Solutions +It's possible to submit an incomplete solution so you can see how others have completed the exercise. diff --git a/exercises/binary-search/binary-search.ipynb b/exercises/binary-search/binary-search.ipynb new file mode 100644 index 0000000000000..3a877e1b3a86b --- /dev/null +++ b/exercises/binary-search/binary-search.ipynb @@ -0,0 +1,173 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Binary Search\n", + "\n", + "Implement a binary search algorithm.\n", + "\n", + "Searching a sorted collection is a common task. A dictionary is a sorted\n", + "list of word definitions. Given a word, one can find its definition. A\n", + "telephone book is a sorted list of people's names, addresses, and\n", + "telephone numbers. Knowing someone's name allows one to quickly find\n", + "their telephone number and address.\n", + "\n", + "If the list to be searched contains more than a few items (a dozen, say)\n", + "a binary search will require far fewer comparisons than a linear search,\n", + "but it imposes the requirement that the list be sorted.\n", + "\n", + "In computer science, a binary search or half-interval search algorithm\n", + "finds the position of a specified input value (the search \"key\") within\n", + "an array sorted by key value.\n", + "\n", + "In each step, the algorithm compares the search key value with the key\n", + "value of the middle element of the array.\n", + "\n", + "If the keys match, then a matching element has been found and the range of indices that equal the search key value are returned.\n", + "\n", + "Otherwise, if the search key is less than the middle element's key, then\n", + "the algorithm repeats its action on the sub-array to the left of the\n", + "middle element or, if the search key is greater, on the sub-array to the\n", + "right.\n", + "\n", + "If the remaining array to be searched is empty, then the key cannot be\n", + "found in the array and a special \"not found\" indication is returned. Search methods in Julia typically return an empty range located at the insertion point in this case.\n", + "\n", + "A binary search halves the number of items to check with each iteration,\n", + "so locating an item (or determining its absence) takes logarithmic time.\n", + "A binary search is a dichotomic divide and conquer search algorithm.\n", + "\n", + "**For simplification, you can assume that all elements of the list to be searched are unique.** Feel free to implement a solution that works on lists with non-unique elements as a bonus task.\n", + "\n", + "## Bonus task\n", + "Implement keyword arguments `by`, `lt` and `rev` so that `by` specifies a transformation applied to all elements of the list, `lt` specifies a comparison and `rev` specifies if the list is ordered in reverse.\n", + "\n", + "## Source\n", + "\n", + "Wikipedia [http://en.wikipedia.org/wiki/Binary_search_algorithm](http://en.wikipedia.org/wiki/Binary_search_algorithm)\n", + "\n", + "Some phrases above and the bonus tasks are taken from the [Julia base documentation (MIT license)](https://docs.julialang.org/en/v1/base/sort/#Base.Sort.searchsorted) of `searchsorted`.\n", + "\n", + "\n", + "## Version compatibility\n", + "This exercise has been tested on Julia versions >=1.0.\n", + "\n", + "## Submitting Incomplete Solutions\n", + "It's possible to submit an incomplete solution so you can see how others have completed the exercise.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# submit\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# canonical data version: 1.2.0\n", + "\n", + "using Test\n", + "\n", + "# include(\"binary-search.jl\")\n", + "\n", + "@testset \"default binary search\" begin\n", + " @testset \"value in array\" begin\n", + " @test binarysearch([6], 6) == 1:1\n", + " @test binarysearch([1, 3, 4, 6, 8, 9, 11], 6) == 4:4\n", + " @test binarysearch([1, 3, 4, 6, 8, 9, 11], 1) == 1:1\n", + " @test binarysearch([1, 3, 4, 6, 8, 9, 11], 11) == 7:7\n", + " @test binarysearch([1, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 634], 144) == 10:10\n", + " @test binarysearch([1, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377], 21) == 6:6\n", + " end\n", + " @testset \"value not in array\" begin\n", + " @test binarysearch([1, 3, 4, 6, 8, 9, 11], 7) == 5:4\n", + " @test binarysearch([1, 3, 4, 6, 8, 9, 11], 0) == 1:0\n", + " @test binarysearch([1, 3, 4, 6, 8, 9, 11], 13) == 8:7\n", + " @test binarysearch([], 1) == 1:0\n", + " end\n", + "end\n", + "\n", + "@testset \"bonus tasks\" begin\n", + " @testset \"reverse search\" begin\n", + " @testset \"value in array\" begin\n", + " @test_skip binarysearch([6], 6, rev = true) == 1:1\n", + " @test_skip binarysearch([11, 9, 8, 6, 4, 3, 1], 6, rev = true) == 4:4\n", + " @test_skip binarysearch([11, 9, 8, 6, 4, 3, 1], 1, rev = true) == 7:7\n", + " @test_skip binarysearch([11, 9, 8, 6, 4, 3, 1], 11, rev = true) == 1:1\n", + " @test_skip binarysearch([634, 377, 233, 144, 89, 55, 34, 21, 13, 8, 5, 3, 1], 144, rev = true) == 4:4\n", + " @test_skip binarysearch([377, 233, 144, 89, 55, 34, 21, 13, 8, 5, 3, 1], 21, rev = true) == 7:7\n", + " end\n", + " @testset \"value not in array\" begin\n", + " @test_skip binarysearch([11, 9, 8, 6, 4, 3, 1], 7, rev = true) == 4:3\n", + " @test_skip binarysearch([11, 9, 8, 6, 4, 3, 1], 0, rev = true) == 8:7\n", + " @test_skip binarysearch([11, 9, 8, 6, 4, 3, 1], 13, rev = true) == 1:0\n", + " @test_skip binarysearch([], 1, rev = true) == 1:0\n", + " end\n", + " end\n", + "\n", + " @testset \"apply transformation\" begin\n", + " @testset \"value in array\" begin\n", + " @test_skip binarysearch([5.5], 6, by = round) == 1:1\n", + " @test_skip binarysearch([1.1, 2.9, 4.4, 5.5, 8.1, 9.0, 10.8], 6, by = round) == 4:4\n", + " @test_skip binarysearch([1.1, 2.9, 4.4, 5.5, 8.1, 9.0, 10.8], 1, by = round) == 1:1\n", + " @test_skip binarysearch([1.1, 2.9, 4.4, 5.5, 8.1, 9.0, 10.8], 11, by = round) == 7:7\n", + " @test_skip binarysearch([1, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 634], 144.4, by = round) == 10:10\n", + " @test_skip binarysearch([1, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377], 20.6, by = round) == 6:6\n", + " end\n", + " @testset \"value not in array\" begin\n", + " @test_skip binarysearch([1.1, 2.9, 4.4, 5.5, 8.1, 9.0, 10.8], 7, by = round) == 5:4\n", + " @test_skip binarysearch([1.1, 2.9, 4.4, 5.5, 8.1, 9.0, 10.8], 0, by = round) == 1:0\n", + " @test_skip binarysearch([1.1, 2.9, 4.4, 5.5, 8.1, 9.0, 10.8], 13, by = round) == 8:7\n", + " @test_skip binarysearch([], 1, by = round) == 1:0\n", + " end\n", + " end\n", + "\n", + " @testset \"compare with > instead of <\" begin\n", + " # this is equivalent to searching in reverse order\n", + " @testset \"value in array\" begin\n", + " @test_skip binarysearch([6], 6, lt = >) == 1:1\n", + " @test_skip binarysearch([11, 9, 8, 6, 4, 3, 1], 6, lt = >) == 4:4\n", + " @test_skip binarysearch([11, 9, 8, 6, 4, 3, 1], 1, lt = >) == 7:7\n", + " @test_skip binarysearch([11, 9, 8, 6, 4, 3, 1], 11, lt = >) == 1:1\n", + " @test_skip binarysearch([634, 377, 233, 144, 89, 55, 34, 21, 13, 8, 5, 3, 1], 144, lt = >) == 4:4\n", + " @test_skip binarysearch([377, 233, 144, 89, 55, 34, 21, 13, 8, 5, 3, 1], 21, lt = >) == 7:7\n", + " end\n", + " @testset \"value not in array\" begin\n", + " @test_skip binarysearch([11, 9, 8, 6, 4, 3, 1], 7, lt = >) == 4:3\n", + " @test_skip binarysearch([11, 9, 8, 6, 4, 3, 1], 0, lt = >) == 8:7\n", + " @test_skip binarysearch([11, 9, 8, 6, 4, 3, 1], 13, lt = >) == 1:0\n", + " @test_skip binarysearch([], 1, lt = >) == 1:0\n", + " end\n", + " end\n", + "end\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# To submit your exercise, you need to save your solution in a file called binary-search.jl before using the CLI.\n", + "# You can either create it manually or use the following functions, which will automatically\n", + "# save every notebook cell starting with `# submit` in that file.\n", + "\n", + "# Pkg.add(\"Exercism\")\n", + "# using Exercism\n", + "# Exercism.create_submission(\"binary-search\")\n" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/exercises/binary-search/binary-search.jl b/exercises/binary-search/binary-search.jl new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/exercises/binary-search/example.jl b/exercises/binary-search/example.jl new file mode 100644 index 0000000000000..3678ae77e1f11 --- /dev/null +++ b/exercises/binary-search/example.jl @@ -0,0 +1,2 @@ +# the base function searchsorted does exactly what the exercise asks for +const binarysearch = searchsorted diff --git a/exercises/binary-search/runtests.jl b/exercises/binary-search/runtests.jl new file mode 100644 index 0000000000000..54f7a268dac2a --- /dev/null +++ b/exercises/binary-search/runtests.jl @@ -0,0 +1,76 @@ +# canonical data version: 1.2.0 + +using Test + +include("binary-search.jl") + +@testset "default binary search" begin + @testset "value in array" begin + @test binarysearch([6], 6) == 1:1 + @test binarysearch([1, 3, 4, 6, 8, 9, 11], 6) == 4:4 + @test binarysearch([1, 3, 4, 6, 8, 9, 11], 1) == 1:1 + @test binarysearch([1, 3, 4, 6, 8, 9, 11], 11) == 7:7 + @test binarysearch([1, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 634], 144) == 10:10 + @test binarysearch([1, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377], 21) == 6:6 + end + @testset "value not in array" begin + @test binarysearch([1, 3, 4, 6, 8, 9, 11], 7) == 5:4 + @test binarysearch([1, 3, 4, 6, 8, 9, 11], 0) == 1:0 + @test binarysearch([1, 3, 4, 6, 8, 9, 11], 13) == 8:7 + @test binarysearch([], 1) == 1:0 + end +end + +@testset "bonus tasks" begin + @testset "reverse search" begin + @testset "value in array" begin + @test_skip binarysearch([6], 6, rev = true) == 1:1 + @test_skip binarysearch([11, 9, 8, 6, 4, 3, 1], 6, rev = true) == 4:4 + @test_skip binarysearch([11, 9, 8, 6, 4, 3, 1], 1, rev = true) == 7:7 + @test_skip binarysearch([11, 9, 8, 6, 4, 3, 1], 11, rev = true) == 1:1 + @test_skip binarysearch([634, 377, 233, 144, 89, 55, 34, 21, 13, 8, 5, 3, 1], 144, rev = true) == 4:4 + @test_skip binarysearch([377, 233, 144, 89, 55, 34, 21, 13, 8, 5, 3, 1], 21, rev = true) == 7:7 + end + @testset "value not in array" begin + @test_skip binarysearch([11, 9, 8, 6, 4, 3, 1], 7, rev = true) == 4:3 + @test_skip binarysearch([11, 9, 8, 6, 4, 3, 1], 0, rev = true) == 8:7 + @test_skip binarysearch([11, 9, 8, 6, 4, 3, 1], 13, rev = true) == 1:0 + @test_skip binarysearch([], 1, rev = true) == 1:0 + end + end + + @testset "apply transformation" begin + @testset "value in array" begin + @test_skip binarysearch([5.5], 6, by = round) == 1:1 + @test_skip binarysearch([1.1, 2.9, 4.4, 5.5, 8.1, 9.0, 10.8], 6, by = round) == 4:4 + @test_skip binarysearch([1.1, 2.9, 4.4, 5.5, 8.1, 9.0, 10.8], 1, by = round) == 1:1 + @test_skip binarysearch([1.1, 2.9, 4.4, 5.5, 8.1, 9.0, 10.8], 11, by = round) == 7:7 + @test_skip binarysearch([1, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 634], 144.4, by = round) == 10:10 + @test_skip binarysearch([1, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377], 20.6, by = round) == 6:6 + end + @testset "value not in array" begin + @test_skip binarysearch([1.1, 2.9, 4.4, 5.5, 8.1, 9.0, 10.8], 7, by = round) == 5:4 + @test_skip binarysearch([1.1, 2.9, 4.4, 5.5, 8.1, 9.0, 10.8], 0, by = round) == 1:0 + @test_skip binarysearch([1.1, 2.9, 4.4, 5.5, 8.1, 9.0, 10.8], 13, by = round) == 8:7 + @test_skip binarysearch([], 1, by = round) == 1:0 + end + end + + @testset "compare with > instead of <" begin + # this is equivalent to searching in reverse order + @testset "value in array" begin + @test_skip binarysearch([6], 6, lt = >) == 1:1 + @test_skip binarysearch([11, 9, 8, 6, 4, 3, 1], 6, lt = >) == 4:4 + @test_skip binarysearch([11, 9, 8, 6, 4, 3, 1], 1, lt = >) == 7:7 + @test_skip binarysearch([11, 9, 8, 6, 4, 3, 1], 11, lt = >) == 1:1 + @test_skip binarysearch([634, 377, 233, 144, 89, 55, 34, 21, 13, 8, 5, 3, 1], 144, lt = >) == 4:4 + @test_skip binarysearch([377, 233, 144, 89, 55, 34, 21, 13, 8, 5, 3, 1], 21, lt = >) == 7:7 + end + @testset "value not in array" begin + @test_skip binarysearch([11, 9, 8, 6, 4, 3, 1], 7, lt = >) == 4:3 + @test_skip binarysearch([11, 9, 8, 6, 4, 3, 1], 0, lt = >) == 8:7 + @test_skip binarysearch([11, 9, 8, 6, 4, 3, 1], 13, lt = >) == 1:0 + @test_skip binarysearch([], 1, lt = >) == 1:0 + end + end +end