skn_utils 3.3.5 → 3.3.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 84cb7bcd483e3369c94109704c491658263a497d
4
- data.tar.gz: 0fefb9e19e864638acfeaa605dd315e12db33a0c
3
+ metadata.gz: 1fd1ca93fc5fbcbdf6d22f5c45851fcd36e19e88
4
+ data.tar.gz: 0aeaade9f44e5a69c701946d24542c9c88f2995e
5
5
  SHA512:
6
- metadata.gz: 66f1414ed28fae1ab9e2701c6d21590e9ea463303964c23de9ffd0f3eabe5df701bc18bb6cceb9c98752d353038d79bbffb97909d1dd447c683eeafc88c9cb12
7
- data.tar.gz: 3552bd034096bc4c60f339082af9a22f09bfe57e4c53b589507306e3e190e7598a5b765da62f7ad561fa5270df14652f6272d9d939359c32901cc10802c54abe
6
+ metadata.gz: 5a934d2d35f6680f15b923df769e3da95795d122317fe15bb49b26aa5917b077c7fa81040b02ef637a26d4bef15791cc447538c663c187b83681e5c698dc6627
7
+ data.tar.gz: a05eae70e269463828a96e6c8e714cd214bacb5bf1256caee2220cdf1c62d634bc58a61e7f42f556e08c2a4254ae27547149186cbf3cd632b85ca871b1c6b1da
data/README.md CHANGED
@@ -17,8 +17,8 @@ Ruby Gem containing a Ruby PORO (Plain Old Ruby Object) that can be instantiated
17
17
 
18
18
  ## New Features
19
19
  08/2017 V3.3.0
20
- Added Linked List classes which implement value based Singular, Doubly, and Circular linked lists patterns. Value based implies
21
- the internal nodes of a linked list are never returned through the public interface, only node contents or values.
20
+ Added Linked List classes which implement Single, Double, and Circular linked lists patterns. LinkedLists are implemented
21
+ with method results returning node values or the nodes themselves.
22
22
 
23
23
  07/2017 V3.1.5
24
24
  Added SknSettings class for use as a replacement to the popular, but obsolete, Config.gem
@@ -70,9 +70,13 @@ Ruby Gem containing a Ruby PORO (Plain Old Ruby Object) that can be instantiated
70
70
 
71
71
 
72
72
  ## Public Methods: SknUtils::Lists::LinkedList, SknUtils::Lists::DoublyLinkedList, and SknUtils::Lists::CircularLinkedList
73
- Each concrete Class supports the following methods:
74
-
73
+ #### Each concrete Class supports the following methods: Value based interface
74
+ Value based interface presumes a direct reference to the object is maintained and the following methods will be called on that
75
+ object instance as needed. Each method will generally return the value contained in the node, Nil, or the int number of nodes
76
+ remaining.
77
+
75
78
  Navigation: return related value from relative positon in list, stops on first/last node for Single/Double, wraps for Circular.
79
+
76
80
  #first -- returns first value in list
77
81
  #next -- returns next value from current position
78
82
  #current -- returns current value
@@ -94,6 +98,7 @@ Ruby Gem containing a Ruby PORO (Plain Old Ruby Object) that can be instantiated
94
98
  block format is: {|a,b| a >= b }; example: 'll.sort(:default) {|a,b| a <= b}'
95
99
 
96
100
  Modification: returns number of elements in the list after the operation
101
+
97
102
  #insert(value) -- inserts value after node at current positon, or appends
98
103
  #append(value) -- inserts value after node at current positon
99
104
  #prepend(value) -- inserts value before node at current positon
@@ -102,9 +107,36 @@ Ruby Gem containing a Ruby PORO (Plain Old Ruby Object) that can be instantiated
102
107
  #remove(value) -- finds first node matching value, then destroys it
103
108
 
104
109
  Initialization: optional &block to identify data key
110
+
105
111
  #new(*vargs, &block) -- Instansiates new list and optionally creates nodes from each comma-seperated value;
106
112
  also, assigns &block as default value identifier for find and sort operations
