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