diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..be51010 --- /dev/null +++ b/Gemfile @@ -0,0 +1 @@ +gem 'minitest' \ No newline at end of file diff --git a/examples/quick.rb b/examples/quick.rb new file mode 100644 index 0000000..2092a31 --- /dev/null +++ b/examples/quick.rb @@ -0,0 +1,51 @@ +#!/usr/bin/env ruby +# +# Classsic quick sort +# +require_relative '../visual_list' + +def partition(arr, start_idx, end_idx) + # pick middle point between start and end + pivot = (end_idx - start_idx) / 2 + start_idx + + target = arr[pivot] + + idx = start_idx + while idx <= end_idx do + if idx < pivot + # move value if > target. keep idx the same, subtract 1 from pivot. move to pivot + 1 + if arr[idx] > target + arr.move(idx, pivot) + pivot -= 1 + else + idx += 1 + end + elsif idx > pivot + # move value if < target. keep idx the same, add 1 to pivot. move to pivot - 1 + if arr[idx] < target + arr.move(idx, pivot) + pivot += 1 + else + idx += 1 + end + else + idx += 1 + end + + end + pivot +end + +def quicksort(arr, start_idx, end_idx) + if (end_idx - start_idx) > 0 + p = partition(arr, start_idx, end_idx) + quicksort(arr, start_idx, p - 1) + quicksort(arr, p + 1, end_idx) + end +end + +#v = VisualList.new(generator: :linear, count: 35) +v = VisualList.new(values: [3,2,5,7,1,4,10,21,6,8]) +quicksort(v, 0, v.last_index) + +v.sorted! \ No newline at end of file diff --git a/quick_sort.rb b/quick_sort.rb new file mode 100644 index 0000000..fa5e89d --- /dev/null +++ b/quick_sort.rb @@ -0,0 +1,45 @@ +def move(arr, source_index, destination_index) + arr.insert(destination_index, arr.delete_at(source_index)) +end + + +def partition(arr, start_idx, end_idx) + # pick middle point between start and end + pivot = (end_idx - start_idx) / 2 + start_idx + + target = arr[pivot] + + idx = start_idx + while idx <= end_idx do + if idx < pivot + # move value if > target. keep idx the same, subtract 1 from pivot. move to pivot + 1 + if arr[idx] > target + move(arr, idx, pivot) + pivot -= 1 + else + idx += 1 + end + elsif idx > pivot + # move value if < target. keep idx the same, add 1 to pivot. move to pivot - 1 + if arr[idx] < target + move(arr, idx, pivot) + pivot += 1 + else + idx += 1 + end + else + idx += 1 + end + + end + pivot +end + +def quicksort(arr, start_idx, end_idx) + if (end_idx - start_idx) > 0 + p = partition(arr, start_idx, end_idx) + quicksort(arr, start_idx, p - 1) + quicksort(arr, p + 1, end_idx) + end + arr +end diff --git a/test_quick_sort.rb b/test_quick_sort.rb new file mode 100644 index 0000000..62b7ed8 --- /dev/null +++ b/test_quick_sort.rb @@ -0,0 +1,59 @@ +require 'minitest/autorun' +require 'minitest/spec' +require './quick_sort' + +class TestQuickSort < MiniTest::Spec + def test_sort_single_item_array_returns_identity + source_array = [1] + retval = quicksort(source_array, 0, 0) + assert_equal(source_array, retval) + end + + def test_two_item_array_already_sorted_returns_identity + source_array = [1,4] + retval = quicksort(source_array, 0, 1) + assert_equal(source_array, retval) + end + + def test_two_item_array_unsorted_returns_sorted_array + source_array = [5,4] + retval = quicksort(source_array, 0, 1) + assert_equal(source_array.sort, retval) + end + + def test_three_item_array_unsorted_returns_sorted_array + source_array = [5,4,1] + retval = quicksort(source_array, 0, source_array.length - 1) + assert_equal(source_array.sort, retval) + end + + def test_arbitrary_item_array_unsorted_returns_sorted_array + source_array = [3,2,5,7,1,4,10,21,6,8] + retval = quicksort(source_array, 0, source_array.length - 1) + assert_equal(source_array.sort, retval) + end + + def test_randomly_generated_unsorted_arrays_returns_sorted_array + (1..5000).each do + source_array = rand(50).times.map {rand(20)} + retval = quicksort(source_array, 0, source_array.length - 1) + + if retval != source_array.sort + puts source_array.inpsect + end + assert_equal(source_array.sort, retval) + end + end + + def test_failing_array + source_array = [0, 1, 1, 1, 1, 1, 2, 1, 2, 3, 3, 4, 3, 4, 5, 6, 7, 7, 7, 8, 8, 9, 13, 9, 13, 13, 13, 14, 14, 15, 16, 15, 16, 17, 17, 17, 17, 17, 18, 18] + retval = quicksort(source_array, 0, source_array.length - 1) + + if retval != source_array.sort + puts source_array.inpsect + end + assert_equal(source_array.sort, retval) + end + + +end \ No newline at end of file diff --git a/test_visual_list.rb b/test_visual_list.rb new file mode 100644 index 0000000..d1181aa --- /dev/null +++ b/test_visual_list.rb @@ -0,0 +1,35 @@ +require 'minitest/autorun' +require 'minitest/spec' +require './examples/quick' + + +class TestVisualList < MiniTest::Spec + def test_obj_valid + v = VisualList.new(values: [3,2,5,7,1,4,10,21,6,8], delay: 0) + assert(v.is_a? VisualList) + end + + def test_simple_sort_works + v = VisualList.new(values: [3,2,5,7,1,4,10,21,6,8], delay: 0) + quicksort(v, 0, v.last_index) + end + + def test_random_sort + (1..50).each do + arr = source_array = rand(50).times.map {rand(20)} + next if arr.length < 8 + v = VisualList.new(values: arr, delay: 0) + quicksort(v, 0, v.last_index) + + v.sorted! + end + end + + def test_failing_sort + arr = [0, 1, 1, 1, 1, 1, 2, 1, 2, 3, 3, 4, 3, 4, 5, 6, 7, 7, 7, 8, 8, 9, 13, 9, 13, 13, 13, 14, 14, 15, 16, 15, 16, 17, 17, 17, 17, 17, 18, 18] + v = VisualList.new(values: arr, delay: 0) + quicksort(v, 0, v.last_index) + v.sorted! + end + +end \ No newline at end of file diff --git a/visual_list.rb b/visual_list.rb index f372eda..e5eae49 100644 --- a/visual_list.rb +++ b/visual_list.rb @@ -12,16 +12,24 @@ class VisualList # - :random - random values from minimum to maximum. Duplicates possible. # - :linear - 1 to count in random order. No duplicates. Default. # - :reversed - count down to 1 in descending order. No duplicates. - def initialize(count: 10, generator: :linear, minimum: 1, maximum: nil, delay: 0.1) - @count = count.to_i.abs - @reads = 0 - @swaps = 0 - @generator = generator - @delay = delay - @minimum = minimum.to_i.abs - @maximum = (maximum || count).to_i.abs - @values = self.send("generator_#{@generator}") + def initialize(count: 10, generator: :linear, minimum: 1, maximum: nil, delay: 0.1, values: nil) + @reads = 0 + @swaps = 0 + @delay = delay + + if values + @count = values.length + @values = values + else + @count = count.to_i.abs + @generator = generator + @delay = delay + @minimum = minimum.to_i.abs + @maximum = (maximum || count).to_i.abs + @values = self.send("generator_#{@generator}") + end @largest_value = @values.inject{|a,b| b > a ? a = b : a } + clear_screen display_list end @@ -39,6 +47,21 @@ def get(index) @values[index] end alias :[] :get + + # move a value in the array to another location + # returns true if success, raises exception if arguments are out of bounds + def move(source_index, destination_index) + [source_index, destination_index].each do |index| + in_bounds!(index) + end + + display_list(swap_index_a: source_index, swap_index_b: destination_index) + + @values.insert(destination_index, @values.delete_at(source_index)) + @swaps += 1 + + display_list(swap_index_a: source_index, swap_index_b: destination_index) + end # Swap the values of two index locations. Returns true if operation is # successful. Raises exception of either argument is our of list bounds. @@ -68,6 +91,7 @@ def sorted! puts "Success! List is sorted!" true else + puts @values.inspect raise "Sorry, list has not been sorted." end end @@ -140,4 +164,6 @@ def generator_linear def generator_reversed @count.downto(1).map{|i| i } end + + end \ No newline at end of file