linked 0.2.0 → 0.2.1

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: d85491962a16ae84236a5e278940ba46b6b29c65
4
- data.tar.gz: 97acd2defdcb3d1a983a29582daaadf3019ca317
3
+ metadata.gz: fefa402916a5d9db25add5e34a0d95cfbfe1440f
4
+ data.tar.gz: 3f17115bffeb8777ba904292f98c8fffbed1b34b
5
5
  SHA512:
6
- metadata.gz: 5f8fc6b24885ac1d7f66b7bbf273bf4744bc765f41b33b6ca71c41e4dc5d3ad0f8cdd37976b2730a79594661d93d6a77dba43155c2e2f8def5b42b650b99c28e
7
- data.tar.gz: fb1a283dfa52704de58e93843a5fedefa86275f2b5be514575f3baef1a3610caacbbbefeba49373257e4863215464a4f57273619898e8a0ec22029f44eb995e1
6
+ metadata.gz: 6dbf54c87e2ca417d725e40f12eff566439fc9212c5f13c12bd2c089c0018a09cf671de635c2aa800151e3d6474de793e6b3991ab51c7e7fa3f224903ad34cb2
7
+ data.tar.gz: 83a352fb6f3f00e5bb7245075d47609521dbeb7c508a1ea066acb2fb120e6affdbc2ed9c54cbcc15c881353b7c716653976eec02e138f7b8131e51c6699ac7c9
data/lib/linked/item.rb CHANGED
@@ -6,10 +6,10 @@ module Linked
6
6
  # This class implements doubly linked list items, designed to work both on
7
7
  # their own and as children of list.
8
8
  #
9
- # +- - - + +------+------+ +- - - +
10
- # | Head | <--| prev | next |--> ... --> | Tail |
11
- # + - - -+ +------+------+ + - - -+
12
- # (optional) First Item N Items (optional)
9
+ # +- - - + +------+------+ +- - - +
10
+ # | Head | <--| prev | next |--> ... --> | Tail |
11
+ # + - - -+ +------+------+ + - - -+
12
+ # (optional) First Item N Items (optional)
13
13
  #
14
14
  # An object is considered a list if it responds to #head, #tail, #grow and
15
15
  # #shrink. The latter facilitate counting of the items and will be called
@@ -17,6 +17,19 @@ module Linked
17
17
  # expected to return two objects that, respectivly
18
18
  # a) responds to #next= and #append, or #prev= and #prepend and
19
19
  # b) returns true for #nil?.
20
+ #
21
+ # Notation
22
+ # --------
23
+ #
24
+ # Some methods operate on chains of items, and to describe the effects of an
25
+ # operation the following syntax is used.
26
+ #
27
+ # A ( A <> B ) [ A <> B ]
28
+ # (i) (ii) (iii)
29
+ #
30
+ # Single items are denoted with capital letters (i), while chains are written
31
+ # as multiple connected items (ii). The parenthesis are optional. To show that
32
+ # one or more nodes are wraped in a list, angle brackets are used (iii).
20
33
 
21
34
  class Item
22
35
  # Access the list (if any) that the item belongs to. Writing to this
@@ -191,16 +204,17 @@ module Linked
191
204
  # the argument after: true, the split will instead happen after this item
192
205
  # and it will instead be kept with those before it.
193
206
  #
194
- # Example
207
+ # Example for the chain (A <> B <> C)
195
208
  #
196
- # item_b.split(after: false) => ~item_a~ |> item_b item_c
197
- # item_b.split(after: true) => item_a item_b <| ~item_c~
209
+ # B.split(after: false) => (A), (B <> C)
210
+ # B.split(after: true) => (A <> B), (C)
198
211
  #
199
212
  # after - determine wheter to split the chain before or after this item.
200
213
  #
201
214
  # Returns self.
202
215
 
203
216
  def split after: false
217
+ warn '[DEPRECATION] this method will be removed in the next major update. Please use #delete_before and #delete_after instead'
204
218
  if after
205
219
  unless last?
206
220
  if @list
@@ -246,6 +260,10 @@ module Linked
246
260
  # the given item is part of a chain, all items following it will be moved to
247
261
  # this one, and added to the list if one is set.
248
262
  #
263
+ # Example for the chain (A <> C)
264
+ #
265
+ # A.append B # => (A <> B <> C)
266
+ #
249
267
  # Alternativly the argument can be an arbitrary object, in which case a new
250
268
  # item will be created around it.
251
269
  #
@@ -258,33 +276,31 @@ module Linked
258
276
  #
259
277
  # Returns the last item that was appended.
260
278
 
