DSA 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,183 @@
1
+ module DSA
2
+ # Exception
3
+ class ListInsertError < StandardError
4
+ end
5
+ class ListRemovalError < StandardError
6
+ end
7
+
8
+ # Node
9
+ class ListNode
10
+ attr_accessor :element, :prev, :next
11
+ def initialize(e)
12
+ @element = e
13
+ @prev = nil
14
+ @next = nil
15
+ end
16
+ end
17
+
18
+ # ListIterator provides iteration on the list and constant time insertion and removal
19
+ class ListIterator
20
+ def initialize(node, list)
21
+ @node = node
22
+ @list = list
23
+ end
24
+
25
+ def next
26
+ @node = @node.next
27
+ raise StopIteration if @node == @list.tail || !@node
28
+ @node.element
29
+ end
30
+
31
+ def previous
32
+ @node = @node.prev
33
+ raise StopIteration if @node == @list.head || !@node
34
+ @node.element
35
+ end
36
+
37
+ def insert(e)
38
+ @list.insert_node_between @node.prev, @node, ListNode.new(e)
39
+ end
40
+
41
+ def remove
42
+ node = @list.remove_node(@node)
43
+ node.element
44
+ end
45
+
46
+ def update(e)
47
+ @node.element = e
48
+ end
49
+
50
+
51
+ end
52
+
53
+ # doubly linked list
54
+ class List
55
+ attr_reader :length
56
+ attr_reader :head, :tail # head and tail are two empty guards
57
+ include Enumerable
58
+
59
+ def initialize
60
+ @head = ListNode.new nil
61
+ @tail = ListNode.new nil
62
+ @head.next = @tail
63
+ @tail.prev = @head
64
+ @length = 0
65
+ end
66
+
67
+ def push(e)
68
+ insert_node_between @tail.prev, @tail, ListNode.new(e)
69
+ end
70
+
71
+ def pop
72
+ node = remove_node @tail.prev
73
+ node.element
74
+ end
75
+
76
+ def unshift(e)
77
+ insert_node_between @head, @head.next, ListNode.new(e)
78
+ end
79
+
80
+ def shift
81
+ node = remove_node @head.next
82
+ node.element
83
+ end
84
+
85
+ def first
86
+ self[0]
87
+ end
88
+
89
+ def last
90
+ self[@length-1]
91
+ end
92
+
93
+ def empty?
94
+ @length == 0
95
+ end
96
+
97
+ # insert an element at position index(0 based), linear time cost, use with caution, iterator preferred
98
+ def insert_at(index, e)
99
+ push e if index > @length - 1
100
+ node = get_node index
101
+ insert_node_between node.prev, node, ListNode.new(e)
102
+ end
103
+
104
+ # remove an element at position index(0 based), linear time cost, use with caution, iterator preferred
105
+ def remove_at(index)
106
+ node = get_node index
107
+ remove_node node
108
+ node.element
109
+ end
110
+
111
+ # access an element at position index(0 based), linear time cost, use with caution
112
+ def [](index)
113
+ get_node(index).element
114
+ end
115
+
116
+ def each
117
+ node = @head.next
118
+ while 1
119
+ break if node == @tail
120
+ yield node.element
121
+ node = node.next
122
+ end
123
+ end
124
+
125
+ # an iterator starting from head
126
+ def begin_iterator
127
+ ListIterator.new @head, self
128
+ end
129
+
130
+ # an iterator starting from end
131
+ def end_iterator
132
+ ListIterator.new @tail, self
133
+ end
134
+
135
+ # the following methods are used to process nodes, user usually does not need to use them
136
+ # insert node between two nodes
137
+ def insert_node_between(a, b, new_one)
138
+ a.next = new_one
139
+ new_one.prev = a
140
+ new_one.next = b
141
+ b.prev = new_one
142
+ @length += 1
143
+ end
144
+
145
+ # remove a node
146
+ def remove_node(node)
147
+ raise( ListRemovalError, 'List is empty' ) if @length == 0
148
+ before = node.prev
149
+ after = node.next
150
+ before.next = after
151
+ after.prev = before
152
+ @length -= 1
153
+ node
154
+ end
155
+
156
+ private
157
+ def get_node(index)
158
+ index += @length if index < 0
159
+ raise(ListInsertError, 'Index out of bound: ' + index.to_s) if index > @length - 1 || index < 0
160
+ if index > @length / 2
161
+ node = @tail.prev
162
+ current_index = @length - 1
163
+ while 1
164
+ return node if index == current_index
165
+ node = node.prev
166
+ current_index -= 1
167
+ end
168
+ else
169
+ node = @head.next
170
+ current_index = 0
171
+ while 1
172
+ return node if index == current_index
173
+ node = node.next
174
+ current_index += 1
175
+ end
176
+ end
177
+ end
178
+ end
179
+
180
+
181
+
182
+
183
+ end
@@ -0,0 +1,70 @@
1
+ module DSA
2
+ class PriorityQueueNode
3
+ attr_accessor :priority, :element
4
+ def initialize( priority, element )
5
+ @priority = priority
6
+ @element = element
7
+ end
8
+ end
9
+ # priority queue built on top of array based heap
10
+ class PriorityQueue
11
+ def initialize
12
+ @data = Array.new
13
+ end
14
+
15
+ def length
16
+ @data.length
17
+ end
18
+
19
+ # get the value of the top without removing it
20
+ def top
21
+ return nil if length == 0
22
+ @data.first.element
23
+ end
24
+
25
+ # priority is a number, the smaller it is, higher it has a priority
26
+ def add(priority, element)
27
+ @data.push PriorityQueueNode.new(priority,element)
28
+ up_heap_bubbling length-1
29
+ end
30
+
31
+ # remove the top and return the element
32
+ def pop
33
+ value = top
34
+ @data[0] = @data.pop # move the rightmost node to the top
35
+ down_heap_sinking 0
36
+ value
37
+ end
38
+
39
+ private
40
+ def up_heap_bubbling(index)
41
+ parent_index = (index - 1) / 2
42
+ return if parent_index < 0
43
+ if @data[index].priority < @data[parent_index].priority
44
+ swap index, parent_index
45
+ up_heap_bubbling parent_index
46
+ end
47
+ end
48
+
49
+ def swap(index_a, index_b)
50
+ @data[index_a], @data[index_b] = @data[index_b], @data[index_a]
51
+ end
52
+
53
+ def down_heap_sinking(index)
54
+ left_child_index = 2*index + 1
55
+ right_child_index = 2*index + 2
56
+ small_child_index = index
57
+ if left_child_index < length
58
+ small_child_index = left_child_index if @data[left_child_index].priority < @data[small_child_index].priority
59
+ end
60
+ if right_child_index < length
61
+ small_child_index = right_child_index if @data[right_child_index].priority < @data[small_child_index].priority
62
+ end
63
+ if small_child_index != index
64
+ swap small_child_index, index
65
+ down_heap_sinking small_child_index
66
+ end
67
+ end
68
+
69
+ end
70
+ end
@@ -0,0 +1,151 @@
1
+ require_relative 'list'
2
+
3
+ module DSA
4
+ # The stack data structure
5
+ # Delegate the job to ruby Array, no need to worry about its growing and shrinking.
6
+ class ArrayStack
7
+ def initialize
8
+ @data = Array.new
9
+ end
10
+
11
+ def push(e)
12
+ @data.push e
13
+ end
14
+
15
+ def pop
16
+ @data.pop
17
+ end
18
+
19
+ def top
20
+ @data.last
21
+ end
22
+
23
+ def empty?
24
+ @data.empty?
25
+ end
26
+
27
+ def length
28
+ @data.length
29
+ end
30
+
31
+ end
32
+
33
+ # Stack built on top of list
34
+ class ListStack
35
+ def initialize
36
+ @data = DSA::List.new
37
+ end
38
+
39
+ def push(e)
40
+ @data.push e
41
+ end
42
+
43
+ def pop
44
+ @data.pop
45
+ end
46
+
47
+ def top
48
+ @data.last
49
+ end
50
+
51
+ def empty?
52
+ @data.empty?
53
+ end
54
+
55
+ def length
56
+ @data.length
57
+ end
58
+
59
+ end
60
+
61
+ # Typically, array is not a good idea to implement a queue, as it requires linear time to shift other elements after dequeue
62
+ # We can try to avoid the shifting by keep track of the index of the front queue element and make the array circular
63
+ class ArrayQueue
64
+ attr_reader :length
65
+
66
+ def initialize(initial_capacity = 10)
67
+ @data = Array.new initial_capacity
68
+ @front_index = 0
69
+ @length = 0
70
+ end
71
+
72
+ def enqueue(e)
73
+ grow if @length+1 == capacity
74
+ @length += 1
75
+ index = (@front_index + @length - 1 ) % capacity
76
+ @data[index] = e
77
+ end
78
+
79
+ def dequeue
80
+ shrink if @length - 1 < capacity / 2 and capacity / 2 >= 10
81
+ return nil if @length == 0
82
+ next_front = (@front_index + 1 ) % capacity
83
+ value = @data[@front_index]
84
+ @data[@front_index] = nil
85
+ @front_index = next_front
86
+ @length -= 1
87
+ value
88
+ end
89
+
90
+ def first
91
+ @data[@front_index]
92
+ end
93
+
94
+ def empty?
95
+ @length == 0
96
+ end
97
+
98
+ private
99
+ def capacity
100
+ @data.length
101
+ end
102
+
103
+ def grow
104
+ new_data = Array.new capacity * 2
105
+ copy_to new_data
106
+ end
107
+
108
+ def shrink
109
+ new_data = Array.new capacity / 2
110
+ copy_to new_data
111
+ end
112
+
113
+ def copy_to(new_data)
114
+ (0..@length-1).each do |i|
115
+ old_index = (@front_index + i) % capacity
116
+ new_data[i] = @data[old_index]
117
+ end
118
+ @data = new_data
119
+ @front_index = 0
120
+ end
121
+
122
+ end
123
+
124
+ # FIFO queue built on top of linked list
125
+ class ListQueue
126
+ def initialize
127
+ @data = DSA::List.new
128
+ end
129
+
130
+ def enqueue(e)
131
+ @data.push e
132
+ end
133
+
134
+ def dequeue
135
+ @data.shift
136
+ end
137
+
138
+ def first
139
+ @data.first
140
+ end
141
+
142
+ def empty?
143
+ @data.empty?
144
+ end
145
+
146
+ def length
147
+ @data.length
148
+ end
149
+ end
150
+
151
+ end
@@ -0,0 +1,3 @@
1
+ module DSA
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,91 @@
1
+ require 'test/unit'
2
+ require 'benchmark'
3
+ require 'DSA'
4
+
5
+ class MyTest < Test::Unit::TestCase
6
+
7
+ # Called before every test method runs. Can be used
8
+ # to set up fixture information.
9
+ def setup
10
+ # Do nothing
11
+ end
12
+
13
+ # Called after every test method runs. Can be used to tear
14
+ # down fixture information.
15
+
16
+ def teardown
17
+ # Do nothing
18
+ end
19
+
20
+ def test_function
21
+ assert_equal 120, DSA::Algorithm::factorial(5), 'factorial function is wrong'
22
+ assert DSA::Algorithm::binary_search((1..9).to_a, 2, 0, 8), 'binary search failed'
23
+ end
24
+
25
+ def test_performance
26
+ puts
27
+
28
+ value = 10**7
29
+ target = value/3
30
+ data = (0..value).to_a
31
+ puts 'testing performance'
32
+ Benchmark.bm(20) do |x|
33
+ x.report('binary search') { DSA::Algorithm::binary_search(data, target, 0, value-1) }
34
+ x.report('linear search') { data.each do |a| break if a == target end }
35
+ end
36
+ end
37
+
38
+
39
+ def test_sort
40
+ original = [3,6,7,8,2,4,7,8,1,5,9,6]
41
+ expect = [1,2,3,4,5,6,6,7,7,8,8,9]
42
+ DSA::Algorithm::insertion_sort!(original)
43
+ assert_equal expect, original, 'sort failed'
44
+
45
+ original = [3,6,7,8,2,4,7,8,1,5,9,6]
46
+ expect = [1,2,3,4,5,6,6,7,7,8,8,9]
47
+ DSA::Algorithm::quick_sort!(original, 0, original.length-1)
48
+ assert_equal expect, original, 'sort failed'
49
+
50
+ puts
51
+ puts 'insertion sort vs built in'
52
+ value = 10**3
53
+ sorted_a = (0..value).to_a
54
+ sorted_b = (0..value).to_a
55
+ sorted_c = (0..value).to_a
56
+
57
+
58
+ puts 'sort on already sorted'
59
+ Benchmark.bm(20) do |x|
60
+ x.report('insertion sort') { DSA::Algorithm::insertion_sort! sorted_a }
61
+ x.report('quick sort') { DSA::Algorithm::quick_sort! sorted_c, 0, sorted_c.length-1 }
62
+ x.report('built in sort') { sorted_b.sort! }
63
+ end
64
+
65
+
66
+ puts 'sort on not sorted'
67
+ value = 10**4
68
+ not_sorted_a = value.times.map { Random.rand(value) }
69
+ not_sorted_b = value.times.map { Random.rand(value) }
70
+ not_sorted_c = value.times.map { Random.rand(value) }
71
+ Benchmark.bm(20) do |x|
72
+ x.report('insertion sort') { DSA::Algorithm::insertion_sort! not_sorted_a }
73
+ x.report('quick sort') { DSA::Algorithm::quick_sort! not_sorted_c, 0, not_sorted_c.length-1 }
74
+ x.report('built in sort') { not_sorted_b.sort! }
75
+ end
76
+
77
+ puts 'sort on not sorted'
78
+ value = 10**6
79
+ not_sorted_b = value.times.map { Random.rand(value) }
80
+ not_sorted_c = value.times.map { Random.rand(value) }
81
+ Benchmark.bm(20) do |x|
82
+ x.report('quick sort') { DSA::Algorithm::quick_sort! not_sorted_c, 0, not_sorted_c.length-1 }
83
+ x.report('built in sort') { not_sorted_b.sort! }
84
+ end
85
+
86
+
87
+ end
88
+
89
+
90
+
91
+ end