skn_utils 3.3.5 → 3.3.6

Sign up to get free protection for your applications and to get access to all the features.
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