d_heap 0.1.0
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/.gitignore +16 -0
- data/.rspec +3 -0
- data/.travis.yml +6 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +37 -0
- data/LICENSE.txt +21 -0
- data/README.md +141 -0
- data/Rakefile +14 -0
- data/bin/console +14 -0
- data/bin/rake +29 -0
- data/bin/rspec +29 -0
- data/bin/setup +8 -0
- data/d_heap.gemspec +31 -0
- data/ext/d_heap/d_heap.c +219 -0
- data/ext/d_heap/d_heap.h +33 -0
- data/ext/d_heap/extconf.rb +3 -0
- data/lib/d_heap.rb +38 -0
- data/lib/d_heap/version.rb +3 -0
- metadata +68 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 585cdfa768be7f778b92fa500d952f4207b98ea46378d7289f2ff936bc1f06f0
|
4
|
+
data.tar.gz: d306234243d78a9dfd0ef9beb06e569ddd9915f2c733ebd9109953fc84584e11
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: dd379eecd4bc218b62729d2fdd39773ffbf1e248fb83eeffda910f439644ad5ed8588cf1e8fd4de807ca074f3165e073300d584eb64fae2c54aeed5206374cfc
|
7
|
+
data.tar.gz: 7fac571f7d14bb2a7b6d2773fe531a803474587a83508710c4898c15649117558aaa399151d5be3dd49f07afbb06ffd38fe55161e07e483cee729c4f1efdbb20
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
In the interest of fostering an open and welcoming environment, we as
|
6
|
+
contributors and maintainers pledge to making participation in our project and
|
7
|
+
our community a harassment-free experience for everyone, regardless of age, body
|
8
|
+
size, disability, ethnicity, gender identity and expression, level of experience,
|
9
|
+
nationality, personal appearance, race, religion, or sexual identity and
|
10
|
+
orientation.
|
11
|
+
|
12
|
+
## Our Standards
|
13
|
+
|
14
|
+
Examples of behavior that contributes to creating a positive environment
|
15
|
+
include:
|
16
|
+
|
17
|
+
* Using welcoming and inclusive language
|
18
|
+
* Being respectful of differing viewpoints and experiences
|
19
|
+
* Gracefully accepting constructive criticism
|
20
|
+
* Focusing on what is best for the community
|
21
|
+
* Showing empathy towards other community members
|
22
|
+
|
23
|
+
Examples of unacceptable behavior by participants include:
|
24
|
+
|
25
|
+
* The use of sexualized language or imagery and unwelcome sexual attention or
|
26
|
+
advances
|
27
|
+
* Trolling, insulting/derogatory comments, and personal or political attacks
|
28
|
+
* Public or private harassment
|
29
|
+
* Publishing others' private information, such as a physical or electronic
|
30
|
+
address, without explicit permission
|
31
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
32
|
+
professional setting
|
33
|
+
|
34
|
+
## Our Responsibilities
|
35
|
+
|
36
|
+
Project maintainers are responsible for clarifying the standards of acceptable
|
37
|
+
behavior and are expected to take appropriate and fair corrective action in
|
38
|
+
response to any instances of unacceptable behavior.
|
39
|
+
|
40
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
41
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
42
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
43
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
44
|
+
threatening, offensive, or harmful.
|
45
|
+
|
46
|
+
## Scope
|
47
|
+
|
48
|
+
This Code of Conduct applies both within project spaces and in public spaces
|
49
|
+
when an individual is representing the project or its community. Examples of
|
50
|
+
representing a project or community include using an official project e-mail
|
51
|
+
address, posting via an official social media account, or acting as an appointed
|
52
|
+
representative at an online or offline event. Representation of a project may be
|
53
|
+
further defined and clarified by project maintainers.
|
54
|
+
|
55
|
+
## Enforcement
|
56
|
+
|
57
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
58
|
+
reported by contacting the project team at nicholas.evans@gmail.com. All
|
59
|
+
complaints will be reviewed and investigated and will result in a response that
|
60
|
+
is deemed necessary and appropriate to the circumstances. The project team is
|
61
|
+
obligated to maintain confidentiality with regard to the reporter of an incident.
|
62
|
+
Further details of specific enforcement policies may be posted separately.
|
63
|
+
|
64
|
+
Project maintainers who do not follow or enforce the Code of Conduct in good
|
65
|
+
faith may face temporary or permanent repercussions as determined by other
|
66
|
+
members of the project's leadership.
|
67
|
+
|
68
|
+
## Attribution
|
69
|
+
|
70
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
71
|
+
available at [https://contributor-covenant.org/version/1/4][version]
|
72
|
+
|
73
|
+
[homepage]: https://contributor-covenant.org
|
74
|
+
[version]: https://contributor-covenant.org/version/1/4/
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
d_heap (0.1.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
diff-lcs (1.4.4)
|
10
|
+
rake (12.3.3)
|
11
|
+
rake-compiler (1.1.1)
|
12
|
+
rake
|
13
|
+
rspec (3.10.0)
|
14
|
+
rspec-core (~> 3.10.0)
|
15
|
+
rspec-expectations (~> 3.10.0)
|
16
|
+
rspec-mocks (~> 3.10.0)
|
17
|
+
rspec-core (3.10.0)
|
18
|
+
rspec-support (~> 3.10.0)
|
19
|
+
rspec-expectations (3.10.0)
|
20
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
21
|
+
rspec-support (~> 3.10.0)
|
22
|
+
rspec-mocks (3.10.0)
|
23
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
24
|
+
rspec-support (~> 3.10.0)
|
25
|
+
rspec-support (3.10.0)
|
26
|
+
|
27
|
+
PLATFORMS
|
28
|
+
ruby
|
29
|
+
|
30
|
+
DEPENDENCIES
|
31
|
+
d_heap!
|
32
|
+
rake (~> 12.0)
|
33
|
+
rake-compiler
|
34
|
+
rspec (~> 3.0)
|
35
|
+
|
36
|
+
BUNDLED WITH
|
37
|
+
2.1.4
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2020 nicholas a. evans
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
# DHeap
|
2
|
+
|
3
|
+
A fast _d_-ary heap implementation for ruby, useful in priority queues and graph
|
4
|
+
algorithms.
|
5
|
+
|
6
|
+
The _d_-ary heap data structure is a generalization of the binary heap, in which
|
7
|
+
the nodes have _d_ children instead of 2. This allows for "decrease priority"
|
8
|
+
operations to be performed more quickly with the tradeoff of slower delete
|
9
|
+
minimum. Additionally, _d_-ary heaps can have better memory cache behavior than
|
10
|
+
binary heaps, allowing them to run more quickly in practice despite slower
|
11
|
+
worst-case time complexity.
|
12
|
+
|
13
|
+
_TODO:_ In addition to a basic _d_-ary heap class (`DHeap`), this library
|
14
|
+
~~includes~~ _will include_ extensions to `Array`, allowing an Array to be
|
15
|
+
directly handled as a priority queue. These extension methods are meant to be
|
16
|
+
used similarly to how `#bsearch` and `#bsearch_index` might be used.
|
17
|
+
|
18
|
+
_TODO:_ Also included is `DHeap::Set`, which augments the basic heap with an
|
19
|
+
internal `Hash`, which maps a set of values to scores.
|
20
|
+
loosely inspired by go's timers. e.g: It lazily sifts its heap after deletion
|
21
|
+
and adjustments, to achieve faster average runtime for *add* and *cancel*
|
22
|
+
operations.
|
23
|
+
|
24
|
+
_TODO:_ Also included is `DHeap::Timers`, which contains some features that are
|
25
|
+
loosely inspired by go's timers. e.g: It lazily sifts its heap after deletion
|
26
|
+
and adjustments, to achieve faster average runtime for *add* and *cancel*
|
27
|
+
operations.
|
28
|
+
|
29
|
+
## Motivation
|
30
|
+
|
31
|
+
Ruby's Array class comes with some helpful methods for maintaining a sorted
|
32
|
+
array, by combining `#bsearch_index` with `#insert`. With certain insert/remove
|
33
|
+
workloads that can perform very well, but in the worst-case an insert or delete
|
34
|
+
can result in O(n), since it may need to memcopy a significant portion of the
|
35
|
+
array. Knowing that priority queues are usually implemented with a heap, and
|
36
|
+
that the heap is a relatively simple data structure, I set out to replace my
|
37
|
+
`#bsearch_index` and `#insert` code with a one. I was surprised to find that,
|
38
|
+
at least under certain benchmarks, my ruby Heap implementation was tied with or
|
39
|
+
slower than inserting into a fully sorted array. On the one hand, this is a
|
40
|
+
testament to ruby's fine-tuned Array implementation. On the other hand, it
|
41
|
+
seemed like a heap implementated in C should easily match the speed of ruby's
|
42
|
+
bsearch + insert.
|
43
|
+
|
44
|
+
Additionally, I was inspired by reading go's "timer.go" implementation to
|
45
|
+
experiment with a 4-ary heap, instead of the traditional binary heap. In the
|
46
|
+
case of timers, new timers are usually scheduled to run after most of the
|
47
|
+
existing timers and timers are usually canceled before they have a chance to
|
48
|
+
run. While a binary heap holds 50% of its elements in its last layer, 75% of a
|
49
|
+
4-ary heap will have no children. That diminishes the extra comparison
|
50
|
+
overhead during sift-down.
|
51
|
+
|
52
|
+
## Installation
|
53
|
+
|
54
|
+
Add this line to your application's Gemfile:
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
gem 'd_heap'
|
58
|
+
```
|
59
|
+
|
60
|
+
And then execute:
|
61
|
+
|
62
|
+
$ bundle install
|
63
|
+
|
64
|
+
Or install it yourself as:
|
65
|
+
|
66
|
+
$ gem install d_heap
|
67
|
+
|
68
|
+
## Usage
|
69
|
+
|
70
|
+
The simplest way to use it is simply with `#push` and `#pop`. Push will
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
require "d_heap"
|
74
|
+
|
75
|
+
heap = DHeap.new # defaults to a 4-ary heap
|
76
|
+
|
77
|
+
# storing [time, task] tuples
|
78
|
+
heap << [Time.now + 5*60, Task.new(1)]
|
79
|
+
heap << [Time.now + 30, Task.new(2)]
|
80
|
+
heap << [Time.now + 60, Task.new(3)]
|
81
|
+
heap << [Time.now + 5, Task.new(4)]
|
82
|
+
|
83
|
+
# peeking and popping (using last to get the task and ignore the time)
|
84
|
+
heap.pop.last # => Task[4]
|
85
|
+
heap.pop.last # => Task[2]
|
86
|
+
heap.peak.last # => Task[3]
|
87
|
+
heap.pop.last # => Task[3]
|
88
|
+
heap.pop.last # => Task[1]
|
89
|
+
```
|
90
|
+
|
91
|
+
Read the `rdoc` for more detailed documentation and examples.
|
92
|
+
|
93
|
+
## Benchmarks
|
94
|
+
|
95
|
+
_TODO: put benchmarks here._
|
96
|
+
|
97
|
+
## Alternative data structures
|
98
|
+
|
99
|
+
Depending on what you're doing, maintaining a sorted `Array` using
|
100
|
+
`#bsearch_index` and `#insert` might be faster!
|
101
|
+
|
102
|
+
If it is important to be able to quickly enumerate the set or find the ranking
|
103
|
+
of values in it, then you probably want to use a self-balancing binary search
|
104
|
+
tree (e.g. a red-black tree) or a skip-list.
|
105
|
+
|
106
|
+
A Hashed Timing Wheel or Heirarchical Timing Wheels (or some variant in that
|
107
|
+
family of data structures) can be constructed to have effectively O(1) running
|
108
|
+
time in most cases. However, the implementation for that data structure is much
|
109
|
+
more complex than a heap. If a 4-ary heap is good enough for go's timers,
|
110
|
+
it should be suitable for many use cases.
|
111
|
+
|
112
|
+
## Development
|
113
|
+
|
114
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
115
|
+
`rake spec` to run the tests. You can also run `bin/console` for an interactive
|
116
|
+
prompt that will allow you to experiment.
|
117
|
+
|
118
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To
|
119
|
+
release a new version, update the version number in `version.rb`, and then run
|
120
|
+
`bundle exec rake release`, which will create a git tag for the version, push
|
121
|
+
git commits and tags, and push the `.gem` file to
|
122
|
+
[rubygems.org](https://rubygems.org).
|
123
|
+
|
124
|
+
## Contributing
|
125
|
+
|
126
|
+
Bug reports and pull requests are welcome on GitHub at
|
127
|
+
https://github.com/nevans/d_heap. This project is intended to be a safe,
|
128
|
+
welcoming space for collaboration, and contributors are expected to adhere to
|
129
|
+
the [code of
|
130
|
+
conduct](https://github.com/nevans/d_heap/blob/master/CODE_OF_CONDUCT.md).
|
131
|
+
|
132
|
+
## License
|
133
|
+
|
134
|
+
The gem is available as open source under the terms of the [MIT
|
135
|
+
License](https://opensource.org/licenses/MIT).
|
136
|
+
|
137
|
+
## Code of Conduct
|
138
|
+
|
139
|
+
Everyone interacting in the DHeap project's codebases, issue trackers, chat
|
140
|
+
rooms and mailing lists is expected to follow the [code of
|
141
|
+
conduct](https://github.com/nevans/d_heap/blob/master/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rspec/core/rake_task"
|
3
|
+
|
4
|
+
RSpec::Core::RakeTask.new(:spec)
|
5
|
+
|
6
|
+
require "rake/extensiontask"
|
7
|
+
|
8
|
+
task :build => :compile
|
9
|
+
|
10
|
+
Rake::ExtensionTask.new("d_heap") do |ext|
|
11
|
+
ext.lib_dir = "lib/d_heap"
|
12
|
+
end
|
13
|
+
|
14
|
+
task :default => [:clobber, :compile, :spec]
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "d_heap"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/rake
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rake' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("rake", "rake")
|
data/bin/rspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rspec' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("rspec-core", "rspec")
|
data/bin/setup
ADDED
data/d_heap.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require_relative 'lib/d_heap/version'
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "d_heap"
|
5
|
+
spec.version = DHeap::VERSION
|
6
|
+
spec.authors = ["nicholas a. evans"]
|
7
|
+
spec.email = ["nicholas.evans@gmail.com"]
|
8
|
+
|
9
|
+
spec.summary = %q{A d-ary heap implementation, for priority queues}
|
10
|
+
spec.description = <<~DESC
|
11
|
+
A C extension implementation of a d-ary heap data structure, suitable for
|
12
|
+
use in e.g. priority queues or Djikstra's algorithm.
|
13
|
+
DESC
|
14
|
+
spec.homepage = "https://github.com/nevans/#{spec.name}"
|
15
|
+
spec.license = "MIT"
|
16
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
|
17
|
+
|
18
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
19
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
20
|
+
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/master/Changelog.md"
|
21
|
+
|
22
|
+
# Specify which files should be added to the gem when it is released.
|
23
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
24
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
25
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
26
|
+
end
|
27
|
+
spec.bindir = "exe"
|
28
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
29
|
+
spec.require_paths = ["lib"]
|
30
|
+
spec.extensions = ["ext/d_heap/extconf.rb"]
|
31
|
+
end
|
data/ext/d_heap/d_heap.c
ADDED
@@ -0,0 +1,219 @@
|
|
1
|
+
#include "d_heap.h"
|
2
|
+
|
3
|
+
ID id_ivar_a;
|
4
|
+
ID id_ivar_d;
|
5
|
+
|
6
|
+
#define DHEAP_Check_d_size(d) \
|
7
|
+
if (d < 2) { \
|
8
|
+
rb_raise(rb_eIndexError, "DHeap d=%d is too small", d); \
|
9
|
+
} \
|
10
|
+
if (d > DHEAP_MAX_D) { \
|
11
|
+
rb_raise(rb_eIndexError, "DHeap d=%d is too large", d); \
|
12
|
+
}
|
13
|
+
|
14
|
+
#define DHEAP_Check_Sift_Idx(sift_idx, last_idx) \
|
15
|
+
if (sift_idx < 0) { \
|
16
|
+
rb_raise(rb_eIndexError, "sift_idx %ld too small", sift_idx); \
|
17
|
+
} \
|
18
|
+
else if (last_idx < sift_idx) { \
|
19
|
+
rb_raise(rb_eIndexError, "sift_idx %ld too large", sift_idx); \
|
20
|
+
}
|
21
|
+
|
22
|
+
#define DHEAP_Load_Sift_Vals(heap_array, dval, idxval) \
|
23
|
+
Check_Type(dval, T_FIXNUM); \
|
24
|
+
int d = FIX2INT(dval); \
|
25
|
+
long sift_idx = NUM2LONG(idxval);
|
26
|
+
|
27
|
+
#define DHEAP_Check_Sift_Args(heap_array, d, sift_idx) \
|
28
|
+
DHEAP_Check_d_size(d); \
|
29
|
+
Check_Type(heap_array, T_ARRAY); \
|
30
|
+
long last_idx = RARRAY_LEN(heap_array) - 1; \
|
31
|
+
DHEAP_Check_Sift_Idx(sift_idx, last_idx); \
|
32
|
+
\
|
33
|
+
VALUE sift_val = rb_ary_entry(heap_array, sift_idx);
|
34
|
+
|
35
|
+
VALUE
|
36
|
+
dheap_ary_sift_up(VALUE heap_array, int d, long sift_idx) {
|
37
|
+
DHEAP_Check_Sift_Args(heap_array, d, sift_idx);
|
38
|
+
// sift it up to where it belongs
|
39
|
+
for (long parent_idx; 0 < sift_idx; sift_idx = parent_idx) {
|
40
|
+
parent_idx = (sift_idx - 1) / d;
|
41
|
+
VALUE parent_val = rb_ary_entry(heap_array, parent_idx);
|
42
|
+
|
43
|
+
// parent is smaller: heap is restored
|
44
|
+
if (CMP_LTE(parent_val, sift_val)) break;
|
45
|
+
|
46
|
+
// parent is larger: swap and continue sifting up
|
47
|
+
rb_ary_store(heap_array, sift_idx, parent_val);
|
48
|
+
rb_ary_store(heap_array, parent_idx, sift_val);
|
49
|
+
}
|
50
|
+
return LONG2NUM(sift_idx);
|
51
|
+
}
|
52
|
+
|
53
|
+
|
54
|
+
VALUE
|
55
|
+
dheap_ary_sift_down(VALUE heap_array, int d, long sift_idx) {
|
56
|
+
DHEAP_Check_Sift_Args(heap_array, d, sift_idx);
|
57
|
+
// iteratively sift it down to where it belongs
|
58
|
+
for (long child_idx; sift_idx < last_idx; sift_idx = child_idx) {
|
59
|
+
// find first child index, and break if we've reached the last layer
|
60
|
+
long child_idx0 = sift_idx * d + 1;
|
61
|
+
child_idx = child_idx0;
|
62
|
+
if (last_idx < child_idx0) break;
|
63
|
+
|
64
|
+
// find the min child (and its child_idx)
|
65
|
+
// requires "d" comparisons. (d - 1 to find min child; + 1 vs sift_val)
|
66
|
+
VALUE child_val = rb_ary_entry(heap_array, child_idx0);
|
67
|
+
for (int i = 1; i < d; i++) {
|
68
|
+
long sibling_idx = child_idx0 + i;
|
69
|
+
if (last_idx < sibling_idx) break;
|
70
|
+
|
71
|
+
VALUE sibling_val = rb_ary_entry(heap_array, sibling_idx);
|
72
|
+
|
73
|
+
if (CMP_LT(sibling_val, child_val)) {
|
74
|
+
child_val = sibling_val;
|
75
|
+
child_idx = sibling_idx;
|
76
|
+
}
|
77
|
+
}
|
78
|
+
|
79
|
+
// child is larger: heap is restored
|
80
|
+
if (CMP_GT(child_val, sift_val)) break;
|
81
|
+
|
82
|
+
// child is smaller: swap and continue sifting down
|
83
|
+
rb_ary_store(heap_array, sift_idx, child_val);
|
84
|
+
rb_ary_store(heap_array, child_idx, sift_val);
|
85
|
+
}
|
86
|
+
return LONG2NUM(sift_idx);
|
87
|
+
}
|
88
|
+
|
89
|
+
/*
|
90
|
+
* call-seq:
|
91
|
+
* DHeap.array_sift_up(heap_array, d, sift_idx)
|
92
|
+
*
|
93
|
+
* Treats +heap_array+ as a +d+-ary heap and sifts up from +sift_idx+ to restore
|
94
|
+
* the heap property.
|
95
|
+
*
|
96
|
+
* Time complexity: O(d log n / log d). If the average up shifted element sorts
|
97
|
+
* into the bottom layer (e.g. new timers), this can avg O(1).
|
98
|
+
*
|
99
|
+
*/
|
100
|
+
static VALUE
|
101
|
+
dheap_sift_up_s(VALUE unused, VALUE heap_array, VALUE dval, VALUE idxval) {
|
102
|
+
DHEAP_Load_Sift_Vals(heap_array, dval, idxval);
|
103
|
+
return dheap_ary_sift_up(heap_array, d, sift_idx);
|
104
|
+
}
|
105
|
+
|
106
|
+
/*
|
107
|
+
* call-seq:
|
108
|
+
* DHeap.array_sift_down(heap_array, d, sift_idx)
|
109
|
+
*
|
110
|
+
* Treats +heap_array+ as a +d+-ary heap and sifts down from +sift_idx+ to
|
111
|
+
* restore the heap property.
|
112
|
+
*
|
113
|
+
* Time complexity: O(d log n / log d). If the average down shifted element
|
114
|
+
* sorts into the bottom layer (e.g. canceled timers), this can avg O(1).
|
115
|
+
*
|
116
|
+
*/
|
117
|
+
static VALUE
|
118
|
+
dheap_sift_down_s(VALUE unused, VALUE heap_array, VALUE dval, VALUE idxval) {
|
119
|
+
DHEAP_Load_Sift_Vals(heap_array, dval, idxval);
|
120
|
+
return dheap_ary_sift_down(heap_array, d, sift_idx);
|
121
|
+
}
|
122
|
+
|
123
|
+
static VALUE
|
124
|
+
dheap_initialize(int argc, VALUE *argv, VALUE self) {
|
125
|
+
rb_check_arity(argc, 0, 1);
|
126
|
+
int d = DHEAP_DEFAULT_D;
|
127
|
+
if (argc) {
|
128
|
+
d = NUM2INT(argv[0]);
|
129
|
+
}
|
130
|
+
DHEAP_Check_d_size(d);
|
131
|
+
rb_ivar_set(self, id_ivar_d, INT2FIX(d));
|
132
|
+
rb_ivar_set(self, id_ivar_a, rb_ary_new());
|
133
|
+
return self;
|
134
|
+
}
|
135
|
+
|
136
|
+
static inline VALUE dheap_get_a(VALUE dheap) { return rb_ivar_get(dheap, id_ivar_a); }
|
137
|
+
static inline VALUE dheap_get_d(VALUE dheap) { return rb_ivar_get(dheap, id_ivar_d); }
|
138
|
+
|
139
|
+
static VALUE dheap_a(VALUE dheap) { return dheap_get_a(dheap); }
|
140
|
+
static VALUE dheap_d(VALUE dheap) { return dheap_get_d(dheap); }
|
141
|
+
|
142
|
+
/*
|
143
|
+
* Push val onto the end of the heap, then sift up to maintain heap property.
|
144
|
+
*
|
145
|
+
* Returns the index of the value's final position.
|
146
|
+
*
|
147
|
+
*/
|
148
|
+
static VALUE
|
149
|
+
dheap_push(VALUE dheap, VALUE val) {
|
150
|
+
VALUE heap_a = dheap_get_a(dheap);
|
151
|
+
VALUE heap_d = dheap_get_d(dheap);
|
152
|
+
rb_ary_push(heap_a, val);
|
153
|
+
long last_idx = RARRAY_LEN(heap_a) - 1;
|
154
|
+
return dheap_sift_up_s(Qnil, heap_a, heap_d, LONG2NUM(last_idx));
|
155
|
+
}
|
156
|
+
|
157
|
+
/*
|
158
|
+
* Push val onto the end of the heap, then sift up to maintain heap property.
|
159
|
+
*
|
160
|
+
* Time complexity: O(d log n / log d).
|
161
|
+
*
|
162
|
+
* Returns +self+.
|
163
|
+
*
|
164
|
+
*/
|
165
|
+
static VALUE
|
166
|
+
dheap_left_shift(VALUE dheap, VALUE val) {
|
167
|
+
dheap_push(dheap, val);
|
168
|
+
return dheap;
|
169
|
+
}
|
170
|
+
|
171
|
+
/*
|
172
|
+
* Pops the minimum value from the top of the heap, sifting down to maintain
|
173
|
+
* heap property.
|
174
|
+
*
|
175
|
+
* Time complexity: O(log n / log d).
|
176
|
+
*
|
177
|
+
*/
|
178
|
+
static VALUE
|
179
|
+
dheap_pop(VALUE dheap) {
|
180
|
+
VALUE heap_a = dheap_get_a(dheap);
|
181
|
+
VALUE heap_d = dheap_get_d(dheap);
|
182
|
+
long last_idx = RARRAY_LEN(heap_a) - 1;
|
183
|
+
|
184
|
+
/*
|
185
|
+
* short-circuit empty or nearly empty
|
186
|
+
*/
|
187
|
+
if (last_idx <= 0) return rb_ary_pop(heap_a);
|
188
|
+
|
189
|
+
/*
|
190
|
+
* swap with last, then sift down
|
191
|
+
*/
|
192
|
+
VALUE pop_val = rb_ary_entry(heap_a, 0);
|
193
|
+
VALUE sift_val = rb_ary_entry(heap_a, last_idx);
|
194
|
+
rb_ary_store(heap_a, 0, sift_val);
|
195
|
+
rb_ary_pop(heap_a);
|
196
|
+
dheap_sift_down_s(Qnil, heap_a, heap_d, LONG2NUM(0));
|
197
|
+
|
198
|
+
return pop_val;
|
199
|
+
}
|
200
|
+
|
201
|
+
void
|
202
|
+
Init_d_heap(void)
|
203
|
+
{
|
204
|
+
id_cmp = rb_intern_const("<=>");
|
205
|
+
id_ivar_a = rb_intern_const("ary");
|
206
|
+
id_ivar_d = rb_intern_const("d");
|
207
|
+
|
208
|
+
rb_cDHeap = rb_define_class("DHeap", rb_cObject);
|
209
|
+
rb_define_method(rb_cDHeap, "initialize", dheap_initialize, -1);
|
210
|
+
rb_define_method(rb_cDHeap, "d", dheap_d, 0);
|
211
|
+
rb_define_private_method(rb_cDHeap, "_ary_", dheap_a, 0);
|
212
|
+
|
213
|
+
rb_define_singleton_method(rb_cDHeap, "heap_sift_down", dheap_sift_down_s, 3);
|
214
|
+
rb_define_singleton_method(rb_cDHeap, "heap_sift_up", dheap_sift_up_s, 3);
|
215
|
+
|
216
|
+
rb_define_method(rb_cDHeap, "push", dheap_push, 1);
|
217
|
+
rb_define_method(rb_cDHeap, "<<", dheap_left_shift, 1);
|
218
|
+
rb_define_method(rb_cDHeap, "pop", dheap_pop, 0);
|
219
|
+
}
|
data/ext/d_heap/d_heap.h
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
#ifndef D_HEAP_H
|
2
|
+
#define D_HEAP_H 1
|
3
|
+
|
4
|
+
#include "ruby.h"
|
5
|
+
|
6
|
+
// This is somewhat a arbitary boundary, but it is highly unlikely that the
|
7
|
+
// gains from fewer levels can outweight doing this many comparisons per level.
|
8
|
+
// Since the comparisons will still be executed using <=> on ruby objects, it's
|
9
|
+
// likely they will be too slow to make any d > 8 worthwhile.
|
10
|
+
#define DHEAP_MAX_D 128
|
11
|
+
#define DHEAP_DEFAULT_D 4
|
12
|
+
|
13
|
+
#define CMP_LT(a, b) \
|
14
|
+
(rb_cmpint(rb_funcallv(a, id_cmp, 1, &b), a, b) < 0)
|
15
|
+
#define CMP_LTE(a, b) \
|
16
|
+
(rb_cmpint(rb_funcallv(a, id_cmp, 1, &b), a, b) <= 0)
|
17
|
+
#define CMP_GT(a, b) \
|
18
|
+
(rb_cmpint(rb_funcallv(a, id_cmp, 1, &b), a, b) > 0)
|
19
|
+
#define CMP_GTE(a, b) \
|
20
|
+
(rb_cmpint(rb_funcallv(a, id_cmp, 1, &b), a, b) >= 0)
|
21
|
+
|
22
|
+
VALUE rb_cDHeap;
|
23
|
+
ID id_cmp;
|
24
|
+
|
25
|
+
#define puts(v) { \
|
26
|
+
ID sym_puts = rb_intern("puts"); \
|
27
|
+
rb_funcall(rb_mKernel, sym_puts, 1, v); \
|
28
|
+
}
|
29
|
+
|
30
|
+
VALUE dheap_ary_sift_up(VALUE heap_array, int d, long sift_idx);
|
31
|
+
VALUE dheap_ary_sift_down(VALUE heap_array, int d, long sift_idx);
|
32
|
+
|
33
|
+
#endif /* D_HEAP_H */
|
data/lib/d_heap.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require "d_heap/d_heap"
|
2
|
+
require "d_heap/version"
|
3
|
+
|
4
|
+
class DHeap
|
5
|
+
|
6
|
+
def initialize_dup(other)
|
7
|
+
super
|
8
|
+
_ary_.replace(_ary_.dup)
|
9
|
+
end
|
10
|
+
|
11
|
+
def freeze
|
12
|
+
_ary_.freeze
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
def peek
|
17
|
+
_ary_[0]
|
18
|
+
end
|
19
|
+
|
20
|
+
def empty?
|
21
|
+
_ary_.empty?
|
22
|
+
end
|
23
|
+
|
24
|
+
def size
|
25
|
+
_ary_.size
|
26
|
+
end
|
27
|
+
|
28
|
+
def each_in_order
|
29
|
+
return to_enum(__method__) unless block_given?
|
30
|
+
heap = dup
|
31
|
+
yield val until heap.emptu?
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_a
|
35
|
+
_ary_.dup
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: d_heap
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- nicholas a. evans
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-12-23 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: |
|
14
|
+
A C extension implementation of a d-ary heap data structure, suitable for
|
15
|
+
use in e.g. priority queues or Djikstra's algorithm.
|
16
|
+
email:
|
17
|
+
- nicholas.evans@gmail.com
|
18
|
+
executables: []
|
19
|
+
extensions:
|
20
|
+
- ext/d_heap/extconf.rb
|
21
|
+
extra_rdoc_files: []
|
22
|
+
files:
|
23
|
+
- ".gitignore"
|
24
|
+
- ".rspec"
|
25
|
+
- ".travis.yml"
|
26
|
+
- CODE_OF_CONDUCT.md
|
27
|
+
- Gemfile
|
28
|
+
- Gemfile.lock
|
29
|
+
- LICENSE.txt
|
30
|
+
- README.md
|
31
|
+
- Rakefile
|
32
|
+
- bin/console
|
33
|
+
- bin/rake
|
34
|
+
- bin/rspec
|
35
|
+
- bin/setup
|
36
|
+
- d_heap.gemspec
|
37
|
+
- ext/d_heap/d_heap.c
|
38
|
+
- ext/d_heap/d_heap.h
|
39
|
+
- ext/d_heap/extconf.rb
|
40
|
+
- lib/d_heap.rb
|
41
|
+
- lib/d_heap/version.rb
|
42
|
+
homepage: https://github.com/nevans/d_heap
|
43
|
+
licenses:
|
44
|
+
- MIT
|
45
|
+
metadata:
|
46
|
+
homepage_uri: https://github.com/nevans/d_heap
|
47
|
+
source_code_uri: https://github.com/nevans/d_heap
|
48
|
+
changelog_uri: https://github.com/nevans/d_heap/blob/master/Changelog.md
|
49
|
+
post_install_message:
|
50
|
+
rdoc_options: []
|
51
|
+
require_paths:
|
52
|
+
- lib
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: 2.5.0
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
requirements: []
|
64
|
+
rubygems_version: 3.1.4
|
65
|
+
signing_key:
|
66
|
+
specification_version: 4
|
67
|
+
summary: A d-ary heap implementation, for priority queues
|
68
|
+
test_files: []
|