dsa-ruby 1.0.0 → 1.0.2
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 +4 -4
- data/README.md +9 -3
- data/lib/dsa-ruby/binary_search_tree.rb +258 -123
- data/lib/dsa-ruby/deque.rb +136 -55
- data/lib/dsa-ruby/heap.rb +235 -107
- data/lib/dsa-ruby/linked_list.rb +218 -0
- data/lib/dsa-ruby/priority_queue.rb +107 -41
- data/lib/dsa-ruby/queue.rb +72 -26
- data/lib/dsa-ruby/stack.rb +72 -26
- data/lib/dsa-ruby/trie.rb +109 -51
- data/lib/dsa-ruby/union_find.rb +93 -38
- data/lib/dsa-ruby/version.rb +8 -1
- metadata +15 -1
data/lib/dsa-ruby/stack.rb
CHANGED
|
@@ -1,34 +1,80 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
# DSA::Stack - Last-In-First-Out (LIFO) data structure.
|
|
2
|
+
#
|
|
3
|
+
# @example
|
|
4
|
+
# stack = DSA::Stack.new
|
|
5
|
+
# stack.push(1).push(2).push(3)
|
|
6
|
+
# stack.pop # => 3
|
|
7
|
+
# stack.peek # => 2
|
|
8
|
+
class DSA::Stack
|
|
9
|
+
# Initialize a new empty stack.
|
|
10
|
+
#
|
|
11
|
+
# @return [DSA::Stack] a new empty stack
|
|
12
|
+
def initialize
|
|
13
|
+
@elements = []
|
|
14
|
+
end
|
|
6
15
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
16
|
+
# Pushes a value onto the top of the stack.
|
|
17
|
+
#
|
|
18
|
+
# @param val [Object] the value to push onto the stack
|
|
19
|
+
# @return [DSA::Stack] self for method chaining
|
|
20
|
+
# @example Push with chaining
|
|
21
|
+
# stack.push(1).push(2).push(3)
|
|
22
|
+
def push(val)
|
|
23
|
+
@elements.push(val)
|
|
24
|
+
self
|
|
25
|
+
end
|
|
11
26
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
27
|
+
# Removes and returns the value at the top of the stack.
|
|
28
|
+
#
|
|
29
|
+
# @return [Object] the value at the top of the stack
|
|
30
|
+
# @raise [IndexError] if the stack is empty
|
|
31
|
+
# @example
|
|
32
|
+
# stack.push(1).push(2)
|
|
33
|
+
# stack.pop # => 2
|
|
34
|
+
def pop
|
|
35
|
+
raise IndexError, "stack is empty" if empty?
|
|
36
|
+
@elements.pop
|
|
37
|
+
end
|
|
16
38
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
39
|
+
# Returns the value at the top of the stack without removing it.
|
|
40
|
+
#
|
|
41
|
+
# @return [Object] the value at the top of the stack
|
|
42
|
+
# @raise [IndexError] if the stack is empty
|
|
43
|
+
# @example
|
|
44
|
+
# stack.push(1).push(2)
|
|
45
|
+
# stack.peek # => 2
|
|
46
|
+
def peek
|
|
47
|
+
raise IndexError, "stack is empty" if empty?
|
|
48
|
+
@elements.last
|
|
49
|
+
end
|
|
21
50
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
51
|
+
# Returns the number of elements in the stack.
|
|
52
|
+
#
|
|
53
|
+
# @return [Integer] the number of elements
|
|
54
|
+
# @example
|
|
55
|
+
# stack.push(1).push(2)
|
|
56
|
+
# stack.size # => 2
|
|
57
|
+
def size
|
|
58
|
+
@elements.size
|
|
59
|
+
end
|
|
25
60
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
61
|
+
# Checks if the stack is empty.
|
|
62
|
+
#
|
|
63
|
+
# @return [Boolean] true if the stack contains no elements
|
|
64
|
+
# @example
|
|
65
|
+
# stack = DSA::Stack.new
|
|
66
|
+
# stack.empty? # => true
|
|
67
|
+
def empty?
|
|
68
|
+
@elements.empty?
|
|
69
|
+
end
|
|
29
70
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
71
|
+
# Returns a defensive copy of the stack as an array.
|
|
72
|
+
#
|
|
73
|
+
# @return [Array] an array containing all elements in the stack (top to bottom order)
|
|
74
|
+
# @example
|
|
75
|
+
# stack.push(1).push(2).push(3)
|
|
76
|
+
# stack.to_a # => [3, 2, 1]
|
|
77
|
+
def to_a
|
|
78
|
+
@elements.dup
|
|
33
79
|
end
|
|
34
80
|
end
|
data/lib/dsa-ruby/trie.rb
CHANGED
|
@@ -1,70 +1,128 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
# DSA::Trie - A prefix tree (trie) data structure for string storage and lookup.
|
|
2
|
+
#
|
|
3
|
+
# Efficient for prefix-based searches and word lookups.
|
|
4
|
+
#
|
|
5
|
+
# @example
|
|
6
|
+
# trie = DSA::Trie.new
|
|
7
|
+
# trie.insert("apple")
|
|
8
|
+
# trie.search("apple") # => true
|
|
9
|
+
# trie.search("app") # => false
|
|
10
|
+
# trie.starts_with("app") # => true
|
|
11
|
+
# trie.delete("apple")
|
|
12
|
+
class DSA::Trie
|
|
13
|
+
# Internal node structure for the trie.
|
|
14
|
+
#
|
|
15
|
+
# @!attribute [rw] children
|
|
16
|
+
# @return [Hash] map of character to child node
|
|
17
|
+
# @!attribute [rw] word_end
|
|
18
|
+
# @return [Boolean] true if this node marks the end of a word
|
|
19
|
+
Node = Struct.new(:children, :word_end, keyword_init: true) do
|
|
9
20
|
def initialize
|
|
10
|
-
|
|
21
|
+
super(children: {}, word_end: false)
|
|
11
22
|
end
|
|
23
|
+
end
|
|
12
24
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
node.word_end = true
|
|
20
|
-
self
|
|
21
|
-
end
|
|
25
|
+
# Initialize a new empty trie.
|
|
26
|
+
#
|
|
27
|
+
# @return [DSA::Trie] a new empty trie
|
|
28
|
+
def initialize
|
|
29
|
+
@root = Node.new
|
|
30
|
+
end
|
|
22
31
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
32
|
+
# Inserts a word into the trie.
|
|
33
|
+
#
|
|
34
|
+
# @param word [String] the word to insert
|
|
35
|
+
# @return [DSA::Trie] self for method chaining
|
|
36
|
+
# @example
|
|
37
|
+
# trie.insert("apple").insert("banana")
|
|
38
|
+
def insert(word)
|
|
39
|
+
node = @root
|
|
40
|
+
word.each_char do |char|
|
|
41
|
+
node.children[char] ||= Node.new
|
|
42
|
+
node = node.children[char]
|
|
26
43
|
end
|
|
44
|
+
node.word_end = true
|
|
45
|
+
self
|
|
46
|
+
end
|
|
27
47
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
48
|
+
# Searches for an exact word in the trie.
|
|
49
|
+
#
|
|
50
|
+
# @param word [String] the word to search for
|
|
51
|
+
# @return [Boolean] true if the word exists in the trie
|
|
52
|
+
# @example
|
|
53
|
+
# trie.insert("apple")
|
|
54
|
+
# trie.search("apple") # => true
|
|
55
|
+
# trie.search("app") # => false
|
|
56
|
+
def search(word)
|
|
57
|
+
node = find_node(word)
|
|
58
|
+
!!(node && node.word_end)
|
|
59
|
+
end
|
|
31
60
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
61
|
+
# Checks if any word in the trie starts with the given prefix.
|
|
62
|
+
#
|
|
63
|
+
# @param prefix [String] the prefix to search for
|
|
64
|
+
# @return [Boolean] true if any word starts with the prefix
|
|
65
|
+
# @example
|
|
66
|
+
# trie.insert("apple")
|
|
67
|
+
# trie.starts_with("app") # => true
|
|
68
|
+
# trie.starts_with("ban") # => false
|
|
69
|
+
def starts_with(prefix)
|
|
70
|
+
!!find_node(prefix)
|
|
71
|
+
end
|
|
35
72
|
|
|
36
|
-
|
|
73
|
+
# Deletes a word from the trie if it exists.
|
|
74
|
+
#
|
|
75
|
+
# @param word [String] the word to delete
|
|
76
|
+
# @return [Boolean] true if the word was deleted, false if not found
|
|
77
|
+
# @example
|
|
78
|
+
# trie.insert("apple")
|
|
79
|
+
# trie.delete("apple") # => true
|
|
80
|
+
# trie.delete("banana") # => false
|
|
81
|
+
def delete(word)
|
|
82
|
+
delete_recursive(@root, word, 0)
|
|
83
|
+
end
|
|
37
84
|
|
|
38
|
-
|
|
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
|
|
85
|
+
private
|
|
46
86
|
|
|
47
|
-
|
|
48
|
-
|
|
87
|
+
# Finds the node corresponding to a given word/prefix.
|
|
88
|
+
#
|
|
89
|
+
# @param word [String] the word or prefix to find
|
|
90
|
+
# @return [Node, nil] the node if found, nil otherwise
|
|
91
|
+
def find_node(word)
|
|
92
|
+
node = @root
|
|
93
|
+
word.each_char do |char|
|
|
94
|
+
return nil unless node.children[char]
|
|
95
|
+
node = node.children[char]
|
|
96
|
+
end
|
|
97
|
+
node
|
|
98
|
+
end
|
|
49
99
|
|
|
50
|
-
|
|
51
|
-
|
|
100
|
+
# Recursively deletes a word from the trie.
|
|
101
|
+
#
|
|
102
|
+
# @param node [Node] the current node being examined
|
|
103
|
+
# @param word [String] the word to delete
|
|
104
|
+
# @param index [Integer] the current character index in the word
|
|
105
|
+
# @return [Boolean] true if the node should be deleted (no children and not word end)
|
|
106
|
+
def delete_recursive(node, word, index)
|
|
107
|
+
return false unless node
|
|
52
108
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
end
|
|
109
|
+
if index == word.length
|
|
110
|
+
return false unless node.word_end
|
|
56
111
|
|
|
57
|
-
|
|
58
|
-
return
|
|
112
|
+
node.word_end = false
|
|
113
|
+
return node.children.empty?
|
|
114
|
+
end
|
|
59
115
|
|
|
60
|
-
|
|
116
|
+
char = word[index]
|
|
117
|
+
return false unless node.children[char]
|
|
61
118
|
|
|
62
|
-
|
|
63
|
-
node.children.delete(char)
|
|
64
|
-
return !node.word_end && node.children.empty?
|
|
65
|
-
end
|
|
119
|
+
should_delete = delete_recursive(node.children[char], word, index + 1)
|
|
66
120
|
|
|
67
|
-
|
|
121
|
+
if should_delete
|
|
122
|
+
node.children.delete(char)
|
|
123
|
+
return !node.word_end && node.children.empty?
|
|
68
124
|
end
|
|
125
|
+
|
|
126
|
+
false
|
|
69
127
|
end
|
|
70
128
|
end
|
data/lib/dsa-ruby/union_find.rb
CHANGED
|
@@ -1,50 +1,105 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
# DSA::UnionFind - Disjoint-set data structure with path compression and union by rank.
|
|
2
|
+
#
|
|
3
|
+
# Tracks a set of elements partitioned into disjoint subsets. Supports efficient
|
|
4
|
+
# union and find operations.
|
|
5
|
+
#
|
|
6
|
+
# @example
|
|
7
|
+
# uf = DSA::UnionFind.new(10)
|
|
8
|
+
# uf.union(1, 2)
|
|
9
|
+
# uf.union(2, 3)
|
|
10
|
+
# uf.connected?(1, 3) # => true
|
|
11
|
+
# uf.count # => 8 (started with 10, merged 2 pairs)
|
|
12
|
+
class DSA::UnionFind
|
|
13
|
+
# Initialize a new UnionFind structure with n disjoint sets.
|
|
14
|
+
#
|
|
15
|
+
# @param n [Integer] the number of elements (0 to n-1), each in its own set
|
|
16
|
+
# @return [DSA::UnionFind] a new UnionFind structure
|
|
17
|
+
# @example
|
|
18
|
+
# uf = DSA::UnionFind.new(10)
|
|
19
|
+
def initialize(n)
|
|
20
|
+
@parent = (0...n).to_a
|
|
21
|
+
@rank = Array.new(n, 0)
|
|
22
|
+
@count = n
|
|
23
|
+
end
|
|
11
24
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
25
|
+
# Finds the root of the set containing element x, with path compression.
|
|
26
|
+
#
|
|
27
|
+
# @param x [Integer] the element to find
|
|
28
|
+
# @return [Integer] the root of the set containing x
|
|
29
|
+
# @raise [IndexError] if x is out of bounds
|
|
30
|
+
# @example
|
|
31
|
+
# uf.union(1, 2)
|
|
32
|
+
# uf.find(2) # => root of the set containing 1 and 2
|
|
33
|
+
def find(x)
|
|
34
|
+
raise IndexError, "index out of bounds" if x < 0 || x >= @parent.size
|
|
15
35
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
36
|
+
@parent[x] = find(@parent[x]) if @parent[x] != x
|
|
37
|
+
@parent[x]
|
|
38
|
+
end
|
|
19
39
|
|
|
20
|
-
|
|
21
|
-
|
|
40
|
+
# Merges the sets containing elements x and y.
|
|
41
|
+
#
|
|
42
|
+
# @param x [Integer] the first element
|
|
43
|
+
# @param y [Integer] the second element
|
|
44
|
+
# @return [Boolean] true if the sets were merged, false if already connected
|
|
45
|
+
# @raise [IndexError] if x or y is out of bounds
|
|
46
|
+
# @example
|
|
47
|
+
# uf.union(1, 2) # => true
|
|
48
|
+
# uf.union(2, 3) # => true
|
|
49
|
+
# uf.union(1, 3) # => false (already connected)
|
|
50
|
+
def union(x, y)
|
|
51
|
+
raise IndexError, "index out of bounds" if x < 0 || x >= @parent.size
|
|
52
|
+
raise IndexError, "index out of bounds" if y < 0 || y >= @parent.size
|
|
22
53
|
|
|
23
|
-
|
|
54
|
+
root_x = find(x)
|
|
55
|
+
root_y = find(y)
|
|
24
56
|
|
|
25
|
-
|
|
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
|
|
57
|
+
return false if root_x == root_y
|
|
33
58
|
|
|
34
|
-
|
|
35
|
-
|
|
59
|
+
if @rank[root_x] < @rank[root_y]
|
|
60
|
+
@parent[root_x] = root_y
|
|
61
|
+
elsif @rank[root_x] > @rank[root_y]
|
|
62
|
+
@parent[root_y] = root_x
|
|
63
|
+
else
|
|
64
|
+
@parent[root_y] = root_x
|
|
65
|
+
@rank[root_x] += 1
|
|
36
66
|
end
|
|
37
67
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
68
|
+
@count -= 1
|
|
69
|
+
true
|
|
70
|
+
end
|
|
41
71
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
72
|
+
# Checks if two elements are in the same set.
|
|
73
|
+
#
|
|
74
|
+
# @param x [Integer] the first element
|
|
75
|
+
# @param y [Integer] the second element
|
|
76
|
+
# @return [Boolean] true if x and y are in the same set
|
|
77
|
+
# @example
|
|
78
|
+
# uf.union(1, 2)
|
|
79
|
+
# uf.connected?(1, 2) # => true
|
|
80
|
+
# uf.connected?(1, 3) # => false
|
|
81
|
+
def connected?(x, y)
|
|
82
|
+
find(x) == find(y)
|
|
83
|
+
end
|
|
45
84
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
85
|
+
# Returns the number of disjoint sets.
|
|
86
|
+
#
|
|
87
|
+
# @return [Integer] the number of disjoint sets
|
|
88
|
+
# @example
|
|
89
|
+
# uf = DSA::UnionFind.new(10)
|
|
90
|
+
# uf.union(1, 2)
|
|
91
|
+
# uf.count # => 9
|
|
92
|
+
def count
|
|
93
|
+
@count
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Returns the total number of elements.
|
|
97
|
+
#
|
|
98
|
+
# @return [Integer] the number of elements (0 to n-1)
|
|
99
|
+
# @example
|
|
100
|
+
# uf = DSA::UnionFind.new(10)
|
|
101
|
+
# uf.size # => 10
|
|
102
|
+
def size
|
|
103
|
+
@parent.size
|
|
49
104
|
end
|
|
50
105
|
end
|
data/lib/dsa-ruby/version.rb
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# DSA module - Data Structures for Algorithms in Ruby
|
|
2
|
+
#
|
|
3
|
+
# Provides common data structures needed for coding interviews and algorithm
|
|
4
|
+
# problems that are missing from Ruby's standard library.
|
|
1
5
|
module DSA
|
|
2
|
-
|
|
6
|
+
# Current version of the dsa-ruby gem.
|
|
7
|
+
#
|
|
8
|
+
# @return [String] the version string (semantic versioning)
|
|
9
|
+
VERSION = "1.0.2"
|
|
3
10
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dsa-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- You
|
|
@@ -23,6 +23,20 @@ dependencies:
|
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '3.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: yard
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0.9'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0.9'
|
|
26
40
|
description: Provides MinHeap, MaxHeap, PriorityQueue, Deque, Trie, UnionFind, and
|
|
27
41
|
LinkedList — data structures commonly needed for coding interviews but missing from
|
|
28
42
|
Ruby's stdlib.
|