I've just completed this binary search kata - which has some weird requirements - and I spent a little while "cleaning" up my code and making it more compact. I would appreciate some feedback on the readability of the BinarySearch class. I've packed a lot of logic into some of the lines and I'd like to know if that makes it more or less readable. Thanks! A Ruby Kata for Binary Search of Arrays: Binary Search Kata Create a binary search class that is initialized with an array of integers - detail: Do not use built in array functions - detail: The initial array is [1,3] - detail: return the index of the search value if found - example: searching for 3 returns 1 completed (Y|n): Successfully deal with values that are not in the data set - example: searching for 5 indicates to the caller the value is not found completed (Y|n): Binary Search handles an odd number of elements in the data set - detail: Allows the data set to be redefined with [1,3,5,7,9] - example: Searching for 5 returns 2 - example: Searching for 7 returns 3 completed (Y|n): Handles more than trivial sized data set - detail: consider a data set of the first 10000 integers - example: Searching for 899 returns 898 completed (Y|n): Supports duplicate elements in the data set - detail: Use [1,3,5,5,7,9] as the data set - example: Searching for 3 returns 1 - example: Searching for 5 returns 2 completed (Y|n): Handles expired call for duplicate elements - detail: Use [1,3,5,5,7,9] as the data set - example: Third call to search for 5 is handled like missing element completed (Y|n): Supports sorted array of strings - detail: Use ["a", "b", "c", "d"] as the data set - example: Searching for "c" returns 2 completed (Y|n): Supports duplicate strings - detail: Use ["a", "b", "c", "c", "d"] as the data set - example: First search for "c" returns 2 - example: Second search for "c" returns 3 completed (Y|n): Handles expired call for duplicate string elements - detail: Use ["a", "b", "c", "c", "d"] as the data set - example: Third call to search for "c" is handled like missing element completed (Y|n): ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┓ ┃ Requirement ┃ Time ┃ ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╊━━━━━━━━━━┫ ┃ Create a binary search class that is initialized with an array of integers ┃ 00:04:02 ┃ ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╊━━━━━━━━━━┫ ┃ Successfully deal with values that are not in the data set ┃ 00:04:09 ┃ ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╊━━━━━━━━━━┫ ┃ Binary Search handles an odd number of elements in the data set ┃ 00:10:49 ┃ ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╊━━━━━━━━━━┫ ┃ Handles more than trivial sized data set ┃ 00:02:28 ┃ ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╊━━━━━━━━━━┫ ┃ Supports duplicate elements in the data set ┃ 00:05:17 ┃ ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╊━━━━━━━━━━┫ ┃ Handles expired call for duplicate elements ┃ 00:10:35 ┃ ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╊━━━━━━━━━━┫ ┃ Supports sorted array of strings ┃ 00:02:27 ┃ ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╊━━━━━━━━━━┫ ┃ Supports duplicate strings ┃ 00:02:35 ┃ ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╊━━━━━━━━━━┫ ┃ Handles expired call for duplicate string elements ┃ 00:00:57 ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━┛ Questions: - What about supporting duplicate data changed your solution? - Did your approach get better or worse after dealing with duplicates? - What was the hardest part about adding support for strings? - Can you think of a different technique for building the kata? - What can you say about the relative merits of the various techniques you chose? - Which is the most likely to make it into production code? - Which was the most fun to write? - Which requirement was the hardest to get working? - For all of the above, ask yourself why? My Specs: 1 require 'spec_helper' 2 require 'binary_search' 3 require 'its' 4 5 describe BinarySearch do 6 subject(:binary_search) { BinarySearch.new(data) } 7 let(:data) { [1,3] } 8 9 describe "#initialize" do 10 it "instantiates" do 11 expect { binary_search }.to_not raise_exception 12 end 13 end 14 15 describe '.find' do 16 its(:find, 3) { should eq(1) } 17 its(:find, 5) { should be_nil } 18 19 context 'odd numbers of elements in data set' do 20 let(:data) { [1,3,5,7,9] } 21 its(:find, 0) { should be_nil } 22 its(:find, 1) { should eq(0) } 23 its(:find, 2) { should be_nil } 24 its(:find, 3) { should eq(1) } 25 its(:find, 4) { should be_nil } 26 its(:find, 5) { should eq(2) } 27 its(:find, 6) { should be_nil } 28 its(:find, 7) { should eq(3) } 29 its(:find, 8) { should be_nil } 30 its(:find, 9) { should eq(4) } 31 its(:find, 10) { should be_nil } 32 end 33 34 context 'non trivial data sets' do 35 let(:data) { [] } 36 before { for i in 1..10000 do data << i end } 37 its(:find, 899) { should eq(898) } 38 end 39 40 context 'duplicate data elements' do 41 let(:data) { [1,3,5,5,7,9] } 42 its(:find, 3) { should eq(1) } 43 its(:find, 5) { should eq(2) } 44 it 'handles expired calls for duplicate elements' do 45 2.times do binary_search.find(5) end 46 binary_search.find(5).should be_nil 47 end 48 end 49 50 context 'sorted array of strings' do 51 let(:data) { ['a','b','c','d'] } 52 its(:find, 'a') { should eq(0) } 53 its(:find, 'b') { should eq(1) } 54 its(:find, 'c') { should eq(2) } 55 its(:find, 'd') { should eq(3) } 56 its(:find, 'x') { should be_nil } 57 end 58 59 context 'duplicate strings' do 60 let(:data) { ['a', 'b', 'c', 'c', 'd'] } 61 its(:find, 'c') { should eq(2) } 62 it 'handles multiple searches' do 63 binary_search.find('c') 64 binary_search.find('c').should eq(3) 65 end 66 it 'handles expired calls for strings' do 67 2.times do binary_search.find('c') end 68 binary_search.find('c').should be_nil 69 end 70 end 71 end 72 end And, the BinarySearch Class: 1 class BinarySearch 2 def initialize(data) 3 @data = data 4 @search_hash = Hash.new 5 end 6 7 def find(target) 8 return @search_hash[target] = first_find(target) unless @search_hash[target] 9 return @data[@search_hash[target] + 1] == target ? @search_hash[target] += 1 : nil 10 end 11 12 private 13 14 def first_find(target) 15 return nil unless pivot = bsearch(target, @data) 16 pivot -= 1 while @data[pivot - 1] == target 17 return pivot 18 end 19 20 def bsearch(target, data) 21 pivot = data.length / 2 22 23 return pivot if data[pivot] == target 24 return nil if pivot == 0 25 26 if data[pivot] > target 27 return bsearch(target, data[0..pivot - 1]) 28 else 29 offset = bsearch(target, data[pivot..-1]) 30 return offset ? pivot + offset : nil 31 end 32 end 33 end