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.
@@ -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
@@ -0,0 +1,16 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ *.bundle
10
+ *.so
11
+ *.o
12
+ *.a
13
+ mkmf.log
14
+
15
+ # rspec failure tracking
16
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,6 @@
1
+ ---
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.7.2
6
+ before_install: gem install bundler -v 2.1.4
@@ -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
@@ -0,0 +1,8 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in d_heap.gemspec
4
+ gemspec
5
+
6
+ gem "rake", "~> 12.0"
7
+ gem "rake-compiler"
8
+ gem "rspec", "~> 3.0"
@@ -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
@@ -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.
@@ -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).
@@ -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]
@@ -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__)
@@ -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")
@@ -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")
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -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
@@ -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
+ }
@@ -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 */
@@ -0,0 +1,3 @@
1
+ require "mkmf"
2
+
3
+ create_makefile("d_heap/d_heap")
@@ -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
@@ -0,0 +1,3 @@
1
+ class DHeap
2
+ VERSION = "0.1.0"
3
+ 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: []