dlinked 0.1.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 +7 -0
- data/.github/workflows/release.yml +122 -0
- data/.gitignore +83 -0
- data/.rubocop.yml +45 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +221 -0
- data/Rakefile +10 -0
- data/benchmark.rb +54 -0
- data/dlinked.gemspec +37 -0
- data/example.rb +44 -0
- data/lib/d_linked/list/node.rb +42 -0
- data/lib/d_linked/list.rb +579 -0
- data/lib/d_linked/version.rb +5 -0
- data/lib/dlinked.rb +8 -0
- metadata +156 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 724380c942032cdc4308e6457a68996d0cffd35681a8a2f869e4f7e9f4938d59
|
|
4
|
+
data.tar.gz: 073d48b50858323574657b66386f14c3ec7137857a9dc16f3879579bcc09b002
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 260d3fefb326f3e41243b16875b38880d599706a3bcc497dd18583ddacf232700aa1e14e06033ad2bc21f01869c4c67df6fb29c5d5395a0e1fef4c26b591ab26
|
|
7
|
+
data.tar.gz: 79c0f06249f6c3df620248d4114ab3b18bdb3110c8c9e0284eee945362b5cc698c8848b5464e505ba4590ae64c93885086d6f350799f90f3ef27cd9a4d9f9ba6
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# .github/workflows/release.yml
|
|
2
|
+
|
|
3
|
+
name: Publish Ruby Gem
|
|
4
|
+
|
|
5
|
+
# This workflow runs when:
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
branches:
|
|
9
|
+
- main # Run tests and build on every push to main
|
|
10
|
+
# Run the build and release steps ONLY when a new tag is pushed (e.g., v0.1.0)
|
|
11
|
+
release:
|
|
12
|
+
types: [published]
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
test:
|
|
16
|
+
name: Run Tests & Build Gem
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
|
|
19
|
+
# Use a matrix to test across common Ruby versions (e.g., 3.0, 3.1, 3.2, 3.3)
|
|
20
|
+
strategy:
|
|
21
|
+
matrix:
|
|
22
|
+
ruby: ['3.1', '3.2', '3.3']
|
|
23
|
+
|
|
24
|
+
steps:
|
|
25
|
+
- name: Checkout Code
|
|
26
|
+
uses: actions/checkout@v4
|
|
27
|
+
|
|
28
|
+
- name: Set up Ruby
|
|
29
|
+
uses: ruby/setup-ruby@v1
|
|
30
|
+
with:
|
|
31
|
+
ruby-version: ${{ matrix.ruby }}
|
|
32
|
+
bundler-cache: true # Installs dependencies from Gemfile.lock
|
|
33
|
+
|
|
34
|
+
- name: Run Tests with SimpleCov
|
|
35
|
+
run: bundle exec rake test
|
|
36
|
+
|
|
37
|
+
- name: Upload Test Coverage Report (Artifact)
|
|
38
|
+
uses: actions/upload-artifact@v4
|
|
39
|
+
with:
|
|
40
|
+
name: coverage-report-${{ matrix.ruby }}
|
|
41
|
+
path: coverage/
|
|
42
|
+
|
|
43
|
+
# Only build the .gem file on the latest stable Ruby version
|
|
44
|
+
- name: Build Gem
|
|
45
|
+
if: matrix.ruby == '3.3'
|
|
46
|
+
run: bundle exec rake build
|
|
47
|
+
|
|
48
|
+
- name: Upload Gem Artifact
|
|
49
|
+
if: matrix.ruby == '3.3'
|
|
50
|
+
uses: actions/upload-artifact@v4
|
|
51
|
+
with:
|
|
52
|
+
name: dlinked-gem
|
|
53
|
+
path: pkg/
|
|
54
|
+
deploy_docs:
|
|
55
|
+
name: Deploy Documentation to GitHub Pages
|
|
56
|
+
runs-on: ubuntu-latest
|
|
57
|
+
needs: test # Ensure tests pass before deploying docs
|
|
58
|
+
|
|
59
|
+
# Run only when pushing to the main branch
|
|
60
|
+
if: github.ref == 'refs/heads/main'
|
|
61
|
+
|
|
62
|
+
permissions:
|
|
63
|
+
contents: write # Needed to push generated documentation to gh-pages branch
|
|
64
|
+
|
|
65
|
+
steps:
|
|
66
|
+
- name: Checkout Code
|
|
67
|
+
uses: actions/checkout@v4
|
|
68
|
+
|
|
69
|
+
- name: Set up Ruby
|
|
70
|
+
uses: ruby/setup-ruby@v1
|
|
71
|
+
with:
|
|
72
|
+
ruby-version: 3.3
|
|
73
|
+
bundler-cache: true
|
|
74
|
+
|
|
75
|
+
- name: Install YARD (if not in Gemfile)
|
|
76
|
+
run: gem install yard
|
|
77
|
+
|
|
78
|
+
- name: Generate YARD Documentation
|
|
79
|
+
run: yard doc
|
|
80
|
+
|
|
81
|
+
- name: Deploy to GitHub Pages
|
|
82
|
+
uses: peaceiris/actions-gh-pages@v3
|
|
83
|
+
with:
|
|
84
|
+
# This is the authentication token provided by GitHub
|
|
85
|
+
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
86
|
+
# The branch where YARD documentation will be pushed
|
|
87
|
+
publish_branch: gh-pages
|
|
88
|
+
# The directory to publish (generated by yard doc)
|
|
89
|
+
publish_dir: ./doc
|
|
90
|
+
publish:
|
|
91
|
+
name: Publish to RubyGems
|
|
92
|
+
runs-on: ubuntu-latest
|
|
93
|
+
needs: test
|
|
94
|
+
|
|
95
|
+
# This step only executes when a release is published (a tag is pushed)
|
|
96
|
+
if: github.event_name == 'release' && github.event.action == 'published'
|
|
97
|
+
|
|
98
|
+
steps:
|
|
99
|
+
- name: Checkout Code (Needed to access config)
|
|
100
|
+
uses: actions/checkout@v4
|
|
101
|
+
|
|
102
|
+
- name: Set up Ruby
|
|
103
|
+
uses: ruby/setup-ruby@v1
|
|
104
|
+
with:
|
|
105
|
+
ruby-version: 3.3 # Use the latest version for publishing
|
|
106
|
+
bundler-cache: false # We only need the runtime
|
|
107
|
+
|
|
108
|
+
- name: Download Gem Artifact
|
|
109
|
+
uses: actions/download-artifact@v4
|
|
110
|
+
with:
|
|
111
|
+
name: dlinked-gem
|
|
112
|
+
path: pkg/
|
|
113
|
+
|
|
114
|
+
- name: Set up RubyGems credentials
|
|
115
|
+
run: |
|
|
116
|
+
mkdir -p ~/.gem
|
|
117
|
+
echo ":rubygems_api_key: ${{ secrets.RUBYGEMS_API_KEY }}" > ~/.gem/credentials
|
|
118
|
+
chmod 0600 ~/.gem/credentials
|
|
119
|
+
|
|
120
|
+
- name: Push Gem to RubyGems.org
|
|
121
|
+
# The gem file is expected to be in the 'pkg/' directory
|
|
122
|
+
run: gem push pkg/*.gem
|
data/.gitignore
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
|
|
2
|
+
*.gem
|
|
3
|
+
*.rbc
|
|
4
|
+
/.config
|
|
5
|
+
/coverage/
|
|
6
|
+
/InstalledFiles
|
|
7
|
+
/pkg/
|
|
8
|
+
/spec/reports/
|
|
9
|
+
/spec/examples.txt
|
|
10
|
+
/test/tmp/
|
|
11
|
+
/test/version_tmp/
|
|
12
|
+
/tmp/
|
|
13
|
+
|
|
14
|
+
# Used by dotenv library to load environment variables.
|
|
15
|
+
.env
|
|
16
|
+
|
|
17
|
+
# Ignore Byebug command history file.
|
|
18
|
+
.byebug_history
|
|
19
|
+
|
|
20
|
+
## Specific to RubyMotion:
|
|
21
|
+
.dat*
|
|
22
|
+
.repl_history
|
|
23
|
+
build/
|
|
24
|
+
*.bridgesupport
|
|
25
|
+
build-iPhoneOS/
|
|
26
|
+
build-iPhoneSimulator/
|
|
27
|
+
|
|
28
|
+
## Specific to RubyMotion (use of CocoaPods):
|
|
29
|
+
#
|
|
30
|
+
# We recommend against adding the Pods directory to your .gitignore. However
|
|
31
|
+
# you should judge for yourself, the pros and cons are mentioned at:
|
|
32
|
+
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
|
33
|
+
#
|
|
34
|
+
# vendor/Pods/
|
|
35
|
+
|
|
36
|
+
## Documentation cache and generated files:
|
|
37
|
+
/.yardoc/
|
|
38
|
+
/_yardoc/
|
|
39
|
+
/doc/
|
|
40
|
+
/rdoc/
|
|
41
|
+
|
|
42
|
+
## Environment normalization:
|
|
43
|
+
/.bundle/
|
|
44
|
+
/vendor/bundle
|
|
45
|
+
/lib/bundler/man/
|
|
46
|
+
|
|
47
|
+
# for a library or gem, you might want to ignore these files since the code is
|
|
48
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
49
|
+
Gemfile.lock
|
|
50
|
+
.ruby-version
|
|
51
|
+
.ruby-gemset
|
|
52
|
+
|
|
53
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
|
54
|
+
.rvmrc
|
|
55
|
+
|
|
56
|
+
# Used by RuboCop. Remote config files pulled in from inherit_from directive.
|
|
57
|
+
.rubocop-https?--*
|
|
58
|
+
|
|
59
|
+
# IDE files
|
|
60
|
+
.idea/
|
|
61
|
+
.vscode/
|
|
62
|
+
*.swp
|
|
63
|
+
*.swo
|
|
64
|
+
*~
|
|
65
|
+
|
|
66
|
+
# OS files
|
|
67
|
+
.DS_Store
|
|
68
|
+
Thumbs.db
|
|
69
|
+
# --- Ruby Gem/Bundler Artifacts ---
|
|
70
|
+
# Ignores the compiled .gem file (which should be built on CI/release)
|
|
71
|
+
*.gem
|
|
72
|
+
# Ignores the cached extension files
|
|
73
|
+
/ext/**/tmp
|
|
74
|
+
/ext/**/objects
|
|
75
|
+
/ext/**/gems
|
|
76
|
+
|
|
77
|
+
# --- Test/Coverage Reports ---
|
|
78
|
+
# SimpleCov coverage output directory
|
|
79
|
+
/coverage
|
|
80
|
+
|
|
81
|
+
# --- Documentation Artifacts (YARD/RDoc) ---
|
|
82
|
+
# When you generate API documentation, it creates this folder
|
|
83
|
+
/doc
|
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# .rubocop.yml
|
|
2
|
+
|
|
3
|
+
# We'll use the default Rubocop configuration as a starting point.
|
|
4
|
+
inherit_gem:
|
|
5
|
+
rubocop-minitest:
|
|
6
|
+
- config/default.yml
|
|
7
|
+
|
|
8
|
+
require:
|
|
9
|
+
- rubocop-minitest # Good practice since you are using Minitest
|
|
10
|
+
|
|
11
|
+
AllCops:
|
|
12
|
+
# Exclude the gem build artifacts, configuration files, etc.
|
|
13
|
+
Exclude:
|
|
14
|
+
- 'dlinked-*.gem'
|
|
15
|
+
- 'vendor/**/*'
|
|
16
|
+
- 'bin/**/*'
|
|
17
|
+
- 'node_modules/**/*'
|
|
18
|
+
- 'pkg/**/*'
|
|
19
|
+
- 'Rakefile' # Often contains unique logic
|
|
20
|
+
- 'dlinked.gemspec'
|
|
21
|
+
|
|
22
|
+
# Target Ruby version is often set here
|
|
23
|
+
TargetRubyVersion: 3.1 # Set this to the version you are primarily developing on
|
|
24
|
+
|
|
25
|
+
# --- Specific Cops Configuration ---
|
|
26
|
+
|
|
27
|
+
Style/FrozenStringLiteralComment:
|
|
28
|
+
Enabled: true
|
|
29
|
+
|
|
30
|
+
Metrics/BlockLength:
|
|
31
|
+
# Allows longer blocks, often needed for test files
|
|
32
|
+
Exclude:
|
|
33
|
+
- 'test/**/*.rb'
|
|
34
|
+
- 'benchmark.rb'
|
|
35
|
+
Max: 100
|
|
36
|
+
|
|
37
|
+
Metrics/MethodLength:
|
|
38
|
+
# Your list.rb methods are complex due to assignment/slice logic
|
|
39
|
+
Exclude:
|
|
40
|
+
- 'lib/d_linked/list.rb'
|
|
41
|
+
Max: 20
|
|
42
|
+
|
|
43
|
+
# Allow documentation for public methods to be required
|
|
44
|
+
Documentation:
|
|
45
|
+
Enabled: false
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
|
|
2
|
+
MIT License
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2025 [Your Name]
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# DLinked
|
|
2
|
+
|
|
3
|
+
A fast, lightweight doubly linked list implementation for Ruby.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Lightweight**: Minimal memory footprint using optimized node class
|
|
8
|
+
- **Fast**: O(1) operations for insertion/deletion at both ends
|
|
9
|
+
- **Ruby-native**: Includes Enumerable for full integration with Ruby
|
|
10
|
+
- **Bidirectional**: Iterate forward or backward efficiently
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
Add this line to your application's Gemfile:
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
gem 'dlinked'
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Or install it yourself:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
gem install dlinked
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Test
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
bundle exec ruby test/test_d_linked_list.rb
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
### 1. Basic Initialization and O(1) Operations
|
|
36
|
+
Demonstrate creating the list, adding elements to both ends, and removing them quickly.
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
require 'dlinked'
|
|
41
|
+
|
|
42
|
+
# 1. Initialization
|
|
43
|
+
list = DLinked::List.new
|
|
44
|
+
list.size # => 0
|
|
45
|
+
|
|
46
|
+
# 2. O(1) Prepend (Add to Head)
|
|
47
|
+
list.prepend(20).prepend(10) # Using method chaining
|
|
48
|
+
# The list now looks like: [10, 20]
|
|
49
|
+
|
|
50
|
+
# 3. O(1) Append (Add to Tail)
|
|
51
|
+
list << 30
|
|
52
|
+
list.append(40)
|
|
53
|
+
# The list now looks like: [10, 20, 30, 40]
|
|
54
|
+
|
|
55
|
+
# 4. O(1) Removal
|
|
56
|
+
list.shift # => 10 (Removes from head)
|
|
57
|
+
list.pop # => 40 (Removes from tail)
|
|
58
|
+
# The list now looks like: [20, 30]
|
|
59
|
+
|
|
60
|
+
list.to_a # => [20, 30]
|
|
61
|
+
```
|
|
62
|
+
### 2. Array-Like Access and Assignment
|
|
63
|
+
Show users how the list behaves like a Ruby Array, utilizing the [] and []= methods you implemented.
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
list = DLinked::List.new
|
|
68
|
+
list << 'A' << 'B' << 'C' << 'D'
|
|
69
|
+
|
|
70
|
+
# 1. Access by Index
|
|
71
|
+
list[2] # => 'C'
|
|
72
|
+
list[-1] # => 'D' (Accessing from the tail)
|
|
73
|
+
|
|
74
|
+
# 2. Element Assignment (O(n) but familiar)
|
|
75
|
+
list[1] = 'B_NEW'
|
|
76
|
+
list.to_a # => ["A", "B_NEW", "C", "D"]
|
|
77
|
+
|
|
78
|
+
# 3. Slice/Range Access
|
|
79
|
+
list[1, 2].to_a # => ["B_NEW", "C"] (start at 1, length 2)
|
|
80
|
+
list[0..2].to_a # => ["A", "B_NEW", "C"] (using a Range)
|
|
81
|
+
|
|
82
|
+
# 4. Slice Replacement (Deletion and Insertion)
|
|
83
|
+
list[1, 2] = ['X', 'Y', 'Z'] # Replace two elements with three new elements
|
|
84
|
+
list.to_a # => ["A", "X", "Y", "Z", "D"]
|
|
85
|
+
list.size # => 5
|
|
86
|
+
```
|
|
87
|
+
### 3. Enumerable and Iteration
|
|
88
|
+
Demonstrate how it works seamlessly with standard Ruby collection methods.
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
```ruby
|
|
92
|
+
list = DLinked::List.new
|
|
93
|
+
list << 10 << 20 << 30 << 40
|
|
94
|
+
|
|
95
|
+
# Standard Enumerable methods work out of the box
|
|
96
|
+
list.map { |n| n * 2 } # => [20, 40, 60, 80]
|
|
97
|
+
list.select(&:even?) # => [10, 20, 30, 40]
|
|
98
|
+
|
|
99
|
+
# O(n) Insertion in the middle
|
|
100
|
+
list.insert(2, 25)
|
|
101
|
+
list.to_a # => [10, 20, 25, 30, 40]
|
|
102
|
+
|
|
103
|
+
# O(n) Deletion by value
|
|
104
|
+
list.delete(20)
|
|
105
|
+
list.to_a # => [10, 25, 30, 40]
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
### 4. Utility, Inspection, and Conversion Methods
|
|
110
|
+
This section covers the basic checks, conversions, and advanced destructive operations.
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
|
|
114
|
+
list = DLinked::List.new
|
|
115
|
+
list << 10 << 20 << 30
|
|
116
|
+
|
|
117
|
+
# --- Basic Inspection ---
|
|
118
|
+
|
|
119
|
+
list.size # => 3
|
|
120
|
+
list.length # => 3 (alias for size)
|
|
121
|
+
list.empty? # => false
|
|
122
|
+
DLinked::List.new.empty? # => true
|
|
123
|
+
|
|
124
|
+
list.first # => 10 (O(1))
|
|
125
|
+
list.last # => 30 (O(1))
|
|
126
|
+
|
|
127
|
+
list.to_a # => [10, 20, 30]
|
|
128
|
+
list.to_s # => "[10, 20, 30]"
|
|
129
|
+
list.inspect # => "[10, 20, 30]"
|
|
130
|
+
|
|
131
|
+
# --- Lookup ---
|
|
132
|
+
|
|
133
|
+
list.index(20) # => 1
|
|
134
|
+
list.index(99) # => nil (Value not found)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### 5. Advanced Insertion, Deletion, and Slicing
|
|
138
|
+
Demonstrate non-O(1) operations that are useful for list manipulation, including the destructive slice! method.
|
|
139
|
+
|
|
140
|
+
```ruby
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
list = DLinked::List.new
|
|
144
|
+
list << 'A' << 'B' << 'C' << 'D' << 'E'
|
|
145
|
+
|
|
146
|
+
# --- O(n) Insertion ---
|
|
147
|
+
|
|
148
|
+
# Insert at the middle (index 2)
|
|
149
|
+
list.insert(2, 'Z')
|
|
150
|
+
list.to_a # => ["A", "B", "Z", "C", "D", "E"]
|
|
151
|
+
list.insert(0, 'Start') # Same as prepend (O(1))
|
|
152
|
+
list.to_a # => ["Start", "A", "B", "Z", "C", "D", "E"]
|
|
153
|
+
|
|
154
|
+
# --- Deletion by Value ---
|
|
155
|
+
|
|
156
|
+
list.delete('Z') # => "Z" (Returns the deleted value)
|
|
157
|
+
list.to_a # => ["Start", "A", "B", "C", "D", "E"]
|
|
158
|
+
|
|
159
|
+
# --- Destructive Slicing (slice!) ---
|
|
160
|
+
|
|
161
|
+
# Extract and remove a slice of 2 elements, starting at index 1
|
|
162
|
+
removed_slice = list.slice!(1, 2)
|
|
163
|
+
list.to_a # => ["Start", "C", "D", "E"]
|
|
164
|
+
removed_slice.to_a # => ["A", "B"]
|
|
165
|
+
|
|
166
|
+
# Remove one element using a range (index 2)
|
|
167
|
+
list.slice!(2..2)
|
|
168
|
+
list.to_a # => ["Start", "C", "E"]
|
|
169
|
+
|
|
170
|
+
list.size # => 3
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### 6. Concatenation and Arithmetic
|
|
174
|
+
Demonstrate how lists can be combined.
|
|
175
|
+
|
|
176
|
+
```ruby
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
list1 = DLinked::List.new << 1 << 2
|
|
180
|
+
list2 = DLinked::List.new << 3 << 4
|
|
181
|
+
|
|
182
|
+
# Non-destructive concatenation (returns a new list)
|
|
183
|
+
new_list = list1 + list2
|
|
184
|
+
new_list.to_a # => [1, 2, 3, 4]
|
|
185
|
+
list1.to_a # => [1, 2] (list1 is unchanged)
|
|
186
|
+
|
|
187
|
+
# Destructive concatenation (modifies list1)
|
|
188
|
+
list1.concat(list2)
|
|
189
|
+
list1.to_a # => [1, 2, 3, 4]
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## ⚡ Performance Characteristics
|
|
193
|
+
This library is designed to offer the guaranteed performance benefits of a Doubly Linked List over a standard Ruby `Array` for certain operations.
|
|
194
|
+
|
|
195
|
+
| Operation | Method(s) | Complexity | Notes |
|
|
196
|
+
| - | - | - | - |
|
|
197
|
+
| End Insertion | append, prepend, push, unshift, << | $O(1)$ | Constant time, regardless of list size. |
|
|
198
|
+
| End Deletion | pop, shift | $O(1)$ | Constant time. |
|
|
199
|
+
| End Access | first, last | $O(1)$ | Constant time access to boundary values. |
|
|
200
|
+
| Middle Insertion/Deletion | insert, delete (by value), slice!, []= (slice) | $O(n)$ | Requires traversal to find the node. |
|
|
201
|
+
| Random Access/Search | [] (getter), index | $O(n)$ | Requires traversal; average time is $O(n/2)$. |
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
## 💾 Memory Usage
|
|
206
|
+
|
|
207
|
+
While memory usage is highly dependent on the objects stored, the overhead of the list structure itself is minimal and highly efficient:
|
|
208
|
+
|
|
209
|
+
* **Node Overhead:** Each node in the list uses approximately 40 bytes. This includes the object header, and three necessary pointers:
|
|
210
|
+
|
|
211
|
+
1. Pointer to the stored **value**
|
|
212
|
+
|
|
213
|
+
2. Pointer to the **next** node
|
|
214
|
+
|
|
215
|
+
3. Pointer to the **previous** node
|
|
216
|
+
|
|
217
|
+
* **Efficiency Advantage:** This structured overhead is often more memory-efficient than a large Ruby `Array` that requires constant reallocation and copying when its capacity is exceeded, especially if the `Array` is being modified frequently at the head.
|
|
218
|
+
|
|
219
|
+
## License
|
|
220
|
+
|
|
221
|
+
MIT License. See LICENSE.txt for details.
|
data/Rakefile
ADDED
data/benchmark.rb
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# benchmark.rb - Compare Struct vs Class performance
|
|
4
|
+
# Run with: ruby benchmark.rb
|
|
5
|
+
require 'benchmark'
|
|
6
|
+
|
|
7
|
+
# Struct version
|
|
8
|
+
NodeStruct = Struct.new(:value, :prev, :next)
|
|
9
|
+
|
|
10
|
+
# Class version
|
|
11
|
+
class NodeClass
|
|
12
|
+
attr_accessor :value, :prev, :next
|
|
13
|
+
|
|
14
|
+
def initialize(value, prev_node, next_node)
|
|
15
|
+
@value = value
|
|
16
|
+
@prev = prev_node
|
|
17
|
+
@next = next_node
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
N = 2_000_000
|
|
22
|
+
|
|
23
|
+
puts "Creating and accessing #{N} nodes:\n\n"
|
|
24
|
+
|
|
25
|
+
Benchmark.bm(20) do |x|
|
|
26
|
+
x.report('Class creation:') do
|
|
27
|
+
N.times { |i| NodeClass.new(i, nil, nil) }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
x.report('Struct creation:') do
|
|
31
|
+
N.times { |i| NodeStruct.new(i, nil, nil) }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Test access speed (the important part!)
|
|
35
|
+
struct_nodes = Array.new(1000) { |i| NodeStruct.new(i, nil, nil) }
|
|
36
|
+
class_nodes = Array.new(1000) { |i| NodeClass.new(i, nil, nil) }
|
|
37
|
+
|
|
38
|
+
x.report('Class access:') do
|
|
39
|
+
N.times do
|
|
40
|
+
node = class_nodes[rand(1000)]
|
|
41
|
+
v = node.value
|
|
42
|
+
node.value = v + 1
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
x.report('Struct access:') do
|
|
46
|
+
N.times do
|
|
47
|
+
node = struct_nodes[rand(1000)]
|
|
48
|
+
v = node.value
|
|
49
|
+
node.value = v + 1
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
puts "\nConclusion: Run this benchmark on your target Ruby version to decide!"
|
data/dlinked.gemspec
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
require_relative "lib/d_linked/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "dlinked"
|
|
7
|
+
spec.version = DLinked::VERSION
|
|
8
|
+
# spec.version = File.read(File.expand_path("lib/d_linked/version.rb")).scan(/VERSION = "([^"]+)"/).flatten.first
|
|
9
|
+
spec.authors = ["Daniele Frisanco"]
|
|
10
|
+
spec.email = ["daniele.frisanco@gmail.com"]
|
|
11
|
+
|
|
12
|
+
spec.summary = "A highly performant Doubly Linked List implementation for Ruby."
|
|
13
|
+
spec.description = "Provides a native Doubly Linked List data structure in Ruby, focusing on O(1) performance for head/tail operations and standard Enumerable compatibility."
|
|
14
|
+
spec.homepage = "https://github.com/danielefrisanco/dlinked"
|
|
15
|
+
spec.license = "MIT"
|
|
16
|
+
|
|
17
|
+
spec.required_ruby_version = ">= 2.7.0"
|
|
18
|
+
|
|
19
|
+
# --- Files to Include in the Gem ---
|
|
20
|
+
|
|
21
|
+
# Ensure all necessary files are included in the built gem
|
|
22
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
23
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|benchmark)/}) }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Pin the main file that gets loaded when someone 'require's the gem
|
|
27
|
+
spec.require_paths = ["lib"]
|
|
28
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
29
|
+
|
|
30
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
|
31
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
|
32
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
|
33
|
+
spec.add_development_dependency "rubocop", "~> 1.0"
|
|
34
|
+
spec.add_development_dependency "rubocop-minitest", "~> 0.16.0"
|
|
35
|
+
spec.add_development_dependency "simplecov", "~> 0.22"
|
|
36
|
+
spec.add_development_dependency "yard", "~> 0.9"
|
|
37
|
+
end
|
data/example.rb
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'dlinked' # Add this line!
|
|
4
|
+
|
|
5
|
+
list = DLinked::List.new
|
|
6
|
+
list.append(2).append(3).prepend(1)
|
|
7
|
+
puts "After operations: #{list}"
|
|
8
|
+
puts "Size: #{list.size}"
|
|
9
|
+
|
|
10
|
+
# More examples...
|
|
11
|
+
list << 10 << 20 << 30
|
|
12
|
+
puts "List: #{list}"
|
|
13
|
+
puts "First: #{list.first}, Last: #{list.last}"
|
|
14
|
+
|
|
15
|
+
list.each { |v| puts " Value: #{v}" }
|
|
16
|
+
if __FILE__ == $PROGRAM_NAME
|
|
17
|
+
list = DLinked::List.new
|
|
18
|
+
|
|
19
|
+
# Test append and prepend
|
|
20
|
+
list.append(2).append(3).prepend(1)
|
|
21
|
+
puts "After operations: #{list}" # [1, 2, 3]
|
|
22
|
+
puts "Size: #{list.size}" # 3
|
|
23
|
+
|
|
24
|
+
# Test pop and shift
|
|
25
|
+
puts "Pop: #{list.pop}" # 3
|
|
26
|
+
puts "Shift: #{list.shift}" # 1
|
|
27
|
+
puts "After pop/shift: #{list}" # [2]
|
|
28
|
+
|
|
29
|
+
# Test iteration
|
|
30
|
+
list.append(4).append(6).prepend(0)
|
|
31
|
+
puts "\nForward iteration:"
|
|
32
|
+
list.each { |v| puts " #{v}" }
|
|
33
|
+
|
|
34
|
+
puts "\nReverse iteration:"
|
|
35
|
+
list.reverse_each { |v| puts " #{v}" }
|
|
36
|
+
|
|
37
|
+
# Test enumerable methods
|
|
38
|
+
puts "\nSquared values: #{list.map { |v| v * v }}"
|
|
39
|
+
puts "Sum: #{list.sum}"
|
|
40
|
+
|
|
41
|
+
# Test delete
|
|
42
|
+
list.delete(2)
|
|
43
|
+
puts "After deleting 2: #{list}"
|
|
44
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DLinked
|
|
4
|
+
class List
|
|
5
|
+
# The Node class represents a single element in the Doubly Linked List.
|
|
6
|
+
# It holds the element's value and pointers to the next and previous nodes.
|
|
7
|
+
# Note: If this class is defined outside of DLinked::List, adjust the scope accordingly.
|
|
8
|
+
|
|
9
|
+
# Represents a single element within the Doubly Linked List.
|
|
10
|
+
#
|
|
11
|
+
# Each node maintains three critical pieces of data:
|
|
12
|
+
# 1. The actual value stored by the user.
|
|
13
|
+
# 2. A pointer to the next node in the list.
|
|
14
|
+
# 3. A pointer to the previous node in the list.
|
|
15
|
+
#
|
|
16
|
+
# This structure is the foundation of the list's O(1) performance for boundary operations.
|
|
17
|
+
class Node
|
|
18
|
+
# @!attribute [rw] value
|
|
19
|
+
# @return [Object] The actual data stored by the user in this node.
|
|
20
|
+
attr_accessor :value
|
|
21
|
+
|
|
22
|
+
# @!attribute [rw] next
|
|
23
|
+
# @return [DLinked::List::Node, nil] A pointer to the subsequent node in the list, or nil if this is the tail.
|
|
24
|
+
attr_accessor :next
|
|
25
|
+
|
|
26
|
+
# @!attribute [rw] prev
|
|
27
|
+
# @return [DLinked::List::Node, nil] A pointer to the preceding node in the list, or nil if this is the head.
|
|
28
|
+
attr_accessor :prev
|
|
29
|
+
|
|
30
|
+
# Initializes a new Node instance.
|
|
31
|
+
#
|
|
32
|
+
# @param value [Object, nil] The value to store in the node.
|
|
33
|
+
# @param prev [DLinked::List::Node, nil] The node preceding this one.
|
|
34
|
+
# @param next_node [DLinked::List::Node, nil] The node succeeding this one.
|
|
35
|
+
def initialize(value = nil, prev = nil, next_node = nil)
|
|
36
|
+
@value = value
|
|
37
|
+
@prev = prev
|
|
38
|
+
@next = next_node
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|