dlinked 0.1.8 → 0.1.9
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 +4 -4
- data/CODE_OF_CONDUCT.md +74 -0
- data/CONTRIBUTING.md +88 -0
- data/README.md +88 -0
- data/benchmark.rb +60 -36
- data/dlinked.gemspec +1 -0
- data/lib/d_linked/cache_list.rb +122 -93
- data/lib/d_linked/list.rb +167 -146
- data/lib/d_linked/version.rb +1 -1
- data/lru_cache_example.rb +152 -0
- metadata +19 -2
data/lib/d_linked/list.rb
CHANGED
|
@@ -3,16 +3,34 @@
|
|
|
3
3
|
require_relative 'list/node'
|
|
4
4
|
|
|
5
5
|
module DLinked
|
|
6
|
-
# A fast, lightweight doubly linked list
|
|
6
|
+
# A fast, lightweight, and standards-compliant doubly linked list.
|
|
7
|
+
#
|
|
8
|
+
# This class provides a memory-efficient and performant alternative to Ruby's
|
|
9
|
+
# standard `Array` for scenarios requiring frequent O(1) insertions or
|
|
10
|
+
# deletions from the ends of the list (e.g., queues, stacks).
|
|
11
|
+
#
|
|
12
|
+
# It includes the `Enumerable` module, allowing it to work seamlessly with
|
|
13
|
+
# standard Ruby collection methods like `map`, `select`, and `each`.
|
|
14
|
+
#
|
|
15
|
+
# @example Basic Usage
|
|
16
|
+
# list = DLinked::List.new
|
|
17
|
+
# list.append(20).prepend(10) # => [10, 20]
|
|
18
|
+
# list.shift # => 10
|
|
19
|
+
# list.to_a # => [20]
|
|
7
20
|
class List
|
|
8
21
|
include Enumerable
|
|
9
22
|
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
|
|
23
|
+
# @!attribute [r] size
|
|
24
|
+
# The number of elements in the list.
|
|
25
|
+
# @return [Integer] The count of elements.
|
|
13
26
|
attr_reader :size
|
|
14
27
|
alias length size
|
|
15
28
|
|
|
29
|
+
# Initializes a new, empty list.
|
|
30
|
+
#
|
|
31
|
+
# @example
|
|
32
|
+
# list = DLinked::List.new
|
|
33
|
+
# list.size # => 0
|
|
16
34
|
def initialize
|
|
17
35
|
@head = nil
|
|
18
36
|
@tail = nil
|
|
@@ -23,22 +41,34 @@ module DLinked
|
|
|
23
41
|
|
|
24
42
|
# Adds a new value to the beginning of the list (the head).
|
|
25
43
|
#
|
|
26
|
-
# This is an **O(1)** operation
|
|
44
|
+
# This is an **O(1)** operation.
|
|
45
|
+
#
|
|
46
|
+
# @example
|
|
47
|
+
# list = DLinked::List.new
|
|
48
|
+
# list.prepend(10)
|
|
49
|
+
# list.prepend(5)
|
|
50
|
+
# list.to_a # => [5, 10]
|
|
27
51
|
#
|
|
28
52
|
# @param value [Object] The value to store in the new node.
|
|
29
|
-
# @return [self]
|
|
53
|
+
# @return [self] The list instance, allowing for method chaining.
|
|
30
54
|
def prepend(value)
|
|
31
55
|
node = Node.new(value, nil, @head)
|
|
32
56
|
list_prepend_logic(node)
|
|
33
57
|
end
|
|
34
58
|
alias unshift prepend
|
|
35
|
-
|
|
59
|
+
|
|
36
60
|
# Adds a new value to the end of the list (the tail).
|
|
37
61
|
#
|
|
38
|
-
# This is an O(1) operation.
|
|
62
|
+
# This is an **O(1)** operation.
|
|
63
|
+
#
|
|
64
|
+
# @example
|
|
65
|
+
# list = DLinked::List.new
|
|
66
|
+
# list.append(10)
|
|
67
|
+
# list.append(20)
|
|
68
|
+
# list.to_a # => [10, 20]
|
|
39
69
|
#
|
|
40
70
|
# @param value [Object] The value to add to the list.
|
|
41
|
-
# @return [
|
|
71
|
+
# @return [self] The list instance, for method chaining.
|
|
42
72
|
def append(value)
|
|
43
73
|
node = Node.new(value, @tail, nil)
|
|
44
74
|
list_append_logic(node)
|
|
@@ -50,7 +80,13 @@ module DLinked
|
|
|
50
80
|
#
|
|
51
81
|
# This is an **O(1)** operation.
|
|
52
82
|
#
|
|
53
|
-
# @
|
|
83
|
+
# @example
|
|
84
|
+
# list = DLinked::List.new << 1 << 2
|
|
85
|
+
# list.shift # => 1
|
|
86
|
+
# list.shift # => 2
|
|
87
|
+
# list.shift # => nil
|
|
88
|
+
#
|
|
89
|
+
# @return [Object, nil] The value of the removed element, or `nil` if the list is empty.
|
|
54
90
|
def shift
|
|
55
91
|
return nil if empty?
|
|
56
92
|
|
|
@@ -63,9 +99,15 @@ module DLinked
|
|
|
63
99
|
|
|
64
100
|
# Removes the last element from the list (the tail) and returns its value.
|
|
65
101
|
#
|
|
66
|
-
# This is an **O(1)** operation
|
|
102
|
+
# This is an **O(1)** operation.
|
|
103
|
+
#
|
|
104
|
+
# @example
|
|
105
|
+
# list = DLinked::List.new << 1 << 2
|
|
106
|
+
# list.pop # => 2
|
|
107
|
+
# list.pop # => 1
|
|
108
|
+
# list.pop # => nil
|
|
67
109
|
#
|
|
68
|
-
# @return [Object, nil] The value of the removed element, or nil if the list is empty.
|
|
110
|
+
# @return [Object, nil] The value of the removed element, or `nil` if the list is empty.
|
|
69
111
|
def pop
|
|
70
112
|
return nil if empty?
|
|
71
113
|
|
|
@@ -76,20 +118,20 @@ module DLinked
|
|
|
76
118
|
value
|
|
77
119
|
end
|
|
78
120
|
|
|
79
|
-
# Returns the value of the element at the head
|
|
121
|
+
# Returns the value of the element at the head of the list without removing it.
|
|
80
122
|
#
|
|
81
123
|
# This is an **O(1)** operation.
|
|
82
124
|
#
|
|
83
|
-
# @return [Object, nil] The value of the first element, or nil if the list is empty.
|
|
125
|
+
# @return [Object, nil] The value of the first element, or `nil` if the list is empty.
|
|
84
126
|
def first
|
|
85
127
|
@head&.value
|
|
86
128
|
end
|
|
87
129
|
|
|
88
|
-
# Returns the value of the element at the tail
|
|
130
|
+
# Returns the value of the element at the tail of the list without removing it.
|
|
89
131
|
#
|
|
90
132
|
# This is an **O(1)** operation.
|
|
91
133
|
#
|
|
92
|
-
# @return [Object, nil] The value of the last element, or nil if the list is empty.
|
|
134
|
+
# @return [Object, nil] The value of the last element, or `nil` if the list is empty.
|
|
93
135
|
def last
|
|
94
136
|
@tail&.value
|
|
95
137
|
end
|
|
@@ -98,16 +140,16 @@ module DLinked
|
|
|
98
140
|
#
|
|
99
141
|
# This is an **O(1)** operation.
|
|
100
142
|
#
|
|
101
|
-
# @return [Boolean]
|
|
143
|
+
# @return [Boolean] `true` if the list is empty, `false` otherwise.
|
|
102
144
|
def empty?
|
|
103
145
|
@size.zero?
|
|
104
146
|
end
|
|
105
147
|
|
|
106
|
-
# Removes all elements from the list
|
|
148
|
+
# Removes all elements from the list.
|
|
107
149
|
#
|
|
108
|
-
# This is an **O(1)** operation
|
|
150
|
+
# This is an **O(1)** operation.
|
|
109
151
|
#
|
|
110
|
-
# @return [self]
|
|
152
|
+
# @return [self] The cleared list instance.
|
|
111
153
|
def clear
|
|
112
154
|
@head = nil
|
|
113
155
|
@tail = nil
|
|
@@ -117,13 +159,12 @@ module DLinked
|
|
|
117
159
|
|
|
118
160
|
# --- O(n) ENUMERATION & LOOKUP ---
|
|
119
161
|
|
|
120
|
-
# Iterates through the list, yielding the value of each element in order.
|
|
162
|
+
# Iterates through the list, yielding the value of each element in order from head to tail.
|
|
121
163
|
#
|
|
122
|
-
# This is an **O(n)** operation
|
|
164
|
+
# This is an **O(n)** operation.
|
|
123
165
|
#
|
|
124
166
|
# @yield [Object] The value of the current element.
|
|
125
|
-
# @return [self, Enumerator] Returns
|
|
126
|
-
# otherwise returns an Enumerator.
|
|
167
|
+
# @return [self, Enumerator] Returns `self` if a block is given, otherwise returns an `Enumerator`.
|
|
127
168
|
def each
|
|
128
169
|
return enum_for(:each) unless block_given?
|
|
129
170
|
|
|
@@ -135,14 +176,12 @@ module DLinked
|
|
|
135
176
|
self
|
|
136
177
|
end
|
|
137
178
|
|
|
138
|
-
# Iterates through the list in reverse order,
|
|
139
|
-
# starting from the tail and moving to the head.
|
|
179
|
+
# Iterates through the list in reverse order, from tail to head.
|
|
140
180
|
#
|
|
141
|
-
# This is an **O(n)** operation
|
|
181
|
+
# This is an **O(n)** operation.
|
|
142
182
|
#
|
|
143
183
|
# @yield [Object] The value of the current element.
|
|
144
|
-
# @return [self, Enumerator] Returns
|
|
145
|
-
# otherwise returns an Enumerator.
|
|
184
|
+
# @return [self, Enumerator] Returns `self` if a block is given, otherwise returns an `Enumerator`.
|
|
146
185
|
def reverse_each
|
|
147
186
|
return enum_for(:reverse_each) unless block_given?
|
|
148
187
|
|
|
@@ -156,10 +195,10 @@ module DLinked
|
|
|
156
195
|
|
|
157
196
|
# Finds the index of the first occurrence of a given value.
|
|
158
197
|
#
|
|
159
|
-
# This is an **O(n)** operation
|
|
198
|
+
# This is an **O(n)** operation.
|
|
160
199
|
#
|
|
161
200
|
# @param value [Object] The value to search for.
|
|
162
|
-
# @return [Integer, nil] The index of the first matching element, or nil if
|
|
201
|
+
# @return [Integer, nil] The index of the first matching element, or `nil` if not found.
|
|
163
202
|
def index(value)
|
|
164
203
|
current = @head
|
|
165
204
|
idx = 0
|
|
@@ -174,19 +213,18 @@ module DLinked
|
|
|
174
213
|
|
|
175
214
|
# Converts the linked list into a standard Ruby Array.
|
|
176
215
|
#
|
|
177
|
-
# This is an **O(n)** operation
|
|
178
|
-
# and allocating a new Array.
|
|
216
|
+
# This is an **O(n)** operation.
|
|
179
217
|
#
|
|
180
|
-
# @return [Array] A new Array containing all elements in order.
|
|
218
|
+
# @return [Array] A new `Array` containing all elements in order.
|
|
181
219
|
def to_a
|
|
182
220
|
map { |v| v }
|
|
183
221
|
end
|
|
184
222
|
|
|
185
|
-
# Returns a string representation of the list
|
|
223
|
+
# Returns a string representation of the list.
|
|
186
224
|
#
|
|
187
|
-
# This is an **O(n)** operation
|
|
225
|
+
# This is an **O(n)** operation.
|
|
188
226
|
#
|
|
189
|
-
# @return [String] The string representation (e.g., "[10, 20, 30]").
|
|
227
|
+
# @return [String] The string representation (e.g., `"[10, 20, 30]"`).
|
|
190
228
|
def to_s
|
|
191
229
|
"[#{to_a.join(', ')}]"
|
|
192
230
|
end
|
|
@@ -194,20 +232,22 @@ module DLinked
|
|
|
194
232
|
|
|
195
233
|
# --- O(n) ARRAY/SLICE COMPATIBILITY ---
|
|
196
234
|
|
|
197
|
-
#
|
|
198
|
-
#
|
|
199
|
-
#
|
|
200
|
-
#
|
|
201
|
-
#
|
|
202
|
-
#
|
|
203
|
-
#
|
|
204
|
-
#
|
|
205
|
-
#
|
|
206
|
-
#
|
|
207
|
-
#
|
|
208
|
-
#
|
|
209
|
-
#
|
|
210
|
-
#
|
|
235
|
+
# @overload [](index)
|
|
236
|
+
# Retrieves the element at a specific index.
|
|
237
|
+
# Traversal is optimized to start from the head or tail, whichever is closer.
|
|
238
|
+
# @param index [Integer] The index (positive or negative).
|
|
239
|
+
# @return [Object, nil] The value at the index, or `nil` if out of bounds.
|
|
240
|
+
#
|
|
241
|
+
# @overload [](start, length)
|
|
242
|
+
# Retrieves a slice of `length` elements starting at `start`.
|
|
243
|
+
# @param start [Integer] The starting index.
|
|
244
|
+
# @param length [Integer] The number of elements to retrieve.
|
|
245
|
+
# @return [DLinked::List, nil] A new list containing the slice, or `nil` if `start` is out of bounds.
|
|
246
|
+
#
|
|
247
|
+
# @overload [](range)
|
|
248
|
+
# Retrieves a slice using a `Range`.
|
|
249
|
+
# @param range [Range] The range of indices to retrieve.
|
|
250
|
+
# @return [DLinked::List] A new list containing the slice.
|
|
211
251
|
def [](*args)
|
|
212
252
|
# Case 1: Single Index Access (list[i])
|
|
213
253
|
if args.size == 1 && args[0].is_a?(Integer)
|
|
@@ -223,27 +263,24 @@ module DLinked
|
|
|
223
263
|
slice(*args) # Delegate to the robust slice method
|
|
224
264
|
end
|
|
225
265
|
|
|
226
|
-
#
|
|
227
|
-
#
|
|
228
|
-
#
|
|
229
|
-
#
|
|
230
|
-
#
|
|
231
|
-
#
|
|
232
|
-
#
|
|
233
|
-
#
|
|
234
|
-
#
|
|
235
|
-
#
|
|
236
|
-
#
|
|
237
|
-
#
|
|
238
|
-
#
|
|
239
|
-
#
|
|
240
|
-
#
|
|
241
|
-
#
|
|
242
|
-
#
|
|
243
|
-
#
|
|
244
|
-
# - `(range, replacement)` for range replacement.
|
|
245
|
-
# @return [Object, Array, nil] The value(s) assigned, or nil if the assignment failed
|
|
246
|
-
# due to an invalid out-of-bounds single index.
|
|
266
|
+
# @overload []=(index, value)
|
|
267
|
+
# Sets the value of an element at a single index.
|
|
268
|
+
# @param index [Integer] The index to modify.
|
|
269
|
+
# @param value [Object] The new value.
|
|
270
|
+
# @return [Object, nil] The assigned value, or `nil` if the index is out of bounds.
|
|
271
|
+
#
|
|
272
|
+
# @overload []=(start, length, value)
|
|
273
|
+
# Replaces a slice of `length` elements starting at `start` with a new value (or values).
|
|
274
|
+
# @param start [Integer] The starting index.
|
|
275
|
+
# @param length [Integer] The number of elements to replace.
|
|
276
|
+
# @param value [Object, Array<Object>] The new value(s).
|
|
277
|
+
# @return [Object, Array<Object>] The assigned value(s).
|
|
278
|
+
#
|
|
279
|
+
# @overload []=(range, value)
|
|
280
|
+
# Replaces a slice defined by a `Range` with a new value (or values).
|
|
281
|
+
# @param range [Range] The range of indices to replace.
|
|
282
|
+
# @param value [Object, Array<Object>] The new value(s).
|
|
283
|
+
# @return [Object, Array<Object>] The assigned value(s).
|
|
247
284
|
def []=(*args)
|
|
248
285
|
replacement = args.pop
|
|
249
286
|
|
|
@@ -251,20 +288,14 @@ module DLinked
|
|
|
251
288
|
if args.size == 1 && args[0].is_a?(Integer)
|
|
252
289
|
index = args[0]
|
|
253
290
|
index += @size if index.negative?
|
|
254
|
-
# Check bounds for simple assignment (Must be within 0 to size-1)
|
|
255
291
|
return nil unless index >= 0 && index < @size
|
|
256
292
|
|
|
257
|
-
# Simple assignment: O(n) lookup, O(1) set
|
|
258
293
|
node = find_node_at_index(index)
|
|
259
294
|
node.value = replacement
|
|
260
295
|
return replacement
|
|
261
|
-
|
|
262
|
-
# For out-of-bounds, Array compatibility is usually IndexError, but
|
|
263
|
-
# based on your design, we return nil
|
|
264
|
-
|
|
265
296
|
end
|
|
266
297
|
|
|
267
|
-
# 2. Handle Slice Replacement
|
|
298
|
+
# 2. Handle Slice Replacement
|
|
268
299
|
start_index, length = *args
|
|
269
300
|
|
|
270
301
|
if args.size == 1 && start_index.is_a?(Range)
|
|
@@ -285,20 +316,17 @@ module DLinked
|
|
|
285
316
|
|
|
286
317
|
replacement = Array(replacement)
|
|
287
318
|
|
|
288
|
-
# Find Boundaries
|
|
289
319
|
predecessor = start_index.positive? ? find_node_at_index(start_index - 1) : nil
|
|
290
320
|
current = predecessor ? predecessor.next : @head
|
|
291
321
|
|
|
292
322
|
deleted_count = 0
|
|
293
323
|
length.times do
|
|
294
324
|
break unless current
|
|
295
|
-
|
|
296
325
|
current = current.next
|
|
297
326
|
deleted_count += 1
|
|
298
327
|
end
|
|
299
328
|
successor = current
|
|
300
329
|
|
|
301
|
-
# Stage 1: DELETION (Relink the neighbors)
|
|
302
330
|
if predecessor
|
|
303
331
|
predecessor.next = successor
|
|
304
332
|
else
|
|
@@ -312,43 +340,37 @@ module DLinked
|
|
|
312
340
|
end
|
|
313
341
|
@size -= deleted_count
|
|
314
342
|
|
|
315
|
-
# Stage 2: INSERTION (Insert new nodes at the boundary)
|
|
316
343
|
insertion_point = predecessor
|
|
317
344
|
replacement.each do |value|
|
|
318
345
|
new_node = Node.new(value, insertion_point, successor)
|
|
319
|
-
|
|
320
346
|
if insertion_point
|
|
321
347
|
insertion_point.next = new_node
|
|
322
348
|
else
|
|
323
349
|
@head = new_node
|
|
324
350
|
end
|
|
325
|
-
|
|
326
351
|
insertion_point = new_node
|
|
327
352
|
@size += 1
|
|
328
353
|
end
|
|
329
354
|
|
|
330
|
-
# Stage 3: FINAL RELINKING (The last inserted node links back to the successor)
|
|
331
355
|
if successor
|
|
332
356
|
successor.prev = insertion_point
|
|
333
357
|
else
|
|
334
358
|
@tail = insertion_point
|
|
335
359
|
end
|
|
336
360
|
|
|
337
|
-
replacement
|
|
361
|
+
replacement
|
|
338
362
|
end
|
|
339
363
|
|
|
340
364
|
# Inserts a new element at the specified index.
|
|
341
365
|
#
|
|
342
|
-
#
|
|
343
|
-
#
|
|
344
|
-
# For all other valid indices, this is an **O(n)** operation as it requires traversal
|
|
345
|
-
# to find the insertion point.
|
|
346
|
-
#
|
|
347
|
-
# Supports negative indices, where list.insert(-1, value) inserts before the last element.
|
|
366
|
+
# This is an **O(n)** operation, unless inserting at the head (`index` = 0)
|
|
367
|
+
# or tail (`index` >= `size`), in which case it is **O(1)**.
|
|
348
368
|
#
|
|
349
|
-
# @param index [Integer] The index before which the new element
|
|
350
|
-
# @param value [Object] The value to
|
|
351
|
-
# @return [
|
|
369
|
+
# @param index [Integer] The index before which to insert the new element.
|
|
370
|
+
# @param value [Object] The value to insert.
|
|
371
|
+
# @return [self] The list instance for method chaining.
|
|
372
|
+
# @see #prepend
|
|
373
|
+
# @see #append
|
|
352
374
|
def insert(index, value)
|
|
353
375
|
if index.negative?
|
|
354
376
|
index += @size
|
|
@@ -358,12 +380,8 @@ module DLinked
|
|
|
358
380
|
return prepend(value) if index <= 0
|
|
359
381
|
return append(value) if index >= @size
|
|
360
382
|
|
|
361
|
-
# Find the node to insert BEFORE
|
|
362
383
|
current = find_node_at_index(index)
|
|
363
|
-
|
|
364
384
|
new_node = Node.new(value, current.prev, current)
|
|
365
|
-
|
|
366
|
-
# Insert before current node (O(1) linking)
|
|
367
385
|
current.prev.next = new_node
|
|
368
386
|
current.prev = new_node
|
|
369
387
|
|
|
@@ -371,13 +389,12 @@ module DLinked
|
|
|
371
389
|
self
|
|
372
390
|
end
|
|
373
391
|
|
|
374
|
-
# Deletes the *first* node that matches the given value
|
|
392
|
+
# Deletes the *first* node that matches the given value.
|
|
375
393
|
#
|
|
376
|
-
# This is an **O(n)** operation
|
|
377
|
-
# However, once the node is found, the relinking operation is O(1).
|
|
394
|
+
# This is an **O(n)** operation.
|
|
378
395
|
#
|
|
379
396
|
# @param value [Object] The value to search for and delete.
|
|
380
|
-
# @return [Object, nil] The value of the deleted element, or nil if
|
|
397
|
+
# @return [Object, nil] The value of the deleted element, or `nil` if not found.
|
|
381
398
|
def delete(value)
|
|
382
399
|
current = @head
|
|
383
400
|
while current
|
|
@@ -390,13 +407,13 @@ module DLinked
|
|
|
390
407
|
nil
|
|
391
408
|
end
|
|
392
409
|
|
|
393
|
-
#
|
|
410
|
+
# Appends the elements of another list to this one (destructive).
|
|
394
411
|
#
|
|
395
|
-
# This is an **O(
|
|
396
|
-
# every element of the other list into the current list structure.
|
|
412
|
+
# This is an **O(k)** operation, where `k` is the size of the `other` list.
|
|
397
413
|
#
|
|
398
|
-
# @param other [
|
|
399
|
-
# @return [self]
|
|
414
|
+
# @param other [#each] An enumerable object to append.
|
|
415
|
+
# @return [self] The modified list instance.
|
|
416
|
+
# @raise [TypeError] if `other` is not enumerable.
|
|
400
417
|
def concat(other)
|
|
401
418
|
raise TypeError, "can't convert #{other.class} into DLinked::List" unless other.respond_to?(:each)
|
|
402
419
|
return self if other.empty?
|
|
@@ -405,14 +422,13 @@ module DLinked
|
|
|
405
422
|
self
|
|
406
423
|
end
|
|
407
424
|
|
|
408
|
-
# Returns a new
|
|
425
|
+
# Returns a new list by concatenating this list with another (non-destructive).
|
|
409
426
|
#
|
|
410
|
-
#
|
|
411
|
-
#
|
|
412
|
-
# as both must be traversed and copied into the new list.
|
|
427
|
+
# The complexity is **O(n + k)**, where `n` is the size of this list and `k`
|
|
428
|
+
# is the size of the `other` list.
|
|
413
429
|
#
|
|
414
|
-
# @param other [
|
|
415
|
-
# @return [DLinked::List] A new list containing all elements from both
|
|
430
|
+
# @param other [#each] The enumerable object to concatenate.
|
|
431
|
+
# @return [DLinked::List] A new list containing all elements from both.
|
|
416
432
|
def +(other)
|
|
417
433
|
new_list = self.class.new
|
|
418
434
|
each { |value| new_list.append(value) }
|
|
@@ -420,41 +436,37 @@ module DLinked
|
|
|
420
436
|
new_list
|
|
421
437
|
end
|
|
422
438
|
|
|
423
|
-
#
|
|
424
|
-
#
|
|
425
|
-
#
|
|
426
|
-
#
|
|
427
|
-
# 2. Range (e.g., list.slice(1..3))
|
|
439
|
+
# @overload slice(index)
|
|
440
|
+
# Extracts a single element at `index` and returns it in a new list.
|
|
441
|
+
# @param index [Integer] The index to retrieve.
|
|
442
|
+
# @return [DLinked::List, nil] A new list with one element, or `nil` if out of bounds.
|
|
428
443
|
#
|
|
429
|
-
#
|
|
430
|
-
#
|
|
444
|
+
# @overload slice(start, length)
|
|
445
|
+
# @param start [Integer] The starting index.
|
|
446
|
+
# @param length [Integer] The number of elements in the slice.
|
|
447
|
+
# @return [DLinked::List, nil] A new list, or `nil` if `start` is out of bounds.
|
|
431
448
|
#
|
|
432
|
-
# @
|
|
433
|
-
#
|
|
434
|
-
#
|
|
449
|
+
# @overload slice(range)
|
|
450
|
+
# @param range [Range] A range of indices.
|
|
451
|
+
# @return [DLinked::List] A new list.
|
|
435
452
|
def slice(start, length = nil)
|
|
436
|
-
# Handle Range Argument
|
|
437
453
|
if start.is_a?(Range) && length.nil?
|
|
438
454
|
range = start
|
|
439
455
|
start = range.begin
|
|
440
456
|
length = range.end - range.begin + (range.exclude_end? ? 0 : 1)
|
|
441
457
|
end
|
|
442
458
|
|
|
443
|
-
# 1. Resolve start index (including negative indices)
|
|
444
459
|
start += @size if start.negative?
|
|
445
|
-
|
|
446
460
|
return nil if start.negative? || start >= @size
|
|
447
461
|
|
|
448
462
|
if length.nil?
|
|
449
463
|
node = find_node_at_index(start)
|
|
450
464
|
new_list = self.class.new
|
|
451
465
|
new_list.append(node.value)
|
|
452
|
-
return new_list
|
|
466
|
+
return new_list
|
|
453
467
|
end
|
|
454
468
|
|
|
455
|
-
|
|
456
|
-
return nil if length.negative?
|
|
457
|
-
return List.new if length.zero?
|
|
469
|
+
return List.new if length.negative?
|
|
458
470
|
|
|
459
471
|
new_list = List.new
|
|
460
472
|
current = find_node_at_index(start)
|
|
@@ -469,19 +481,18 @@ module DLinked
|
|
|
469
481
|
new_list
|
|
470
482
|
end
|
|
471
483
|
|
|
472
|
-
# Extracts and removes a slice of elements
|
|
473
|
-
# containing the removed elements.
|
|
484
|
+
# Extracts and removes a slice of elements, returning the removed slice as a new list.
|
|
474
485
|
#
|
|
475
|
-
#
|
|
476
|
-
# 1. Start index and length (e.g., list.slice!(1, 2))
|
|
477
|
-
# 2. Range (e.g., list.slice!(1..3))
|
|
486
|
+
# This is an **O(n)** operation.
|
|
478
487
|
#
|
|
479
|
-
#
|
|
480
|
-
#
|
|
488
|
+
# @overload slice!(start, length)
|
|
489
|
+
# @param start [Integer] The starting index.
|
|
490
|
+
# @param length [Integer] The number of elements to remove.
|
|
491
|
+
# @return [DLinked::List, nil] A new list with the removed elements, or `nil` if the slice is empty.
|
|
481
492
|
#
|
|
482
|
-
# @
|
|
483
|
-
#
|
|
484
|
-
# or nil if the slice is empty
|
|
493
|
+
# @overload slice!(range)
|
|
494
|
+
# @param range [Range] The range of indices to remove.
|
|
495
|
+
# @return [DLinked::List, nil] A new list with the removed elements, or `nil` if the slice is empty.
|
|
485
496
|
def slice!(*args)
|
|
486
497
|
start_index, length = *args
|
|
487
498
|
|
|
@@ -504,7 +515,6 @@ module DLinked
|
|
|
504
515
|
|
|
505
516
|
length.times do
|
|
506
517
|
break unless current
|
|
507
|
-
|
|
508
518
|
current = current.next
|
|
509
519
|
end
|
|
510
520
|
successor = current
|
|
@@ -537,9 +547,12 @@ module DLinked
|
|
|
537
547
|
@size -= removed_count
|
|
538
548
|
result.empty? ? nil : result
|
|
539
549
|
end
|
|
550
|
+
|
|
540
551
|
protected
|
|
541
552
|
|
|
542
|
-
#
|
|
553
|
+
# Handles the O(1) pointer logic for prepending a node.
|
|
554
|
+
# @param node [DLinked::List::Node] The node to prepend.
|
|
555
|
+
# @return [self]
|
|
543
556
|
def list_prepend_logic(node)
|
|
544
557
|
@head.prev = node if @head
|
|
545
558
|
@head = node
|
|
@@ -548,6 +561,9 @@ module DLinked
|
|
|
548
561
|
self
|
|
549
562
|
end
|
|
550
563
|
|
|
564
|
+
# Handles the O(1) pointer logic for appending a node.
|
|
565
|
+
# @param node [DLinked::List::Node] The node to append.
|
|
566
|
+
# @return [self]
|
|
551
567
|
def list_append_logic(node)
|
|
552
568
|
@tail.next = node if @tail
|
|
553
569
|
@tail = node
|
|
@@ -558,7 +574,10 @@ module DLinked
|
|
|
558
574
|
|
|
559
575
|
private
|
|
560
576
|
|
|
561
|
-
#
|
|
577
|
+
# Finds the node at a valid index using an optimized O(n/2) search.
|
|
578
|
+
# @param index [Integer] The index to find.
|
|
579
|
+
# @return [DLinked::List::Node] The node at the specified index.
|
|
580
|
+
# @api private
|
|
562
581
|
def find_node_at_index(index)
|
|
563
582
|
# Optimization: Start from head or tail, whichever is closer
|
|
564
583
|
if index <= @size / 2
|
|
@@ -571,7 +590,9 @@ module DLinked
|
|
|
571
590
|
current
|
|
572
591
|
end
|
|
573
592
|
|
|
574
|
-
#
|
|
593
|
+
# Deletes a node from the list in O(1) time.
|
|
594
|
+
# @param node [DLinked::List::Node] The node to delete.
|
|
595
|
+
# @api private
|
|
575
596
|
def delete_node(node)
|
|
576
597
|
if node.prev
|
|
577
598
|
node.prev.next = node.next
|
data/lib/d_linked/version.rb
CHANGED