107
- compare_key_block example: LinkedList.new({:key=>"Z"},{:key=>"S"},{:key=>"N"}) {|a| a[:key]}
113
+ returns a class instance.
114
+ compare_key_block example: instance = LinkedList.new({:key=>"Z"},{:key=>"S"},{:key=>"N"}) {|a| a[:key]}
115
+
116
+ #### Each concrete Class supports the following methods: Node based interface
117
+ Node based interface presumes a node is retrieved from the LinkedList, then using that/any available node all other methods
118
+ may be used. Methods in play will include the LinkedNode (#next, #value, and #prev), along with all public methods from
119
+ the main class.
120
+
121
+ Navigation: return related Node from relative positon in list, stops on first/last node for Single/Double, wraps for Circular.
122
+
123
+ #first_node -- returns first node
124
+ #next_node -- returns next node from current position
125
+ #current_node -- returns current or last accessed node
126
+ #prev_node -- returns previous node from current position (*not supported in Single)
127
+ #last_node -- returns last node in list
128
+ #node_value -- returns value of the current/receiver node: $ receiver.node_value
129
+ #node_request(method_sym, *vargs, &block)
130
+ -- executes any method on the Value based Interface, returning a node
131
+ #node_value_request(method_sym, *vargs, &block)
132
+ -- executes any method on the Value based Interface, returning result value
133
+
134
+ Initialization: optional &block to identify data key
135
+
136
+ #call(*vargs, &block) -- Instansiates new list and optionally creates nodes from each comma-seperated value;
137
+ also, assigns &block as default value identifier for find and sort operations
138
+ returns the first node when initialized with vargs -- else class instance
139
+ compare_key_block example: node = LinkedList.call({:key=>"Z"},{:key=>"S"},{:key=>"N"}) {|a| a[:key]}
108
140
 
109
141
 
110
142
  ## Public Methods: SknSettings ONLY
@@ -27,54 +27,24 @@ module SknUtils
27
27
  # Circularly Linked List
28
28
  # Forward (#next) and Backwards (#prev) navigation
29
29
  # No Head or Tail
30
- class CircularLinkedList
31
- attr_accessor :size
32
30
 
33
- def initialize(*vargs, &compare_key_proc)
34
- @current = nil
35
- @head = nil
36
- @tail = nil
37
- @size = 0
38
-
39
- @match_value = block_given? ? compare_key_proc : lambda {|obj| obj }
40
- @sort_ascending = lambda {|a_obj,b_obj| @match_value.call(a_obj) >= @match_value.call(b_obj)}
41
- @sort_descending = lambda {|a_obj,b_obj| @match_value.call(a_obj) <= @match_value.call(b_obj)}
42
- @sort_condition = @sort_ascending
43
-
44
- vargs.each {|value| insert(value) }
45
- first if vargs.size > 1
46
- end
31
+ # LinkedCommons provides;
32
+ # - #initialize, #first, #next, #current, #last, #at_index,
33
+ # #insert, #prepend, #append, #empty?, #clear,
34
+ # #each, #to_a, and #sort!
35
+ #
36
+ class CircularLinkedList < LinkedCommons
47
37
 
48
38
  #
49
39
  # Navigation
50
40
  #
51
41
 
52
- # return values and position current to last node accessed
53
- # prevent @current from nil assignment
54
- def first
55
- @current = self.head if self.head
56
- @current.value rescue nil
57
- end
58
-
59
- def next
60
- @current = @current.next if @current and @current.next
61
- @current.value rescue nil
62
- end
63
-
64
- def current
65
- @current.value rescue nil
66
- end
67
42
 
68
43
  def prev
69
44
  @current = @current.prev if @current and @current.prev
70
45
  @current.value rescue nil
71
46
  end
72
47
 
73
- def last
74
- @current = self.tail if self.tail
75
- @current.value rescue nil
76
- end
77
-
78
48
  # -+ int position from current node
79
49
  def nth(index)
80
50
  node = @current
@@ -97,41 +67,14 @@ module SknUtils
97
67
  current
98
68
  end
99
69
 
100
- # return node at positive index from head
101
- def at_index(index)
102
- find_by_index(index)
103
- current
104
- end
105
-
106
- def empty?
107
- self.size == 0
108
- end
109
-
110
70
  #
111
71
  # Modifications
112
72
  #
113
73
 
114
- # return new size
115
- def insert(value)
116
- temp = @current.value rescue nil
117
- insert_after(temp, value)
118
- end
119
-
120
- # return new size
121
- def prepend(value)
122
- temp = self.head.value rescue nil
123
- insert_before(temp, value)
124
- end
125
- # return new size
126
- def append(value)
127
- temp = self.tail.value rescue nil
128
- insert_after(temp, value)
129
- end
130
-
131
74
  # return new size
132
75
  def insert_before(position_value, value)
133
76
  target = find_by_value(position_value)
134
- node = LinkNode.new(value, target, :circle_before, &@match_value)
77
+ node = LinkNode.new(value, target, :circle_before, self, &@match_value)
135
78
  @current = node
136
79
  if self.size == 0 # only
137
80
  self.head = node
@@ -151,7 +94,7 @@ module SknUtils
151
94
  # return new size
152
95
  def insert_after(position_value, value)
153
96
  target = find_by_value(position_value)
154
- node = LinkNode.new(value, target, :circle_after, &@match_value)
97
+ node = LinkNode.new(value, target, :circle_after, self, &@match_value)
155
98
  @current = node
156
99
  if self.size == 0 # only
157
100
  self.head = node
@@ -197,83 +140,8 @@ module SknUtils
197
140
  end
198
141
 
199
142
 
200
- # return number cleared
201
- def clear
202
- rc = 0
203
- node = self.head
204
- position = node
205
- while node do
206
- node = node.remove!
207
- rc += 1
208
- break if position.equal?(node)
209
- end
210
-
211
- @current = nil
212
- self.head = nil
213
- self.tail = nil
214
- self.size = 0
215
- rc
216
- end
217
-
218
- #
219
- # Enumerate
220
- #
221
-
222
- # perform each() or return enumerator
223
- def each(&block)
224
- @current = self.head
225
- position = self.head
226
- if block_given?
227
- while position do
228
- block.call( position.value.dup )
229
- position = position.next
230
- break if position === @current
231
- end
232
- else
233
- Enumerator.new do |yielder|
234
- while position do
235
- yielder << position.value.dup
236
- position = position.next
237
- break if position === @current
238
- end
239
- end
240
- end
241
- end
242
-
243
- # convert self to a value array
244
- def to_a
245
- @current = self.head
246
- position = self.head
247
- result = []
248
- while position do
249
- result << position.value.dup
250
- position = position.next
251
- break if position.equal?(@current)
252
- end
253
- result
254
- end
255
-
256
- # block format: sort condition : {|a_obj,b_obj| a_obj >= b_obj}
257
- def sort!(direction_sym=:default, &compare_sort_proc)
258
- @active_sort_condition = block_given? ? compare_sort_proc :
259
- case direction_sym
260
- when :asc
261
- @sort_ascending
262
- when :desc
263
- @sort_descending
264
- else
265
- @sort_condition
266
- end
267
- sorted = merge_sort(self.to_a)
268
- clear
269
- sorted.each {|item| insert(item) }
270
- self.size
271
- end
272
-
273
143
  protected
274
144
 
275
- attr_accessor :head, :tail
276
-
277
145
  def find_by_value(value)
278
146
  return nil if value.nil? || self.size == 0
279
147
  stop_node = self.head
@@ -295,29 +163,6 @@ module SknUtils
295
163
  node
296
164
  end
297
165
 
298
- # Merged Sort via Ref: http://rubyalgorithms.com/merge_sort.html
299
- # arr is Array to be sorted, sort_cond is Proc expecting a/b params returning true/false
300
- def merge_sort(arr)
301
- return arr if arr.size < 2
302
- middle = arr.size / 2
303
- left = merge_sort(arr[0...middle])
304
- right = merge_sort(arr[middle..arr.size])
305
- merge(left, right)
306
- end
307
-
308
- def merge(left, right)
309
- sorted = []
310
- while left.any? && right.any?
311
- if @active_sort_condition.call(left.first, right.first)
312
- sorted.push right.shift
313
- else
314
- sorted.push left.shift
315
- end
316
- end
317
-
318
- sorted + left + right
319
- end
320
-
321
166
  end # end class
322
167
  end # module
323
168
  end # end module
@@ -28,54 +28,23 @@ module SknUtils
28
28
  # Forward (#next) and Backwards (#prev) navigation
29
29
  # Head when (prev == nil)
30
30
  # Tail when (next == nil)
31
- class DoublyLinkedList
32
- attr_accessor :size
33
31
 
34
- def initialize(*vargs, &compare_key_proc)
35
- @current = nil
36
- @head = nil
37
- @tail = nil
38
- @size = 0
39
-
40
- @match_value = block_given? ? compare_key_proc : lambda {|obj| obj }
41
- @sort_ascending = lambda {|a_obj,b_obj| @match_value.call(a_obj) >= @match_value.call(b_obj)}
42
- @sort_descending = lambda {|a_obj,b_obj| @match_value.call(a_obj) <= @match_value.call(b_obj)}
43
- @sort_condition = @sort_ascending
44
-
45
- vargs.each {|value| insert(value) }
46
- first if vargs.size > 1
47
- end
32
+ # LinkedCommons provides;
33
+ # - #initialize, #first, #next, #current, #last, #at_index,
34
+ # #insert, #prepend, #append, #empty?, #clear,
35
+ # #each, #to_a, and #sort!
36
+ #
37
+ class DoublyLinkedList < LinkedCommons
48
38
 
49
39
  #
50
40
  # Navigation
51
41
  #
52
42
 
53
- # return values and position current to last node accessed
54
- # prevent @current from nil assignment
55
- def first
56
- @current = head if head
57
- @current.value rescue nil
58
- end
59
-
60
- def next
61
- @current = @current.next if @current and @current.next
62
- @current.value rescue nil
63
- end
64
-
65
- def current
66
- @current.value rescue nil
67
- end
68
-
69
43
  def prev
70
44
  @current = @current.prev if @current and @current.prev
71
45
  @current.value rescue nil
72
46
  end
73
47
 
74
- def last
75
- @current = tail if tail
76
- @current.value rescue nil
77
- end
78
-
79
48
  # -+ int position from current node
80
49
  def nth(index)
81
50
  node = @current
@@ -95,54 +64,27 @@ module SknUtils
95
64
  current
96
65
  end
97
66
 
98
- # return node at positive index from head
99
- def at_index(index)
100
- find_by_index(index)
101
- current
102
- end
103
-
104
- def empty?
105
- size == 0
106
- end
107
-
108
67
  #
109
68
  # Modifications
110
69
  #
111
70
 
112
- # return new size
113
- def insert(value)
114
- temp = @current.value rescue nil
115
- insert_after(temp, value)
116
- end
117
-
118
- # return new size
119
- def prepend(value)
120
- temp = head.value rescue nil
121
- insert_before(temp, value)
122
- end
123
- # return new size
124
- def append(value)
125
- temp = tail.value rescue nil
126
- insert_after(temp, value)
127
- end
128
-
129
71
  # return new size
130
72
  def insert_before(position_value, value)
131
73
  target = find_by_value(position_value)
132
- node = LinkNode.new(value, target, :before, &@match_value)
74
+ node = LinkNode.new(value, target, :before, self, &@match_value)
133
75
  @current = node if target
134
- self.head = node if head === target
135
- self.tail = node if tail.nil?
76
+ self.head = node if self.head === target
77
+ self.tail = node if self.tail.nil?
136
78
  self.size += 1
137
79
  end
138
80
 
139
81
  # return new size
140
82
  def insert_after(position_value, value)
141
83
  target = find_by_value(position_value)
142
- node = LinkNode.new(value, target, :after, &@match_value)
84
+ node = LinkNode.new(value, target, :after, self, &@match_value)
143
85
  @current = node
144
- self.head = node if head.nil?
145
- self.tail = node if tail === target
86
+ self.head = node if self.head.nil?
87
+ self.tail = node if self.tail === target
146
88
  self.size += 1
147
89
  end
148
90
 
@@ -150,7 +92,7 @@ module SknUtils
150
92
  def remove(value)
151
93
  target_node = find_by_value(value)
152
94
  if target_node
153
- if size == 1 # will become zero
95
+ if self.size == 1 # will become zero
154
96
  @current = nil
155
97
  self.head = nil
156
98
  self.tail = nil
@@ -172,86 +114,11 @@ module SknUtils
172
114
  end
173
115
  end
174
116
 
175
- # return number cleared
176
- def clear
177
- rc = 0
178
- node = head
179
- position = head
180
- while node do
181
- node = node.remove!
182
- rc += 1
183
- break if position === node
184
- end
185
-
186
- @current = nil
187
- self.head = nil
188
- self.tail = nil
189
- self.size = 0
190
- rc
191
- end
192
-
193
- #
194
- # Enumerate
195
- #
196
-
197
- # perform each() or return enumerator
198
- def each(&block)
199
- @current = head
200
- position = head
201
- if block_given?
202
- while position do
203
- block.call( position.value.dup )
204
- position = position.next
205
- break if position === @current
206
- end
207
- else
208
- Enumerator.new do |yielder|
209
- while position do
210
- yielder << position.value.dup
211
- position = position.next
212
- break if position === @current
213
- end
214
- end
215
- end
216
- end
217
-
218
- # convert self to a value array
219
- def to_a
220
- @current = head
221
- position = head
222
- result = []
223
- while position do
224
- result << position.value.dup
225
- position = position.next
226
- break if position === @current
227
- end
228
- result
229
- end
230
-
231
- # block format: sort condition : {|a_obj,b_obj| a_obj >= b_obj}
232
- def sort!(direction_sym=:default, &compare_sort_proc)
233
- @active_sort_condition = block_given? ? compare_sort_proc :
234
- case direction_sym
235
- when :asc
236
- @sort_ascending
237
- when :desc
238
- @sort_descending
239
- else
240
- @sort_condition
241
- end
242
- sorted = merge_sort(to_a)
243
- clear
244
- sorted.each {|item| insert(item) }
245
- size
246
- end
247
-
248
- private
249
-
250
- attr_accessor :head, :tail
117
+ protected
251
118
 
252
119
  def find_by_value(value)
253
- return nil if head.nil? || value.nil? || size == 0
254
- prior = head
120
+ return nil if self.head.nil? || value.nil? || self.size == 0
121
+ prior = self.head
255
122
  target = prior
256
123
  while target and not target.match_by_value(value)
257
124
  prior = target
@@ -261,42 +128,13 @@ module SknUtils
261
128
  end
262
129
 
263
130
  def find_by_index(index)
264
- return nil if head.nil? or index < 1 or index > size
265
- node = head
131
+ return nil if self.head.nil? or index < 1 or index > self.size
132
+ node = self.head
266
133
  node = node.next while ((index -= 1) > 0 and node.next)
267
134
  @current = node if node
268
135
  node
269
136
  end
270
137
 
271
- # Merged Sort via Ref: http://rubyalgorithms.com/merge_sort.html
272
- # arr is Array to be sorted, sort_cond is Proc expecting a/b params returning true/false
273
- def merge_sort(arr)
274
- return arr if arr.size < 2
275
-
276
- middle = arr.size / 2
277
-
278
- left = merge_sort(arr[0...middle])
279
- right = merge_sort(arr[middle..arr.size])
280
-
281
- merge(left, right)
282
- end
283
-
284
- def merge(left, right)
285
- sorted = []
286
-
287
- while left.any? && right.any?
288
-
289
- if @active_sort_condition.call(left.first, right.first)
290
- sorted.push right.shift
291
- else
292
- sorted.push left.shift
293
- end
294
-
295
- end
296
-
297
- sorted + left + right
298
- end
299
-
300
138
  end # end class
301
139
  end # module
302
140
  end # end module
@@ -8,10 +8,11 @@ module SknUtils
8
8
  class LinkNode
9
9
  attr_accessor :prev, :next, :value
10
10
 
11
- def initialize(val, anchor_node=nil, strategy=:after, &cmp_key)
11
+ def initialize(val, anchor_node=nil, strategy=:after, mgr=nil, &cmp_key)
12
12
  @value = val
13
13
  @prev = nil
14
14
  @next = nil
15
+ @provider = mgr
15
16
  @cmp_proc = block_given? ? cmp_key : lambda {|a| a }
16
17
 
17
18
  case strategy
@@ -55,6 +56,41 @@ module SknUtils
55
56
  def to_s
56
57
  "Node with value: #{@value}"
57
58
  end
59
+
60
+ # Reverse API to Parent Linked List Class
61
+ def node_value
62
+ node_request.value
63
+ end
64
+ def first_node
65
+ node_request(:first)
66
+ end
67
+ def next_node
68
+ node_request(:next)
69
+ end
70
+ def current_node
71
+ node_request(:current)
72
+ end
73
+ def prev_node
74
+ node_request(:prev)
75
+ end
76
+ def last_node
77
+ node_request(:last)
78
+ end
79
+
80
+ protected()
81
+
82
+ def respond_to_missing?(method, include_private=false)
83
+ @provider && @provider.protected_methods(true).include?(method) || super
84
+ end
85
+
86
+ def method_missing(method, *args, &block)
87
+ if @provider and @provider.protected_methods(true).include?(method)
88
+ block_given? ? @provider.send(method, *args, block) :
89
+ (args.size == 0 ? @provider.send(method) : @provider.send(method, *args))
90
+ else
91
+ super
92
+ end
93
+ end
58
94
  end
59
95
  end # module
60
96
  end
@@ -0,0 +1,202 @@
1
+ ##
2
+ # File <SknUtils>/lib/skn_utils/lists/linked_commons.rb
3
+ #
4
+ # Common routines for Linked List:
5
+ # #initialize, #first, #next, #current, #last, #at_index,
6
+ # #insert, #prepend, #append, #empty?, #clear,
7
+ # #each, #to_a, and #sort!
8
+ ##
9
+
10
+ module SknUtils
11
+ module Lists
12
+
13
+ class LinkedCommons
14
+ attr_accessor :size
15
+
16
+ # Initialize and return first node if nodes are available
17
+ def self.call(*vargs, &compare_key_proc)
18
+ target = self.new(*vargs, &compare_key_proc)
19
+ return target.instance_variable_get(:@current) if vargs.size > 1
20
+ target
21
+ end
22
+
23
+ def initialize(*vargs, &compare_key_proc)
24
+ @current = nil
25
+ @head = nil
26
+ @tail = nil
27
+ @size = 0
28
+
29
+ @match_value = block_given? ? compare_key_proc : lambda {|obj| obj }
30
+ @sort_ascending = lambda {|a_obj,b_obj| @match_value.call(a_obj) >= @match_value.call(b_obj)}
31
+ @sort_descending = lambda {|a_obj,b_obj| @match_value.call(a_obj) <= @match_value.call(b_obj)}
32
+ @sort_condition = @sort_ascending
33
+
34
+ vargs.each {|value| insert(value) }
35
+ first if vargs.size > 1
36
+ end
37
+
38
+
39
+ # return values and position current to last node accessed
40
+ # prevent @current from nil assignment
41
+ def first
42
+ @current = self.head if self.head
43
+ @current.value rescue nil
44
+ end
45
+
46
+ def next
47
+ @current = @current.next if @current and @current.next
48
+ @current.value rescue nil
49
+ end
50
+
51
+ def current
52
+ @current.value rescue nil
53
+ end
54
+
55
+ def last
56
+ @current = self.tail if self.tail
57
+ @current.value rescue nil
58
+ end
59
+
60
+ # return node at positive index from head
61
+ def at_index(index)
62
+ find_by_index(index)
63
+ current
64
+ end
65
+
66
+ def empty?
67
+ self.size == 0
68
+ end
69
+
70
+ # return number cleared
71
+ def clear
72
+ rc = 0
73
+ node = self.head
74
+ position = node
75
+ while node do
76
+ node = node.remove!
77
+ rc += 1
78
+ break if position.equal?(node)
79
+ end
80
+
81
+ @current = nil
82
+ self.head = nil
83
+ self.tail = nil
84
+ self.size = 0
85
+ rc
86
+ end
87
+
88
+ # return new size
89
+ def insert(value)
90
+ temp = @current.value rescue nil
91
+ insert_after(temp, value)
92
+ end
93
+
94
+ # return new size
95
+ def prepend(value)
96
+ temp = self.head.value rescue nil
97
+ insert_before(temp, value)
98
+ end
99
+ # return new size
100
+ def append(value)
101
+ temp = self.tail.value rescue nil
102
+ insert_after(temp, value)
103
+ end
104
+
105
+ #
106
+ # Enumerate
107
+ #
108
+
109
+ # perform each() or return enumerator
110
+ def each(&block)
111
+ @current = self.head
112
+ position = self.head
113
+ if block_given?
114
+ while position do
115
+ block.call( position.value.dup )
116
+ position = position.next
117
+ break if position === @current
118
+ end
119
+ else
120
+ Enumerator.new do |yielder|
121
+ while position do
122
+ yielder << position.value.dup
123
+ position = position.next
124
+ break if position === @current
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+ # convert self to a value array
131
+ def to_a
132
+ @current = self.head
133
+ position = self.head
134
+ result = []
135
+ while position do
136
+ result << position.value.dup
137
+ position = position.next
138
+ break if position.equal?(@current)
139
+ end
140
+ result
141
+ end
142
+
143
+ # block format: sort condition : {|a_obj,b_obj| a_obj >= b_obj}
144
+ def sort!(direction_sym=:default, &compare_sort_proc)
145
+ @active_sort_condition = block_given? ? compare_sort_proc :
146
+ case direction_sym
147
+ when :asc
148
+ @sort_ascending
149
+ when :desc
150
+ @sort_descending
151
+ else
152
+ @sort_condition
153
+ end
154
+ sorted = merge_sort(self.to_a)
155
+ clear
156
+ sorted.each {|item| insert(item) }
157
+ self.size
158
+ end
159
+
160
+
161
+ protected
162
+
163
+ attr_accessor :head, :tail
164
+
165
+ # Merged Sort via Ref: http://rubyalgorithms.com/merge_sort.html
166
+ # arr is Array to be sorted, sort_cond is Proc expecting a/b params returning true/false
167
+ def merge_sort(arr)
168
+ return arr if arr.size < 2
169
+ middle = arr.size / 2
170
+ left = merge_sort(arr[0...middle])
171
+ right = merge_sort(arr[middle..arr.size])
172
+ merge(left, right)
173
+ end
174
+
175
+ def merge(left, right)
176
+ sorted = []
177
+ while left.any? && right.any?
178
+ if @active_sort_condition.call(left.first, right.first)
179
+ sorted.push right.shift
180
+ else
181
+ sorted.push left.shift
182
+ end
183
+ end
184
+
185
+ sorted + left + right
186
+ end
187
+
188
+ # Retrieves requested node, not value
189
+ def node_request(method_sym=:current, *vargs, &block)
190
+ position_value = block_given? ? send(method_sym, *vargs, block) :
191
+ (vargs.size == 0 ? send(method_sym) : send(method_sym, *vargs))
192
+ @current
193
+ end
194
+ # Retrieves requested value, not node
195
+ def node_value_request(method_sym=:current, *vargs, &block)
196
+ position_value = block_given? ? send(method_sym, *vargs, block) :
197
+ (vargs.size == 0 ? send(method_sym) : send(method_sym, *vargs))
198
+ end
199
+
200
+ end
201
+ end # module
202
+ end
@@ -28,51 +28,18 @@ module SknUtils
28
28
  # Forward or #next navigation only
29
29
  # Head is absolute via #first
30
30
  # Tail when (next == nil)
31
- class LinkedList
32
- attr_accessor :size
33
31
 
34
- # &compare_key_proc supplies an method to identify
35
- # the key of an object for comparison purposes
36
- def initialize(*vargs, &compare_key_proc)
37
- @sort_condition = nil
38
- @current = nil
39
- @head = nil
40
- @tail = nil
41
- @size = 0
42
-
43
- @match_value = block_given? ? compare_key_proc : lambda {|obj| obj }
44
- @sort_ascending = lambda {|a_obj,b_obj| @match_value.call(a_obj) >= @match_value.call(b_obj)}
45
- @sort_descending = lambda {|a_obj,b_obj| @match_value.call(a_obj) <= @match_value.call(b_obj)}
46
- @sort_condition = @sort_ascending
47
-
48
- vargs.each {|value| insert(value) }
49
- first if vargs.size > 1
50
- end
32
+ # LinkedCommons provides;
33
+ # - #initialize, #first, #next, #current, #last, #at_index,
34
+ # #insert, #prepend, #append, #empty?, #clear,
35
+ # #each, #to_a, and #sort!
36
+ #
37
+ class LinkedList < LinkedCommons
51
38
 
52
39
  #
53
40
  # Navigation
54
41
  #
55
42
 
56
- # return values and position current to last node accessed
57
- def first
58
- @current = head if head
59
- @current.value rescue nil
60
- end
61
-
62
- def next
63
- @current = @current.next if @current and @current.next
64
- @current.value rescue nil
65
- end
66
-
67
- def current
68
- @current.value rescue nil
69
- end
70
-
71
- def last
72
- @current = tail if tail
73
- @current.value rescue nil
74
- end
75
-
76
43
  # +int position from current node
77
44
  def nth(index)
78
45
  node = @current
@@ -85,45 +52,17 @@ module SknUtils
85
52
  current
86
53
  end
87
54
 
88
- # return node at positive index from head
89
- def at_index(index)
90
- find_by_index(index)
91
- current
92
- end
93
-
94
- def empty?
95
- size == 0
96
- end
97
-
98
55
  #
99
56
  # Modifications
100
57
  #
101
58
 
102
- # return new size
103
- def insert(value)
104
- temp = @current.value rescue nil
105
- insert_after(temp, value)
106
- end
107
-
108
- # return new size
109
- def prepend(value)
110
- temp = head.value rescue nil
111
- insert_before(temp, value)
112
- end
113
-
114
- # return new size
115
- def append(value)
116
- temp = tail.value rescue nil
117
- insert_after(temp, value)
118
- end
119
-
120
59
  # return new size
121
60
  def insert_before(position_value, value)
122
61
  prior, target = find_by_value(position_value)
123
- node = LinkNode.new(value, prior, :single, &@match_value)
62
+ node = LinkNode.new(value, prior, :single, self, &@match_value)
124
63
  node.next = target if target
125
- self.head = node if head === target
126
- self.tail = node if tail.nil?
64
+ self.head = node if self.head === target
65
+ self.tail = node if self.tail.nil?
127
66
  @current = node
128
67
  self.size += 1
129
68
  end
@@ -131,9 +70,9 @@ module SknUtils
131
70
  # return new size
132
71
  def insert_after(position_value, value)
133
72
  prior, target = find_by_value(position_value)
134
- node = LinkNode.new(value, target, :single, &@match_value)
135
- self.head = node if head.nil?
136
- self.tail = node if tail === target
73
+ node = LinkNode.new(value, target, :single, self, &@match_value)
74
+ self.head = node if self.head.nil?
75
+ self.tail = node if self.tail === target
137
76
  @current = node
138
77
  self.size += 1
139
78
  end
@@ -143,90 +82,16 @@ module SknUtils
143
82
  prior, target_node = find_by_value(value)
144
83
  @current = prior.nil? ? target_node.next : prior
145
84
  @current.next = target_node.remove! if @current && target_node
146
- self.tail = @current.next if @current && tail === target_node
147
- self.head = @current.next if @current && head === target_node
85
+ self.tail = @current.next if @current && self.tail === target_node
86
+ self.head = @current.next if @current && self.head === target_node
148
87
  self.size -= 1
149
88
  end
150
89
 
151
- # return number cleared
152
- def clear
153
- rc = 0
154
- node = head
155
- position = head
156
- while node do
157
- node = node.remove!
158
- rc += 1
159
- break if position === node
160
- end
161
-
162
- @current = nil
163
- self.head = nil
164
- self.tail = nil
165
- self.size = 0
166
- rc
167
- end
168
-
169
- #
170
- # Enumerate
171
- #
172
-
173
- # perform each() or return enumerator
174
- def each(&block)
175
- @current = head
176
- position = head
177
- if block_given?
178
- while position do
179
- block.call(position.value.dup )
180
- position = position.next
181
- end
182
- else
183
- Enumerator.new do |yielder|
184
- while position do
185
- yielder << position.value.dup
186
- position = position.next
187
- end
188
- end
189
- end
190
- end
191
-
192
- # convert self to a value array
193
- def to_a
194
- @current = head
195
- position = head
196
- result = []
197
- while position do
198
- result << position.value.dup
199
- position = position.next
200
- break if position === @current
201
- end
202
- result
203
- end
204
-
205
- # block format: sort condition : {|a_obj,b_obj| a_obj >= b_obj}
206
- def sort!(direction_sym=:default, &compare_sort_proc)
207
- @active_sort_condition = block_given? ? compare_sort_proc :
208
- case direction_sym
209
- when :asc
210
- @sort_ascending
211
- when :desc
212
- @sort_descending
213
- else
214
- @sort_condition
215
- end
216
-
217
- sorted = merge_sort(to_a)
218
- clear
219
- sorted.each {|item| insert(item) }
220
- size
221
- end
222
-
223
- private
224
-
225
- attr_accessor :head, :tail
90
+ protected
226
91
 
227
92
  def find_by_value(value)
228
- return [@current, nil] if head.nil? || value.nil?
229
- prior = head
93
+ return [@current, nil] if self.head.nil? || value.nil?
94
+ prior = self.head
230
95
  target = prior
231
96
  while target and not target.match_by_value(value)
232
97
  prior = target
@@ -237,42 +102,13 @@ module SknUtils
237
102
  end
238
103
 
239
104
  def find_by_index(index)
240
- return nil if head.nil? || index < 1 || index > size
241
- node = head
105
+ return nil if self.head.nil? || index < 1 || index > self.size
106
+ node = self.head
242
107
  node = node.next while ((index -= 1) > 0 and node.next)
243
108
  @current = node if node
244
109
  node
245
110
  end
246
111
 
247
- # Merged Sort via Ref: http://rubyalgorithms.com/merge_sort.html
248
- # arr is Array to be sorted, sort_cond is Proc expecting a/b params returning true/false
249
- def merge_sort(arr)
250
- return arr if arr.size < 2
251
-
252
- middle = arr.size / 2
253
-
254
- left = merge_sort(arr[0...middle])
255
- right = merge_sort(arr[middle..arr.size])
256
-
257
- merge(left, right)
258
- end
259
-
260
- def merge(left, right)
261
- sorted = []
262
-
263
- while left.any? && right.any?
264
-
265
- if @active_sort_condition.call(left.first, right.first)
266
- sorted.push right.shift
267
- else
268
- sorted.push left.shift
269
- end
270
-
271
- end
272
-
273
- sorted + left + right
274
- end
275
-
276
112
  end # end class
277
113
  end # end module
278
114
  end # end module
@@ -3,7 +3,7 @@ module SknUtils
3
3
  class Version
4
4
  MAJOR = 3
5
5
  MINOR = 3
6
- PATCH = 5
6
+ PATCH = 6
7
7
 
8
8
  def self.to_s
9
9
  [MAJOR, MINOR, PATCH].join('.')
data/lib/skn_utils.rb CHANGED
@@ -5,6 +5,7 @@ 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/linked_commons'
8
9
  require 'skn_utils/lists/link_node'
9
10
  require 'skn_utils/lists/linked_list'
10
11
  require 'skn_utils/lists/doubly_linked_list'
@@ -0,0 +1,85 @@
1
+ ##
2
+ # spec/lib/skn_utils/node_based_linked_list_spec.rb
3
+ #
4
+
5
+ RSpec.describe SknUtils::Lists::DoublyLinkedList, "DoublyLinkedList using node interface " do
6
+
7
+ context "Node Interface Edge Cases " do
8
+ let(:node) { described_class.call(10,20, 30, 40, 50, 60, 70, 80, 90, 100) {|a| a} }
9
+
10
+ context "Node Retrieval " do
11
+
12
+ it "#node_request(:first) returns a LinkedNode object." do
13
+ expect(node.send(:node_request,:first)).to be_a SknUtils::Lists::LinkNode
14
+ end
15
+ it "#first_node returns a LinkedNode object." do
16
+ expect(node.first_node).to be_a SknUtils::Lists::LinkNode
17
+ end
18
+ it "#next_node returns a LinkedNode object." do
19
+ expect(node.next_node).to be_a SknUtils::Lists::LinkNode
20
+ end
21
+ it "#current_node returns a LinkedNode object." do
22
+ expect(node.current_node).to be_a SknUtils::Lists::LinkNode
23
+ end
24
+ it "#prev_node returns a LinkedNode object." do
25
+ expect(node.prev_node).to be_a SknUtils::Lists::LinkNode
26
+ end
27
+ it "#last_node returns a LinkedNode object." do
28
+ expect(node.last_node).to be_a SknUtils::Lists::LinkNode
29
+ end
30
+ end
31
+
32
+ context "Node Values " do
33
+
34
+ it "#methods with params are supported through #{}node_request() interface. " do
35
+ expect(node.first_node.node_value_request(:at_index, 5)).to eq(50)
36
+ end
37
+ it "First node has the expected value. " do
38
+ expect(node.first_node.value).to eq(10)
39
+ end
40
+ it "Next node has the expected value. " do
41
+ expect(node.next_node.value).to eq(20)
42
+ end
43
+ it "Current node has the expected value. " do
44
+ 3.times { node.next_node }
45
+ expect(node.current_node.value).to eq(40)
46
+ end
47
+ it "Last node has the expected value. " do
48
+ expect(node.last_node.value).to eq(100)
49
+ end
50
+ it "#node_value collected match #to_a output. " do
51
+ nav_ary = []
52
+ node.node_value_request(:size).times do
53
+ nav_ary << node.node_value
54
+ node.next_node
55
+ end
56
+
57
+ expect(nav_ary).to eq(node.node_value_request(:to_a))
58
+ end
59
+ end
60
+
61
+ context "Node Navigation " do
62
+
63
+ it "Can navigate to each mode in list, forward. " do
64
+ node.node_value_request(:size).times do
65
+ expect(node.next_node).to be_a SknUtils::Lists::LinkNode
66
+ end
67
+ end
68
+ it "Can navigate to each mode in list, backward. " do
69
+ node.last_node.node_value_request(:size).times do
70
+ expect(node.prev_node).to be_a SknUtils::Lists::LinkNode
71
+ end
72
+ end
73
+ it "Values collected match #to_a output. " do
74
+ nav_ary = []
75
+ node.node_value_request(:size).times do
76
+ nav_ary << node.node_value
77
+ node.next_node
78
+ end
79
+
80
+ expect(node.node_value_request(:to_a)).to eq(nav_ary)
81
+ end
82
+ end
83
+ end
84
+
85
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: skn_utils
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.3.5
4
+ version: 3.3.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Scott Jr
@@ -140,6 +140,7 @@ files:
140
140
  - lib/skn_utils/lists/circular_linked_list.rb
141
141
  - lib/skn_utils/lists/doubly_linked_list.rb
142
142
  - lib/skn_utils/lists/link_node.rb
143
+ - lib/skn_utils/lists/linked_commons.rb
143
144
  - lib/skn_utils/lists/linked_list.rb
144
145
  - lib/skn_utils/nested_result.rb
145
146
  - lib/skn_utils/notifier_base.rb
@@ -159,6 +160,7 @@ files:
159
160
  - spec/lib/skn_utils/lists/Circular_linked_list_spec.rb
160
161
  - spec/lib/skn_utils/lists/doubly_linked_list_spec.rb
161
162
  - spec/lib/skn_utils/lists/linked_list_spec.rb
163
+ - spec/lib/skn_utils/lists/node_based_linked_list_spec.rb
162
164
  - spec/lib/skn_utils/nested_result_spec.rb
163
165
  - spec/lib/skn_utils/notifier_base_spec.rb
164
166
  - spec/lib/skn_utils/null_object_spec.rb
@@ -204,6 +206,7 @@ test_files:
204
206
  - spec/lib/skn_utils/lists/Circular_linked_list_spec.rb
205
207
  - spec/lib/skn_utils/lists/doubly_linked_list_spec.rb
206
208
  - spec/lib/skn_utils/lists/linked_list_spec.rb
209
+ - spec/lib/skn_utils/lists/node_based_linked_list_spec.rb
207
210
  - spec/lib/skn_utils/nested_result_spec.rb
208
211
  - spec/lib/skn_utils/notifier_base_spec.rb
209
212
  - spec/lib/skn_utils/null_object_spec.rb