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 +4 -4
- data/lib/linked/item.rb +177 -46
- data/lib/linked/list.rb +21 -0
- data/lib/linked/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fefa402916a5d9db25add5e34a0d95cfbfe1440f
|
4
|
+
data.tar.gz: 3f17115bffeb8777ba904292f98c8fffbed1b34b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
#
|
11
|
-
#
|
12
|
-
#
|
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
|
-
#
|
197
|
-
#
|
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(
|
262
|
-
if
|
263
|
-
|
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
|
-
|
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
|
-
|
269
|
-
|
270
|
-
@next =
|
271
|
-
|
272
|
-
|
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(
|
301
|
-
if
|
302
|
-
|
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
|
-
|
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
|
-
|
308
|
-
|
309
|
-
@prev =
|
310
|
-
|
311
|
-
|
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
|
data/lib/linked/version.rb
CHANGED
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.
|
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-
|
11
|
+
date: 2016-08-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|