261
- def append(sibling)
262
- if sibling.is_a? Item
263
- sibling.split
279
+ def append(object)
280
+ if object.respond_to? :item
281
+ first_item = object.item
282
+ last_item = first_item.send :extract_beginning_with, @list
264
283
  else
265
- sibling = self.class.new sibling
284
+ first_item = last_item = self.class.new object
285
+ first_item.list = @list
286
+ @list.send :grow if @list
266
287
  end
267
-
268
- sibling.prev = self
269
- after_sibling = @next
270
- @next = sibling
271
-
272
- count = 1 + loop.count do
273
- sibling.list = @list
274
- sibling = sibling.next
275
- end
276
-
277
- @list.send :grow, count if @list
278
-
279
- sibling.next = after_sibling
280
- after_sibling.prev = sibling if after_sibling
281
- sibling
288
+
289
+ first_item.prev = self
290
+ @next.prev = last_item if @next
291
+ @next, last_item.next = first_item, @next
292
+
293
+ last_item
282
294
  end
283
295
 
284
296
  # Inserts the given item between this one and the one before it (if any). If
285
297
  # the given item is part of a chain, all items preceeding it will be moved
286
298
  # to this one, and added to the list if one is set.
287
299
  #
300
+ # Example for the chain (A <> C)
301
+ #
302
+ # C.prepend B # => (A <> B <> C)
303
+ #
288
304
  # Alternativly the argument can be an arbitrary object, in which case a new
289
305
  # item will be created around it.
290
306
  #
@@ -297,29 +313,23 @@ module Linked
297
313
  #
298
314
  # Returns the last item that was prepended.
299
315
 
300
- def prepend(sibling)
301
- if sibling.is_a? Item
302
- sibling.split after: true
316
+ def prepend(object)
317
+ if object.respond_to? :item
318
+ last_item = object.item
319
+ first_item = last_item.send :extract_ending_with, @list
303
320
  else
304
- sibling = self.class.new sibling
321
+ first_item = last_item = self.class.new object
322
+ first_item.list = @list
323
+ @list.send :grow, 1 if @list
305
324
  end
306
-
307
- sibling.next = self
308
- before_sibling = @prev
309
- @prev = sibling
310
-
311
- count = 1 + loop.count do
312
- sibling.list = @list
313
- sibling = sibling.prev
314
- end
315
-
316
- @list.send :grow, count if @list
317
-
318
- sibling.prev = before_sibling
319
- before_sibling.next = sibling if before_sibling
320
- sibling
325
+
326
+ last_item.next = self
327
+ @prev.next = first_item if @prev
328
+ @prev, first_item.prev = last_item, @prev
329
+
330
+ first_item
321
331
  end
322
-
332
+
323
333
  # Remove an item from the chain. If this item is part of a list and is
324
334
  # either first, last or both in that list, #next= and #prev= will be called
325
335
  # on the list head and tail respectivly.
@@ -336,9 +346,32 @@ module Linked
336
346
  @next = @prev = @list = nil
337
347
  self
338
348
  end
349
+
350
+ # Remove all items before this one in the chain. If the items are part of a
351
+ # list they will be removed from it.
352
+ #
353
+ # Returns the last item in the chain that was just deleted, or nil if this
354
+ # is the first item.
355
+
356
+ def delete_before
357
+ @prev.send :extract_ending_with unless first?
358
+ end
359
+
360
+ # Remove all items after this one in the chain. If the items are part of a
361
+ # list they will be removed from it.
362
+ #
363
+ # Returns the last item in the chain that was just deleted, or nil if this
364
+ # is the first item.
365
+
366
+ def delete_after
367
+ @next.send :extract_beginning_with unless last?
368
+ end
339
369
 
340
370
  # Iterates over each item before this, in reverse order. If a block is not
341
371
  # given an enumerator is returned.
372
+ #
373
+ # Note that raising a StopIteraion inside the block will cause the loop to
374
+ # silently stop the iteration early.
342
375
 
343
376
  def before
344
377
  return to_enum(__callee__) unless block_given?
@@ -354,6 +387,9 @@ module Linked
354
387
 
355
388
  # Iterates over each item after this. If a block is not given an enumerator
356
389
  # is returned.
390
+ #
391
+ # Note that raising a StopIteraion inside the block will cause the loop to
392
+ # silently stop the iteration early.
357
393
 
358
394
  def after
359
395
  return to_enum(__callee__) unless block_given?
@@ -384,5 +420,100 @@ module Linked
384
420
  output = format '%s:0x%0x', self.class.name, object_id
385
421
  value ? output + " value=#{value.inspect}" : output
386
422
  end
