skn_utils 3.2.1 → 3.3.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 +4 -4
- data/README.md +52 -11
- data/lib/skn_utils/lists/circular_linked_list.rb +217 -0
- data/lib/skn_utils/lists/doubly_linked_list.rb +215 -0
- data/lib/skn_utils/lists/link_node.rb +59 -0
- data/lib/skn_utils/lists/linked_list.rb +199 -0
- data/lib/skn_utils/notifier_base.rb +5 -5
- data/lib/skn_utils/version.rb +2 -2
- data/lib/skn_utils.rb +4 -0
- data/skn_utils.gemspec +1 -8
- data/spec/lib/skn_utils/lists/Circular_linked_list_spec.rb +242 -0
- data/spec/lib/skn_utils/lists/doubly_linked_list_spec.rb +242 -0
- data/spec/lib/skn_utils/lists/linked_list_spec.rb +227 -0
- metadata +15 -8
@@ -0,0 +1,199 @@
|
|
1
|
+
|
2
|
+
module SknUtils
|
3
|
+
module Lists
|
4
|
+
# Singly Linked List
|
5
|
+
# Forward or #next navigation only
|
6
|
+
# Head is absolute via #first
|
7
|
+
# Tail when (next == nil)
|
8
|
+
class LinkedList
|
9
|
+
attr_accessor :size
|
10
|
+
|
11
|
+
def initialize(*values)
|
12
|
+
@current = nil
|
13
|
+
@head = nil
|
14
|
+
@tail = nil
|
15
|
+
@size = 0
|
16
|
+
values.each {|value| insert(value) }
|
17
|
+
end
|
18
|
+
|
19
|
+
#
|
20
|
+
# Navigation
|
21
|
+
#
|
22
|
+
|
23
|
+
# return values and position current to last node accessed
|
24
|
+
def first
|
25
|
+
@current = head if head
|
26
|
+
@current.value rescue nil
|
27
|
+
end
|
28
|
+
|
29
|
+
def next
|
30
|
+
@current = @current.next if @current and @current.next
|
31
|
+
@current.value rescue nil
|
32
|
+
end
|
33
|
+
|
34
|
+
def current
|
35
|
+
@current.value rescue nil
|
36
|
+
end
|
37
|
+
|
38
|
+
def last
|
39
|
+
@current = tail if tail
|
40
|
+
@current.value rescue nil
|
41
|
+
end
|
42
|
+
|
43
|
+
# -+ int position from current node
|
44
|
+
def nth(index)
|
45
|
+
node = @current
|
46
|
+
while index > 1 and node and node.next
|
47
|
+
node = node.next
|
48
|
+
index -= 1
|
49
|
+
@current = node
|
50
|
+
end
|
51
|
+
# no reverse or prev for Single List
|
52
|
+
current
|
53
|
+
rescue NoMethodError
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
|
57
|
+
# return node at positive index from head
|
58
|
+
def at_index(index)
|
59
|
+
find_by_index(index)
|
60
|
+
current
|
61
|
+
end
|
62
|
+
|
63
|
+
def empty?
|
64
|
+
size == 0
|
65
|
+
end
|
66
|
+
|
67
|
+
#
|
68
|
+
# Modifications
|
69
|
+
#
|
70
|
+
|
71
|
+
# return new size
|
72
|
+
def insert(value)
|
73
|
+
temp = @current.value rescue nil
|
74
|
+
insert_after(temp, value)
|
75
|
+
end
|
76
|
+
|
77
|
+
# return new size
|
78
|
+
def prepend(value)
|
79
|
+
temp = head.value rescue nil
|
80
|
+
insert_before(temp, value)
|
81
|
+
end
|
82
|
+
|
83
|
+
# return new size
|
84
|
+
def append(value)
|
85
|
+
temp = tail.value rescue nil
|
86
|
+
insert_after(temp, value)
|
87
|
+
end
|
88
|
+
|
89
|
+
# return new size
|
90
|
+
def insert_before(position_value, value)
|
91
|
+
prior, target = find_by_value(position_value)
|
92
|
+
node = LinkNode.new(value, prior, :single)
|
93
|
+
node.next = target if target
|
94
|
+
self.head = node if head === target
|
95
|
+
self.tail = node if tail.nil?
|
96
|
+
@current = node
|
97
|
+
self.size += 1
|
98
|
+
end
|
99
|
+
|
100
|
+
# return new size
|
101
|
+
def insert_after(position_value, value)
|
102
|
+
prior, target = find_by_value(position_value)
|
103
|
+
node = LinkNode.new(value, target, :single)
|
104
|
+
self.head = node if head.nil?
|
105
|
+
self.tail = node if tail === target
|
106
|
+
@current = node
|
107
|
+
self.size += 1
|
108
|
+
end
|
109
|
+
|
110
|
+
# return remaining size
|
111
|
+
def remove(value)
|
112
|
+
@current, target_node = find_by_value(value)
|
113
|
+
@current.next = target_node.remove!
|
114
|
+
tail = @current.next if tail === target_node
|
115
|
+
head = @current if head === target_node
|
116
|
+
self.size -= 1
|
117
|
+
end
|
118
|
+
|
119
|
+
# return number cleared
|
120
|
+
def clear
|
121
|
+
rc = 0
|
122
|
+
node = head
|
123
|
+
position = head
|
124
|
+
while node do
|
125
|
+
node = node.remove!
|
126
|
+
rc += 1
|
127
|
+
break if position === node
|
128
|
+
end
|
129
|
+
|
130
|
+
@current = nil
|
131
|
+
self.head = nil
|
132
|
+
self.tail = nil
|
133
|
+
self.size = 0
|
134
|
+
rc
|
135
|
+
end
|
136
|
+
|
137
|
+
#
|
138
|
+
# Enumerate
|
139
|
+
#
|
140
|
+
|
141
|
+
# perform each() or return enumerator
|
142
|
+
def each(&block)
|
143
|
+
@current = head
|
144
|
+
position = head
|
145
|
+
if block_given?
|
146
|
+
while position do
|
147
|
+
yield position.value.dup
|
148
|
+
position = position.next
|
149
|
+
end
|
150
|
+
else
|
151
|
+
Enumerator.new do |yielder, val|
|
152
|
+
while position do
|
153
|
+
yielder << position.value.dup
|
154
|
+
position = position.next
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# convert self to a value array
|
161
|
+
def to_a
|
162
|
+
@current = head
|
163
|
+
position = head
|
164
|
+
result = []
|
165
|
+
while position do
|
166
|
+
result << position.value.dup
|
167
|
+
position = position.next
|
168
|
+
break if position === @current
|
169
|
+
end
|
170
|
+
result
|
171
|
+
end
|
172
|
+
|
173
|
+
private
|
174
|
+
|
175
|
+
attr_accessor :head, :tail
|
176
|
+
|
177
|
+
def find_by_value(value)
|
178
|
+
return [nil,nil] if head.nil?
|
179
|
+
prior = head
|
180
|
+
target = prior
|
181
|
+
while not target.match_by_value(value)
|
182
|
+
prior = target
|
183
|
+
target = prior.next
|
184
|
+
@current = target if target
|
185
|
+
end
|
186
|
+
[prior, target]
|
187
|
+
end
|
188
|
+
|
189
|
+
def find_by_index(index)
|
190
|
+
return nil if head.nil? or index < 1 or index > size
|
191
|
+
node = head
|
192
|
+
node = node.next while ((index -= 1) > 0 and node.next)
|
193
|
+
@current = node if node
|
194
|
+
node
|
195
|
+
end
|
196
|
+
|
197
|
+
end # end class
|
198
|
+
end # end module
|
199
|
+
end # end module
|
@@ -23,16 +23,16 @@ module SknUtils
|
|
23
23
|
def self.attribute(*attrs)
|
24
24
|
attrs.each do |attr|
|
25
25
|
instance_variable_set("@#{attr}", nil)
|
26
|
-
define_method(attr)
|
26
|
+
define_method(attr) do
|
27
27
|
instance_variable_get("@#{attr}")
|
28
|
-
|
28
|
+
end
|
29
29
|
define_method("#{attr}=") do |value|
|
30
30
|
old_value = instance_variable_get("@#{attr}")
|
31
31
|
return if (value == old_value)
|
32
|
-
@listeners.each { |listener|
|
33
|
-
listener.attribute_changed(attr, old_value, value)
|
34
|
-
}
|
35
32
|
instance_variable_set("@#{attr}", value)
|
33
|
+
@listeners.each do |listener|
|
34
|
+
listener.attribute_changed(attr, old_value, value)
|
35
|
+
end
|
36
36
|
end
|
37
37
|
end # loop on attrs
|
38
38
|
end # end of attribute method
|
data/lib/skn_utils/version.rb
CHANGED
data/lib/skn_utils.rb
CHANGED
@@ -5,6 +5,10 @@ require 'skn_utils/page_controls'
|
|
5
5
|
require 'skn_utils/null_object'
|
6
6
|
require 'skn_utils/notifier_base'
|
7
7
|
require 'skn_utils/skn_configuration'
|
8
|
+
require 'skn_utils/lists/link_node'
|
9
|
+
require 'skn_utils/lists/linked_list'
|
10
|
+
require 'skn_utils/lists/doubly_linked_list'
|
11
|
+
require 'skn_utils/lists/circular_linked_list'
|
8
12
|
require 'skn_utils/exploring/commander'
|
9
13
|
require 'skn_utils/exploring/action_service'
|
10
14
|
require 'psych'
|
data/skn_utils.gemspec
CHANGED
@@ -13,7 +13,7 @@ SknUtils contains a small collection of Ruby utilities, the first being a Nested
|
|
13
13
|
EOF
|
14
14
|
|
15
15
|
spec.description = <<-EOF
|
16
|
-
The intent of the NestedResult class is to be a container
|
16
|
+
The intent of the NestedResult class is to be a container for data values composed of key/value pairs,
|
17
17
|
with easy access to its contents, and on-demand transformation back to the hash (#to_hash).
|
18
18
|
|
19
19
|
Review the RSpec tests, and or review the README for more details.
|
@@ -24,13 +24,6 @@ SknUtils::NestedResult class. SknUtils::NestedResult replaces those original cl
|
|
24
24
|
|
25
25
|
Please update your existing code in consideration of the above change, or use the prior version 2.0.6.
|
26
26
|
|
27
|
-
ATTENTION: ****************************************************************
|
28
|
-
This version may require the following be added to your Rails Application 'Gemfile',
|
29
|
-
if you are using the SknSettings configuration class.
|
30
|
-
|
31
|
-
gem 'deep_merge', '~> 1.1'
|
32
|
-
|
33
|
-
************************************************************************
|
34
27
|
EOF
|
35
28
|
spec.homepage = "https://github.com/skoona/skn_utils"
|
36
29
|
spec.license = "MIT"
|
@@ -0,0 +1,242 @@
|
|
1
|
+
##
|
2
|
+
# spec/lib/skn_utils/circular_linked_list_spec.rb
|
3
|
+
#
|
4
|
+
|
5
|
+
RSpec.describe SknUtils::Lists::CircularLinkedList, "Circular LinkedList " do
|
6
|
+
|
7
|
+
context "Initialization" do
|
8
|
+
it "can be initialized without params" do
|
9
|
+
expect(subject).to be
|
10
|
+
end
|
11
|
+
it "can insert the first value" do
|
12
|
+
expect(subject.empty?).to be true
|
13
|
+
expect(subject.insert(101)).to eq(1)
|
14
|
+
end
|
15
|
+
it "can be cleared" do
|
16
|
+
subject.insert(101)
|
17
|
+
expect(subject.clear).to eq(1)
|
18
|
+
end
|
19
|
+
it "can be initialized with one or more initial values" do
|
20
|
+
list = described_class.new(10,100,100)
|
21
|
+
expect(list.current).to eq(100)
|
22
|
+
end
|
23
|
+
it "is initially empty?" do
|
24
|
+
expect(subject.empty?).to be true
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "Navigation" do
|
29
|
+
let(:list) { described_class.new(10,20, 30, 40, 50, 60, 70, 80, 90, 100) }
|
30
|
+
|
31
|
+
it "#first returns the first value" do
|
32
|
+
expect(list.first).to eq(10)
|
33
|
+
end
|
34
|
+
it "#next returns the second value" do
|
35
|
+
expect(list.first).to eq(10)
|
36
|
+
expect(list.next).to eq(20)
|
37
|
+
end
|
38
|
+
it "#current returns the last value as a side-effect of initialization via new" do
|
39
|
+
expect(list.current).to eq(100)
|
40
|
+
end
|
41
|
+
it "#prev returns the prior value" do
|
42
|
+
expect(list.prev).to eq(90)
|
43
|
+
end
|
44
|
+
it "#last returns the last value" do
|
45
|
+
expect(list.last).to eq(100)
|
46
|
+
end
|
47
|
+
it "#nth(6) returns the sixth value" do
|
48
|
+
expect(list.first).to eq(10)
|
49
|
+
expect(list.nth(6)).to eq(60)
|
50
|
+
expect(list.nth(-2)).to eq(40)
|
51
|
+
end
|
52
|
+
it "#at_index(6) returns the sixth value" do
|
53
|
+
expect(list.at_index(6)).to eq(60)
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
context "Insertions" do
|
58
|
+
it "#insert(value) indicates a value was added" do
|
59
|
+
bsize = subject.size
|
60
|
+
expect(subject.insert(110)).to eq(bsize + 1)
|
61
|
+
end
|
62
|
+
it "#prepend(value) indicates a value was added" do
|
63
|
+
bsize = subject.size
|
64
|
+
expect(subject.prepend(110)).to eq(bsize + 1)
|
65
|
+
end
|
66
|
+
it "#append(value) indicates a value was added" do
|
67
|
+
bsize = subject.size
|
68
|
+
expect(subject.append(110)).to eq(bsize + 1)
|
69
|
+
end
|
70
|
+
it "#insert_before(pvalue,value) indicates a value was added" do
|
71
|
+
subject.insert(120)
|
72
|
+
bsize = subject.size
|
73
|
+
expect(subject.insert_before(120, 110)).to eq(bsize + 1)
|
74
|
+
expect(subject.to_a).to eq([110,120])
|
75
|
+
end
|
76
|
+
it "#insert_after(value) indicates a value was added" do
|
77
|
+
subject.insert(120)
|
78
|
+
bsize = subject.size
|
79
|
+
expect(subject.insert_after(120, 125)).to eq(bsize + 1)
|
80
|
+
expect(subject.to_a).to eq([120,125])
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context "Removals" do
|
85
|
+
let(:list) { described_class.new(10,20, 30, 40, 50, 60, 70, 80, 90, 100) }
|
86
|
+
|
87
|
+
it "#remove(value) removes first occurance of that value" do
|
88
|
+
bsize = list.size
|
89
|
+
expect(list.remove(30)).to eq(bsize - 1)
|
90
|
+
expect(list.to_a).to eq([10,20, 40, 50, 60, 70, 80, 90, 100])
|
91
|
+
end
|
92
|
+
|
93
|
+
it "#clear removes all elements from list" do
|
94
|
+
expect(list.clear).to eq(10)
|
95
|
+
expect(list.empty?).to be true
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
context "Enumeration" do
|
100
|
+
let(:list) { described_class.new(10,20, 30, 40, 50, 60, 70, 80, 90, 100) }
|
101
|
+
it "#each works as expected when block is provided" do
|
102
|
+
x = []
|
103
|
+
list.each {|r| x << r}
|
104
|
+
expect(x).to be_a(Array)
|
105
|
+
expect(x).to eq([10,20, 30, 40, 50, 60, 70, 80, 90, 100])
|
106
|
+
end
|
107
|
+
it "#each works as expected when no block is offered" do
|
108
|
+
expect(list.each).to be_a(Enumerator)
|
109
|
+
expect(list.each.first).to eq(10)
|
110
|
+
end
|
111
|
+
it "#to_a returns the contents of linkedlist as an Array" do
|
112
|
+
base = list.to_a
|
113
|
+
expect(base).to be_a(Array)
|
114
|
+
expect(base).to eq([10,20, 30, 40, 50, 60, 70, 80, 90, 100])
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
context "Edge cases " do
|
119
|
+
let(:list) { described_class.new(10,20, 30, 40, 50, 60, 70, 80, 90, 100) }
|
120
|
+
|
121
|
+
it "#at_index(-999) fails and returns the current element. " do
|
122
|
+
expect(list.at_index(-999)).to eq(100)
|
123
|
+
end
|
124
|
+
it "#at_index(0) fails and returns the current element. " do
|
125
|
+
expect(list.at_index(0)).to eq(100)
|
126
|
+
end
|
127
|
+
it "#at_index(999) fails and returns the current element. " do
|
128
|
+
expect(list.at_index(999)).to eq(100)
|
129
|
+
end
|
130
|
+
it "#at_index(n) returns the proper element. " do
|
131
|
+
expect(list.at_index(1)).to eq(10)
|
132
|
+
expect(list.at_index(list.size / 2)).to eq(50)
|
133
|
+
expect(list.at_index(list.size)).to eq(100)
|
134
|
+
end
|
135
|
+
it "#at_index(n) returns the proper element for linkedlist with one element. " do
|
136
|
+
only = described_class.new(55)
|
137
|
+
expect(only.at_index(1)).to eq(55)
|
138
|
+
expect(only.at_index(10)).to eq(55)
|
139
|
+
expect(only.at_index(-10)).to eq(55)
|
140
|
+
end
|
141
|
+
|
142
|
+
it "#nth(-999) returns wraps to starting place, or last initialization value." do
|
143
|
+
expect(list.nth(-999)).to eq(100)
|
144
|
+
end
|
145
|
+
it "#nth(0) returns current value, or last initialization value." do
|
146
|
+
expect(list.nth(0)).to eq(100)
|
147
|
+
end
|
148
|
+
it "#nth(999) returns last initialization value." do
|
149
|
+
expect(list.nth(999)).to eq(100)
|
150
|
+
end
|
151
|
+
it "#current equals last initialization value." do
|
152
|
+
expect(list.current).to eq(100)
|
153
|
+
end
|
154
|
+
it "#next after initialization wraps to first initialization value. " do
|
155
|
+
expect(list.next).to eq(10)
|
156
|
+
expect(list.next).to eq(20)
|
157
|
+
expect(list.next).to eq(30)
|
158
|
+
end
|
159
|
+
it "#prev after first returns proper sequence of values. " do
|
160
|
+
expect(list.first).to eq(10)
|
161
|
+
expect(list.prev).to eq(100)
|
162
|
+
expect(list.prev).to eq(90)
|
163
|
+
end
|
164
|
+
it "#first, #next, #current, #prev, #nth, and #last return same value after initialization with one value. " do
|
165
|
+
only = described_class.new(55)
|
166
|
+
expect(only.first).to eq(55)
|
167
|
+
expect(only.next).to eq(55)
|
168
|
+
expect(only.prev).to eq(55)
|
169
|
+
expect(only.last).to eq(55)
|
170
|
+
expect(only.current).to eq(55)
|
171
|
+
expect(only.nth(1)).to eq(55)
|
172
|
+
expect(only.nth(11)).to eq(55)
|
173
|
+
end
|
174
|
+
it "#first, #next, #current, #prev, #nth, and #last return same value after initialization with no values. " do
|
175
|
+
only = described_class.new
|
176
|
+
expect(only.first).to be nil
|
177
|
+
expect(only.next).to be nil
|
178
|
+
expect(only.prev).to be nil
|
179
|
+
expect(only.last).to be nil
|
180
|
+
expect(only.current).to be nil
|
181
|
+
expect(only.nth(1)).to be nil
|
182
|
+
expect(only.nth(-1)).to be nil
|
183
|
+
end
|
184
|
+
it "#prepend enables navigation methods normal operations. " do
|
185
|
+
only = described_class.new
|
186
|
+
only.prepend(55)
|
187
|
+
expect(only.first).to eq(55)
|
188
|
+
expect(only.next).to eq(55)
|
189
|
+
expect(only.prev).to eq(55)
|
190
|
+
expect(only.last).to eq(55)
|
191
|
+
expect(only.current).to eq(55)
|
192
|
+
expect(only.nth(1)).to eq(55)
|
193
|
+
expect(only.nth(11)).to eq(55)
|
194
|
+
end
|
195
|
+
it "#append enables navigation methods normal operations. " do
|
196
|
+
only = described_class.new
|
197
|
+
only.append(55)
|
198
|
+
expect(only.first).to eq(55)
|
199
|
+
expect(only.next).to eq(55)
|
200
|
+
expect(only.prev).to eq(55)
|
201
|
+
expect(only.last).to eq(55)
|
202
|
+
expect(only.current).to eq(55)
|
203
|
+
expect(only.nth(1)).to eq(55)
|
204
|
+
expect(only.nth(11)).to eq(55)
|
205
|
+
end
|
206
|
+
it "#insert_before enables navigation methods normal operations. " do
|
207
|
+
only = described_class.new
|
208
|
+
only.insert_before(nil, 55)
|
209
|
+
expect(only.first).to eq(55)
|
210
|
+
expect(only.next).to eq(55)
|
211
|
+
expect(only.prev).to eq(55)
|
212
|
+
expect(only.last).to eq(55)
|
213
|
+
expect(only.current).to eq(55)
|
214
|
+
expect(only.nth(1)).to eq(55)
|
215
|
+
expect(only.nth(11)).to eq(55)
|
216
|
+
end
|
217
|
+
it "#insert_after enables navigation methods normal operations. " do
|
218
|
+
only = described_class.new
|
219
|
+
only.insert_after(nil, 55)
|
220
|
+
expect(only.first).to eq(55)
|
221
|
+
expect(only.next).to eq(55)
|
222
|
+
expect(only.prev).to eq(55)
|
223
|
+
expect(only.last).to eq(55)
|
224
|
+
expect(only.current).to eq(55)
|
225
|
+
expect(only.nth(1)).to eq(55)
|
226
|
+
expect(only.nth(11)).to eq(55)
|
227
|
+
end
|
228
|
+
it "#remove does not make navigation methods unstable if only element. " do
|
229
|
+
only = described_class.new(55)
|
230
|
+
only.remove(55)
|
231
|
+
expect(only.first).to be nil
|
232
|
+
expect(only.next).to be nil
|
233
|
+
expect(only.prev).to be nil
|
234
|
+
expect(only.last).to be nil
|
235
|
+
expect(only.current).to be nil
|
236
|
+
expect(only.nth(1)).to be nil
|
237
|
+
expect(only.nth(-1)).to be nil
|
238
|
+
end
|
239
|
+
|
240
|
+
end
|
241
|
+
|
242
|
+
end
|