dsa-ruby 1.0.0

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6d48c162c54e6f3b5a72e0ec6c5bfb01ea194db9aa01edd8b3acf22ec8e65d2a
4
+ data.tar.gz: 90dec8d0903d522bd53c1928f1b4bfe4e3183c51ac0cd6c6c2089864eeaf31ed
5
+ SHA512:
6
+ metadata.gz: ffc6d6ee8215956588a61c477fbf92c8a441d43ded58525f758086c07433389616c9f538bac40d2de30f113d184beec0cfb155d236cc7ddb08da815b9d1aa75f
7
+ data.tar.gz: 700cbc183e14b7fe0884c52b43a49c686530e6276deea50a3eaf6b7df9e65dfd23b51ccc21c38d5af38bed62496f59e1314c43c59a20d0587344d495606406c3
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,245 @@
1
+ # dsa-ruby
2
+
3
+ Data structures for coding interviews in Ruby. Provides the missing structures
4
+ that Ruby's stdlib doesn't include but are essential for technical interviews.
5
+
6
+ ## Installation
7
+
8
+ ```ruby
9
+ gem "dsa-ruby"
10
+ ```
11
+
12
+ Or install directly:
13
+
14
+ ```sh
15
+ gem install dsa-ruby
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ```ruby
21
+ require "dsa-ruby"
22
+ ```
23
+
24
+ ### MinHeap / MaxHeap
25
+
26
+ ```ruby
27
+ heap = DSA::MinHeap.new
28
+ heap.push(5, 3, 7, 1)
29
+ heap.pop # => 1
30
+ heap.peek # => 3
31
+
32
+ max_heap = DSA::MaxHeap.new([5, 3, 7, 1])
33
+ max_heap.pop # => 7
34
+ ```
35
+
36
+ ### PriorityQueue
37
+
38
+ ```ruby
39
+ pq = DSA::PriorityQueue.new(type: :min)
40
+ pq.push("task_a", 3)
41
+ pq.push("task_b", 1)
42
+ pq.push("task_c", 2)
43
+
44
+ pq.pop # => "task_b" (lowest priority first)
45
+
46
+ pq = DSA::PriorityQueue.new(type: :max)
47
+ pq.push("task_a", 3)
48
+ pq.push("task_b", 1)
49
+ pq.pop # => "task_a" (highest priority first)
50
+ ```
51
+
52
+ ### Deque
53
+
54
+ ```ruby
55
+ dq = DSA::Deque.new
56
+ dq.push_front(1).push_back(2)
57
+ dq.pop_front # => 1
58
+ dq.pop_back # => 2
59
+
60
+ # Enumerable support
61
+ dq.push_back(1, 2, 3)
62
+ dq.map { |x| x * 2 } # => [2, 4, 6]
63
+ ```
64
+
65
+ ### Trie
66
+
67
+ ```ruby
68
+ trie = DSA::Trie.new
69
+ trie.insert("apple")
70
+ trie.search("apple") # => true
71
+ trie.search("app") # => false
72
+ trie.starts_with("app") # => true
73
+ trie.delete("apple")
74
+ ```
75
+
76
+ ### UnionFind
77
+
78
+ ```ruby
79
+ uf = DSA::UnionFind.new(10)
80
+ uf.union(1, 2)
81
+ uf.union(2, 3)
82
+ uf.connected?(1, 3) # => true
83
+ uf.count # => 8 (started with 10, merged 2 pairs)
84
+ ```
85
+
86
+ ### LinkedList
87
+
88
+ ```ruby
89
+ # Singly Linked List
90
+ list = DSA::LinkedList::Singly.new
91
+ list.push(1).push(2).push(3)
92
+ list.pop # => 3
93
+ list.to_a # => [2, 1]
94
+ list.reverse!
95
+ list.to_a # => [1, 2]
96
+
97
+ # Doubly Linked List
98
+ dll = DSA::LinkedList::Doubly.new
99
+ dll.push_back(1).push_back(2).push_back(3)
100
+ dll.pop_front # => 1
101
+ dll.pop_back # => 3
102
+ dll.to_a # => [2]
103
+ ```
104
+
105
+ ### Stack
106
+
107
+ ```ruby
108
+ stack = DSA::Stack.new
109
+ stack.push(1).push(2).push(3)
110
+ stack.pop # => 3
111
+ stack.peek # => 2
112
+ ```
113
+
114
+ ### Queue
115
+
116
+ ```ruby
117
+ queue = DSA::Queue.new
118
+ queue.enqueue("a").enqueue("b").enqueue("c")
119
+ queue.dequeue # => "a"
120
+ queue.peek # => "b"
121
+ ```
122
+
123
+ ### BinarySearchTree
124
+
125
+ ```ruby
126
+ bst = DSA::BinarySearchTree.new
127
+ bst.insert(5).insert(3).insert(7).insert(1).insert(4)
128
+ bst.search(3) # => true
129
+ bst.min # => 1
130
+ bst.max # => 7
131
+ bst.inorder # => [1, 3, 4, 5, 7]
132
+ bst.preorder # => [5, 3, 1, 4, 7]
133
+ bst.postorder # => [1, 4, 3, 7, 5]
134
+ bst.delete(3)
135
+ ```
136
+
137
+ ## API Reference
138
+
139
+ ### Stack
140
+
141
+ - `#push(val)` — add to top (O(1))
142
+ - `#pop` — remove and return top (O(1))
143
+ - `#peek` — return top without removing (O(1))
144
+
145
+ ### Queue
146
+
147
+ - `#enqueue(val)` — add to back
148
+ - `#dequeue` — remove and return front
149
+ - `#peek` — return front without removing
150
+
151
+ ### BinarySearchTree
152
+
153
+ - `#insert(val)` — insert value (O(log n) avg)
154
+ - `#search(val)` — check if value exists (boolean)
155
+ - `#delete(val)` — remove value, returns true if deleted
156
+ - `#min` / `#max` — return minimum/maximum value
157
+ - `#inorder` / `#preorder` / `#postorder` — traversal arrays
158
+
159
+ ### Common Methods
160
+
161
+ All structures support:
162
+
163
+ - `#size` — number of elements
164
+ - `#empty?` — whether the structure is empty
165
+ - `#to_a` — returns a defensive copy as an array
166
+
167
+ ### Heap (`MinHeap`, `MaxHeap`)
168
+
169
+ - `#push(val)` — insert element (O(log n))
170
+ - `#pop` — remove and return extremum (O(log n))
171
+ - `#peek` — return extremum without removing (O(1))
172
+
173
+ ### PriorityQueue
174
+
175
+ - `#push(item, priority)` — insert with priority
176
+ - `#pop` — remove and return highest/lowest priority item
177
+ - `#peek` — return top item without removing
178
+ - `#priority_peek` — return top item's priority value
179
+
180
+ ### Deque
181
+
182
+ - `#push_front(val)` / `#push_back(val)` — insert at either end
183
+ - `#pop_front` / `#pop_back` — remove from either end
184
+ - `#front` / `#back` — peek at either end
185
+ - Includes `Enumerable`
186
+
187
+ ### Trie
188
+
189
+ - `#insert(word)` — insert a word
190
+ - `#search(word)` — exact word lookup
191
+ - `#starts_with(prefix)` — prefix lookup
192
+ - `#delete(word)` — remove a word
193
+
194
+ ### UnionFind
195
+
196
+ - `#find(x)` — find root with path compression
197
+ - `#union(x, y)` — merge sets, returns true if merged
198
+ - `#connected?(x, y)` — check if in same set
199
+ - `#count` — number of disjoint sets
200
+
201
+ ### LinkedList (Singly / Doubly)
202
+
203
+ - `#push(val)` / `#push_front(val)` / `#push_back(val)` — insert
204
+ - `#pop` / `#pop_front` / `#pop_back` — remove
205
+ - `#delete(val)` — remove by value
206
+ - `#find(val)` — find node by value
207
+ - `#peek` / `#front` / `#back` — peek
208
+ - `#reverse!` — reverse in-place (Singly only)
209
+ - Includes `Enumerable`
210
+
211
+ ## Development
212
+
213
+ ```sh
214
+ bundle install
215
+ bundle exec rspec
216
+ ```
217
+
218
+ ## Release
219
+
220
+ ### Using the release script (recommended)
221
+
222
+ Bump version, build gem, push to RubyGems, and create GitHub Release:
223
+
224
+ ```sh
225
+ ./release.sh patch # 0.1.0 -> 0.1.1
226
+ ./release.sh minor # 0.1.0 -> 0.2.0
227
+ ./release.sh major # 0.1.0 -> 1.0.0
228
+ ```
229
+
230
+ Requires: `gh` CLI authenticated, `GEM_HOST_API_KEY` environment variable set.
231
+
232
+ ### Using Makefile
233
+
234
+ ```sh
235
+ make build # Build the gem
236
+ make test # Run tests
237
+ make install # Build and install locally
238
+ make publish # Build and push to rubygems.org
239
+ make release # Full release workflow
240
+ make help # Show all commands
241
+ ```
242
+
243
+ ## License
244
+
245
+ MIT
@@ -0,0 +1,154 @@
1
+ module DSA
2
+ class BinarySearchTree
3
+ Node = Struct.new(:val, :left, :right, keyword_init: true)
4
+
5
+ def initialize
6
+ @root = nil
7
+ @size = 0
8
+ end
9
+
10
+ def insert(val)
11
+ @root, added = insert_recursive(@root, val)
12
+ @size += 1 if added
13
+ self
14
+ end
15
+
16
+ def search(val)
17
+ !!find_node(@root, val)
18
+ end
19
+
20
+ def delete(val)
21
+ @root, deleted = delete_recursive(@root, val)
22
+ @size -= 1 if deleted
23
+ deleted
24
+ end
25
+
26
+ def min
27
+ raise IndexError, "tree is empty" if empty?
28
+ find_min(@root).val
29
+ end
30
+
31
+ def max
32
+ raise IndexError, "tree is empty" if empty?
33
+ find_max(@root).val
34
+ end
35
+
36
+ def inorder
37
+ return [] if empty?
38
+ result = []
39
+ traverse_inorder(@root, result)
40
+ result
41
+ end
42
+
43
+ def preorder
44
+ return [] if empty?
45
+ result = []
46
+ traverse_preorder(@root, result)
47
+ result
48
+ end
49
+
50
+ def postorder
51
+ return [] if empty?
52
+ result = []
53
+ traverse_postorder(@root, result)
54
+ result
55
+ end
56
+
57
+ def size
58
+ @size
59
+ end
60
+
61
+ def empty?
62
+ @size == 0
63
+ end
64
+
65
+ def to_a
66
+ inorder
67
+ end
68
+
69
+ private
70
+
71
+ def insert_recursive(node, val)
72
+ if node.nil?
73
+ return [Node.new(val: val, left: nil, right: nil), true]
74
+ end
75
+
76
+ if val < node.val
77
+ node.left, added = insert_recursive(node.left, val)
78
+ elsif val > node.val
79
+ node.right, added = insert_recursive(node.right, val)
80
+ else
81
+ added = false
82
+ end
83
+
84
+ [node, added]
85
+ end
86
+
87
+ def find_node(node, val)
88
+ return nil unless node
89
+ return node if node.val == val
90
+
91
+ if val < node.val
92
+ find_node(node.left, val)
93
+ else
94
+ find_node(node.right, val)
95
+ end
96
+ end
97
+
98
+ def delete_recursive(node, val)
99
+ return [nil, false] unless node
100
+
101
+ if val < node.val
102
+ node.left, deleted = delete_recursive(node.left, val)
103
+ elsif val > node.val
104
+ node.right, deleted = delete_recursive(node.right, val)
105
+ else
106
+ deleted = true
107
+ return [delete_node(node), true]
108
+ end
109
+
110
+ [node, deleted]
111
+ end
112
+
113
+ def delete_node(node)
114
+ return node.right unless node.left
115
+ return node.left unless node.right
116
+
117
+ successor = find_min(node.right)
118
+ node.val = successor.val
119
+ node.right, _ = delete_recursive(node.right, successor.val)
120
+ node
121
+ end
122
+
123
+ def find_min(node)
124
+ node = node.left while node.left
125
+ node
126
+ end
127
+
128
+ def find_max(node)
129
+ node = node.right while node.right
130
+ node
131
+ end
132
+
133
+ def traverse_inorder(node, result)
134
+ return unless node
135
+ traverse_inorder(node.left, result)
136
+ result << node.val
137
+ traverse_inorder(node.right, result)
138
+ end
139
+
140
+ def traverse_preorder(node, result)
141
+ return unless node
142
+ result << node.val
143
+ traverse_preorder(node.left, result)
144
+ traverse_preorder(node.right, result)
145
+ end
146
+
147
+ def traverse_postorder(node, result)
148
+ return unless node
149
+ traverse_postorder(node.left, result)
150
+ traverse_postorder(node.right, result)
151
+ result << node.val
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,57 @@
1
+ module DSA
2
+ class Deque
3
+ def initialize
4
+ @deque = []
5
+ @head = 0
6
+ end
7
+
8
+ def push_front(val)
9
+ @deque.unshift(val)
10
+ self
11
+ end
12
+
13
+ def push_back(val)
14
+ @deque.push(val)
15
+ self
16
+ end
17
+
18
+ def pop_front
19
+ raise IndexError, "deque is empty" if empty?
20
+ @deque.shift
21
+ end
22
+
23
+ def pop_back
24
+ raise IndexError, "deque is empty" if empty?
25
+ @deque.pop
26
+ end
27
+
28
+ def front
29
+ raise IndexError, "deque is empty" if empty?
30
+ @deque.first
31
+ end
32
+
33
+ def back
34
+ raise IndexError, "deque is empty" if empty?
35
+ @deque.last
36
+ end
37
+
38
+ def size
39
+ @deque.size
40
+ end
41
+
42
+ def empty?
43
+ @deque.empty?
44
+ end
45
+
46
+ def to_a
47
+ @deque.dup
48
+ end
49
+
50
+ include Enumerable
51
+
52
+ def each(&block)
53
+ return enum_for(:each) unless block_given?
54
+ @deque.each(&block)
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,145 @@
1
+ module DSA
2
+ class MinHeap
3
+ def initialize(elements = [])
4
+ @elements = []
5
+ elements.each { |e| push(e) }
6
+ end
7
+
8
+ def push(val)
9
+ @elements << val
10
+ sift_up(@elements.size - 1)
11
+ self
12
+ end
13
+
14
+ def pop
15
+ raise IndexError, "heap is empty" if empty?
16
+
17
+ swap(0, @elements.size - 1)
18
+ min = @elements.pop
19
+ sift_down(0)
20
+ min
21
+ end
22
+
23
+ def peek
24
+ raise IndexError, "heap is empty" if empty?
25
+ @elements[0]
26
+ end
27
+
28
+ def size
29
+ @elements.size
30
+ end
31
+
32
+ def empty?
33
+ @elements.empty?
34
+ end
35
+
36
+ def to_a
37
+ @elements.dup
38
+ end
39
+
40
+ private
41
+
42
+ def sift_up(i)
43
+ while i > 0
44
+ parent = (i - 1) / 2
45
+ break if (@elements[parent] <=> @elements[i]) <= 0
46
+
47
+ swap(parent, i)
48
+ i = parent
49
+ end
50
+ end
51
+
52
+ def sift_down(i)
53
+ n = @elements.size
54
+ loop do
55
+ smallest = i
56
+ left = 2 * i + 1
57
+ right = 2 * i + 2
58
+
59
+ smallest = left if left < n && (@elements[left] <=> @elements[smallest]) < 0
60
+ smallest = right if right < n && (@elements[right] <=> @elements[smallest]) < 0
61
+
62
+ break if smallest == i
63
+
64
+ swap(i, smallest)
65
+ i = smallest
66
+ end
67
+ end
68
+
69
+ def swap(i, j)
70
+ @elements[i], @elements[j] = @elements[j], @elements[i]
71
+ end
72
+ end
73
+
74
+ class MaxHeap
75
+ def initialize(elements = [])
76
+ @elements = []
77
+ elements.each { |e| push(e) }
78
+ end
79
+
80
+ def push(val)
81
+ @elements << val
82
+ sift_up(@elements.size - 1)
83
+ self
84
+ end
85
+
86
+ def pop
87
+ raise IndexError, "heap is empty" if empty?
88
+
89
+ swap(0, @elements.size - 1)
90
+ max = @elements.pop
91
+ sift_down(0)
92
+ max
93
+ end
94
+
95
+ def peek
96
+ raise IndexError, "heap is empty" if empty?
97
+ @elements[0]
98
+ end
99
+
100
+ def size
101
+ @elements.size
102
+ end
103
+
104
+ def empty?
105
+ @elements.empty?
106
+ end
107
+
108
+ def to_a
109
+ @elements.dup
110
+ end
111
+
112
+ private
113
+
114
+ def sift_up(i)
115
+ while i > 0
116
+ parent = (i - 1) / 2
117
+ break if (@elements[parent] <=> @elements[i]) >= 0
118
+
119
+ swap(parent, i)
120
+ i = parent
121
+ end
122
+ end
123
+
124
+ def sift_down(i)
125
+ n = @elements.size
126
+ loop do
127
+ largest = i
128
+ left = 2 * i + 1
129
+ right = 2 * i + 2
130
+
131
+ largest = left if left < n && (@elements[left] <=> @elements[largest]) > 0
132
+ largest = right if right < n && (@elements[right] <=> @elements[largest]) > 0
133
+
134
+ break if largest == i
135
+
136
+ swap(i, largest)
137
+ i = largest
138
+ end
139
+ end
140
+
141
+ def swap(i, j)
142
+ @elements[i], @elements[j] = @elements[j], @elements[i]
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,249 @@
1
+ module DSA
2
+ module LinkedList
3
+ class Node
4
+ attr_accessor :val, :next
5
+
6
+ def initialize(val = 0, nxt = nil)
7
+ @val = val
8
+ @next = nxt
9
+ end
10
+ end
11
+
12
+ class DoublyNode
13
+ attr_accessor :val, :next, :prev
14
+
15
+ def initialize(val = 0, nxt = nil, prev = nil)
16
+ @val = val
17
+ @next = nxt
18
+ @prev = prev
19
+ end
20
+ end
21
+
22
+ class Singly
23
+ def initialize
24
+ @head = nil
25
+ @size = 0
26
+ end
27
+
28
+ def push(val)
29
+ @head = Node.new(val, @head)
30
+ @size += 1
31
+ self
32
+ end
33
+
34
+ def append(val)
35
+ new_node = Node.new(val)
36
+ if @head.nil?
37
+ @head = new_node
38
+ else
39
+ node = @head
40
+ node = node.next while node.next
41
+ node.next = new_node
42
+ end
43
+ @size += 1
44
+ self
45
+ end
46
+
47
+ def pop
48
+ raise IndexError, "list is empty" unless @head
49
+
50
+ val = @head.val
51
+ @head = @head.next
52
+ @size -= 1
53
+ val
54
+ end
55
+
56
+ def peek
57
+ raise IndexError, "list is empty" unless @head
58
+ @head.val
59
+ end
60
+
61
+ def delete(val)
62
+ return false unless @head
63
+
64
+ if @head.val == val
65
+ @head = @head.next
66
+ @size -= 1
67
+ return true
68
+ end
69
+
70
+ node = @head
71
+ while node.next
72
+ if node.next.val == val
73
+ node.next = node.next.next
74
+ @size -= 1
75
+ return true
76
+ end
77
+ node = node.next
78
+ end
79
+
80
+ false
81
+ end
82
+
83
+ def find(val)
84
+ node = @head
85
+ while node
86
+ return node if node.val == val
87
+ node = node.next
88
+ end
89
+ nil
90
+ end
91
+
92
+ def reverse!
93
+ return self unless @head
94
+
95
+ prev = nil
96
+ curr = @head
97
+ while curr
98
+ nxt = curr.next
99
+ curr.next = prev
100
+ prev = curr
101
+ curr = nxt
102
+ end
103
+ @head = prev
104
+ self
105
+ end
106
+
107
+ def size
108
+ @size
109
+ end
110
+
111
+ def empty?
112
+ @size == 0
113
+ end
114
+
115
+ def to_a
116
+ result = []
117
+ node = @head
118
+ while node
119
+ result << node.val
120
+ node = node.next
121
+ end
122
+ result
123
+ end
124
+
125
+ include Enumerable
126
+
127
+ def each(&block)
128
+ return enum_for(:each) unless block_given?
129
+ node = @head
130
+ while node
131
+ yield node.val
132
+ node = node.next
133
+ end
134
+ end
135
+ end
136
+
137
+ class Doubly
138
+ def initialize
139
+ @head = nil
140
+ @tail = nil
141
+ @size = 0
142
+ end
143
+
144
+ def push_front(val)
145
+ node = DoublyNode.new(val, @head, nil)
146
+ @head.prev = node if @head
147
+ @head = node
148
+ @tail ||= node
149
+ @size += 1
150
+ self
151
+ end
152
+
153
+ def push_back(val)
154
+ node = DoublyNode.new(val, nil, @tail)
155
+ @tail.next = node if @tail
156
+ @tail = node
157
+ @head ||= node
158
+ @size += 1
159
+ self
160
+ end
161
+
162
+ def pop_front
163
+ raise IndexError, "list is empty" unless @head
164
+
165
+ val = @head.val
166
+ @head = @head.next
167
+ @head.prev = nil if @head
168
+ @tail = nil unless @head
169
+ @size -= 1
170
+ val
171
+ end
172
+
173
+ def pop_back
174
+ raise IndexError, "list is empty" unless @tail
175
+
176
+ val = @tail.val
177
+ @tail = @tail.prev
178
+ @tail.next = nil if @tail
179
+ @head = nil unless @tail
180
+ @size -= 1
181
+ val
182
+ end
183
+
184
+ def front
185
+ raise IndexError, "list is empty" unless @head
186
+ @head.val
187
+ end
188
+
189
+ def back
190
+ raise IndexError, "list is empty" unless @tail
191
+ @tail.val
192
+ end
193
+
194
+ def delete(val)
195
+ node = @head
196
+ while node
197
+ if node.val == val
198
+ node.prev.next = node.next if node.prev
199
+ node.next.prev = node.prev if node.next
200
+ @head = node.next if node == @head
201
+ @tail = node.prev if node == @tail
202
+ @size -= 1
203
+ return true
204
+ end
205
+ node = node.next
206
+ end
207
+ false
208
+ end
209
+
210
+ def find(val)
211
+ node = @head
212
+ while node
213
+ return node if node.val == val
214
+ node = node.next
215
+ end
216
+ nil
217
+ end
218
+
219
+ def size
220
+ @size
221
+ end
222
+
223
+ def empty?
224
+ @size == 0
225
+ end
226
+
227
+ def to_a
228
+ result = []
229
+ node = @head
230
+ while node
231
+ result << node.val
232
+ node = node.next
233
+ end
234
+ result
235
+ end
236
+
237
+ include Enumerable
238
+
239
+ def each(&block)
240
+ return enum_for(:each) unless block_given?
241
+ node = @head
242
+ while node
243
+ yield node.val
244
+ node = node.next
245
+ end
246
+ end
247
+ end
248
+ end
249
+ end
@@ -0,0 +1,43 @@
1
+ module DSA
2
+ class PriorityQueue
3
+ def initialize(type: :min)
4
+ @heap = type == :min ? MinHeap.new : MaxHeap.new
5
+ end
6
+
7
+ def push(item, priority)
8
+ @counter ||= 0
9
+ @heap.push([priority, @counter, item])
10
+ @counter += 1
11
+ self
12
+ end
13
+
14
+ def pop
15
+ raise IndexError, "priority queue is empty" if empty?
16
+ _, _, item = @heap.pop
17
+ item
18
+ end
19
+
20
+ def peek
21
+ raise IndexError, "priority queue is empty" if empty?
22
+ _, _, item = @heap.peek
23
+ item
24
+ end
25
+
26
+ def priority_peek
27
+ raise IndexError, "priority queue is empty" if empty?
28
+ @heap.peek[0]
29
+ end
30
+
31
+ def size
32
+ @heap.size
33
+ end
34
+
35
+ def empty?
36
+ @heap.empty?
37
+ end
38
+
39
+ private
40
+
41
+ attr_reader :heap
42
+ end
43
+ end
@@ -0,0 +1,34 @@
1
+ module DSA
2
+ class Queue
3
+ def initialize
4
+ @elements = []
5
+ end
6
+
7
+ def enqueue(val)
8
+ @elements.push(val)
9
+ self
10
+ end
11
+
12
+ def dequeue
13
+ raise IndexError, "queue is empty" if empty?
14
+ @elements.shift
15
+ end
16
+
17
+ def peek
18
+ raise IndexError, "queue is empty" if empty?
19
+ @elements.first
20
+ end
21
+
22
+ def size
23
+ @elements.size
24
+ end
25
+
26
+ def empty?
27
+ @elements.empty?
28
+ end
29
+
30
+ def to_a
31
+ @elements.dup
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,34 @@
1
+ module DSA
2
+ class Stack
3
+ def initialize
4
+ @elements = []
5
+ end
6
+
7
+ def push(val)
8
+ @elements.push(val)
9
+ self
10
+ end
11
+
12
+ def pop
13
+ raise IndexError, "stack is empty" if empty?
14
+ @elements.pop
15
+ end
16
+
17
+ def peek
18
+ raise IndexError, "stack is empty" if empty?
19
+ @elements.last
20
+ end
21
+
22
+ def size
23
+ @elements.size
24
+ end
25
+
26
+ def empty?
27
+ @elements.empty?
28
+ end
29
+
30
+ def to_a
31
+ @elements.dup
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,70 @@
1
+ module DSA
2
+ class Trie
3
+ Node = Struct.new(:children, :word_end, keyword_init: true) do
4
+ def initialize
5
+ super(children: {}, word_end: false)
6
+ end
7
+ end
8
+
9
+ def initialize
10
+ @root = Node.new
11
+ end
12
+
13
+ def insert(word)
14
+ node = @root
15
+ word.each_char do |char|
16
+ node.children[char] ||= Node.new
17
+ node = node.children[char]
18
+ end
19
+ node.word_end = true
20
+ self
21
+ end
22
+
23
+ def search(word)
24
+ node = find_node(word)
25
+ !!(node && node.word_end)
26
+ end
27
+
28
+ def starts_with(prefix)
29
+ !!find_node(prefix)
30
+ end
31
+
32
+ def delete(word)
33
+ delete_recursive(@root, word, 0)
34
+ end
35
+
36
+ private
37
+
38
+ def find_node(word)
39
+ node = @root
40
+ word.each_char do |char|
41
+ return nil unless node.children[char]
42
+ node = node.children[char]
43
+ end
44
+ node
45
+ end
46
+
47
+ def delete_recursive(node, word, index)
48
+ return false unless node
49
+
50
+ if index == word.length
51
+ return false unless node.word_end
52
+
53
+ node.word_end = false
54
+ return node.children.empty?
55
+ end
56
+
57
+ char = word[index]
58
+ return false unless node.children[char]
59
+
60
+ should_delete = delete_recursive(node.children[char], word, index + 1)
61
+
62
+ if should_delete
63
+ node.children.delete(char)
64
+ return !node.word_end && node.children.empty?
65
+ end
66
+
67
+ false
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,50 @@
1
+ module DSA
2
+ class UnionFind
3
+ def initialize(n)
4
+ @parent = (0...n).to_a
5
+ @rank = Array.new(n, 0)
6
+ @count = n
7
+ end
8
+
9
+ def find(x)
10
+ raise IndexError, "index out of bounds" if x < 0 || x >= @parent.size
11
+
12
+ @parent[x] = find(@parent[x]) if @parent[x] != x
13
+ @parent[x]
14
+ end
15
+
16
+ def union(x, y)
17
+ raise IndexError, "index out of bounds" if x < 0 || x >= @parent.size
18
+ raise IndexError, "index out of bounds" if y < 0 || y >= @parent.size
19
+
20
+ root_x = find(x)
21
+ root_y = find(y)
22
+
23
+ return false if root_x == root_y
24
+
25
+ if @rank[root_x] < @rank[root_y]
26
+ @parent[root_x] = root_y
27
+ elsif @rank[root_x] > @rank[root_y]
28
+ @parent[root_y] = root_x
29
+ else
30
+ @parent[root_y] = root_x
31
+ @rank[root_x] += 1
32
+ end
33
+
34
+ @count -= 1
35
+ true
36
+ end
37
+
38
+ def connected?(x, y)
39
+ find(x) == find(y)
40
+ end
41
+
42
+ def count
43
+ @count
44
+ end
45
+
46
+ def size
47
+ @parent.size
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,3 @@
1
+ module DSA
2
+ VERSION = "1.0.0"
3
+ end
data/lib/dsa-ruby.rb ADDED
@@ -0,0 +1,10 @@
1
+ require_relative "dsa-ruby/version"
2
+ require_relative "dsa-ruby/stack"
3
+ require_relative "dsa-ruby/queue"
4
+ require_relative "dsa-ruby/binary_search_tree"
5
+ require_relative "dsa-ruby/heap"
6
+ require_relative "dsa-ruby/priority_queue"
7
+ require_relative "dsa-ruby/deque"
8
+ require_relative "dsa-ruby/trie"
9
+ require_relative "dsa-ruby/union_find"
10
+ require_relative "dsa-ruby/linked_list"
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dsa-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - You
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rspec
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '3.0'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '3.0'
26
+ description: Provides MinHeap, MaxHeap, PriorityQueue, Deque, Trie, UnionFind, and
27
+ LinkedList — data structures commonly needed for coding interviews but missing from
28
+ Ruby's stdlib.
29
+ email:
30
+ - you@example.com
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - LICENSE.txt
36
+ - README.md
37
+ - lib/dsa-ruby.rb
38
+ - lib/dsa-ruby/binary_search_tree.rb
39
+ - lib/dsa-ruby/deque.rb
40
+ - lib/dsa-ruby/heap.rb
41
+ - lib/dsa-ruby/linked_list.rb
42
+ - lib/dsa-ruby/priority_queue.rb
43
+ - lib/dsa-ruby/queue.rb
44
+ - lib/dsa-ruby/stack.rb
45
+ - lib/dsa-ruby/trie.rb
46
+ - lib/dsa-ruby/union_find.rb
47
+ - lib/dsa-ruby/version.rb
48
+ homepage: https://github.com/you/dsa-ruby
49
+ licenses:
50
+ - MIT
51
+ metadata:
52
+ allowed_push_host: https://rubygems.org
53
+ rdoc_options: []
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: 3.0.0
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ requirements: []
67
+ rubygems_version: 4.0.9
68
+ specification_version: 4
69
+ summary: Data structures for coding interviews in Ruby
70
+ test_files: []