423
+
424
+ # PRIVATE DANGEROUS METHOD. This method should never be called directly
425
+ # since it may leave the extracted item chain in an invalid state.
426
+ #
427
+ # This method extracts the item, together with the chain following it, from
428
+ # the list they are in (if any) and optionally facilitates moving them to a
429
+ # new list.
430
+ #
431
+ # Given the two lists
432
+ # [ A <> B <> C ] [ D ]
433
+ # (I) (II)
434
+ #
435
+ # calling B.extract_beginning_with(II) will result in (B <> C) being removed
436
+ # from (I), and (II) to be grown by two. (B <> C) will now reference (II)
437
+ # but they will not yet be linked to any of the items in it. It is therefore
438
+ # necessary to insert them directly after calling this method, or (II) will
439
+ # be left in an invalid state.
440
+ #
441
+ # Returns the last item of the chain.
442
+
443
+ private def extract_beginning_with(new_list = nil)
444
+ old_list = @list
445
+ # Count items and move them to the new list
446
+ last_item = self
447
+ count = 1 + loop.count do
448
+ last_item.list = new_list
449
+ last_item = last_item.next
450
+ end
451
+
452
+ # Make sure the old list is in a valid state
453
+ if old_list
454
+ if first?
455
+ old_list.send :clear
456
+ else
457
+ old_list.send :shrink, count
458
+ # Fix the links within in the list
459
+ @prev.next = last_item.next!
460
+ last_item.next!.prev = @prev
461
+ end
462
+ else
463
+ # Disconnect the item directly after the chain
464
+ @prev.next = nil unless first?
465
+ end
466
+
467
+ # Disconnect the chain from the list
468
+ @prev = last_item.next = nil
469
+
470
+ # Preemptivly tell the new list to grow
471
+ new_list.send :grow, count if new_list
472
+
473
+ last_item
474
+ end
475
+
476
+ # PRIVATE DANGEROUS METHOD. This method should never be called directly
477
+ # since it may leave the extracted item chain in an invalid state.
478
+ #
479
+ # This method extracts the item, together with the chain preceding it, from
480
+ # the list they are in (if any) and optionally facilitates moving them to a
481
+ # new list. See #extract_beginning_with for a description of the side
482
+ # effects from calling this method.
483
+ #
484
+ # Returns the first item in the chain.
485
+
486
+ private def extract_ending_with(new_list = nil)
487
+ old_list = @list
488
+ # Count items and move them to the new list
489
+ first_item = self
490
+ count = 1 + loop.count do
491
+ first_item.list = new_list
492
+ first_item = first_item.prev
493
+ end
494
+
495
+ # Make sure the old list is in a valid state
496
+ if old_list
497
+ if last?
498
+ old_list.send :clear
499
+ else
500
+ old_list.send :shrink, count
501
+ # Fix the links within in the list
502
+ @next.prev = first_item.prev!
503
+ first_item.prev!.next = @next
504
+ end
505
+ else
506
+ # Disconnect the item directly after the chain
507
+ @next.prev = nil unless last?
508
+ end
509
+
510
+ # Disconnect the chain from the list
511
+ first_item.prev = @next = nil
512
+
513
+ # Preemptivly tell the new list to grow
514
+ new_list.send :grow, count if new_list
515
+
516
+ first_item
517
+ end
387
518
  end
388
519
  end
data/lib/linked/list.rb CHANGED
@@ -306,6 +306,19 @@ module Linked
306
306
  private def shrink(n = 1)
307
307
  @item_count -= n
308
308
  end
309
+
310
+ # Private method to clear the list. Never call this method without also
311
+ # modifying the items in the list, as this operation leavs them in an
312
+ # inconsistant state. If the list items are kept, make sure to
313
+ # a) clear the `prev` pointer of the first item and
314
+ # b) clear the `next` pointer of the last item.
315
+
316
+ private def clear
317
+ head.send :next=, tail
318
+ tail.send :prev=, head
319
+
320
+ @item_count = 0
321
+ end
309
322
 
310
323
  # Protected helper method that returns the first n items, starting just
311
324
  # after item, given that there are items_left items left. Knowing the exact
@@ -370,5 +383,13 @@ module Linked
370
383
  rescue StopIteration
371
384
  arr.compact! || arr
372
385
  end
386
+
387
+ # This method is called whenever the module is included somewhere. In the
388
+ # special case when List is included in an Item the #item method must be
389
+ # changed to return self.
390
+
391
+ def self.included(klass)
392
+ klass.send(:define_method, :item) { self } if klass < Item
393
+ end
373
394
  end
374
395
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Linked
4
- VERSION = '0.2.0'
4
+ VERSION = '0.2.1'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: linked
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sebastian Lindberg
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-08-09 00:00:00.000000000 Z
11
+ date: 2016-08-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler