linked 0.2.0 → 0.2.1

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: 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