d_heap 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +26 -0
- data/.rubocop.yml +160 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +22 -1
- data/README.md +62 -36
- data/Rakefile +8 -2
- data/bin/console +1 -0
- data/bin/rubocop +29 -0
- data/d_heap.gemspec +8 -6
- data/ext/d_heap/d_heap.c +101 -54
- data/ext/d_heap/d_heap.h +38 -47
- data/ext/d_heap/extconf.rb +6 -0
- data/lib/d_heap.rb +12 -0
- data/lib/d_heap/version.rb +4 -1
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f549e01dd83eb6b48c1190443495a628ed1dd64ead7eb94851e661aef2607e14
|
4
|
+
data.tar.gz: 492ce5c17ace9ecc9deaccf8fcd47883835da28d4e5f32328aef60989186b2ff
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 85521dee7f2a9992980935756571e87afbe8ae13347b5b3fbad17b501b5709111972b98ad0c9e1fca6d318c4be20ce2983086dfd84f7c0e73636ac9e4f11f253
|
7
|
+
data.tar.gz: e1daac5b02fcc817b3c6c6a99395e3ca0b92f42bb14bd813fedbb3037eed698bda335c2898d5b0131b48fecd73e4ccf1615943a52287c36f73764b08bf8b1969
|
@@ -0,0 +1,26 @@
|
|
1
|
+
name: Ruby
|
2
|
+
|
3
|
+
on: [push,pull_request]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
build:
|
7
|
+
strategy:
|
8
|
+
fail-fast: false
|
9
|
+
matrix:
|
10
|
+
ruby: [2.5, 2.6, 2.7, 3.0]
|
11
|
+
os: [ubuntu, macos]
|
12
|
+
experimental: [false]
|
13
|
+
runs-on: ${{ matrix.os }}-latest
|
14
|
+
continue-on-error: ${{ matrix.experimental }}
|
15
|
+
steps:
|
16
|
+
- uses: actions/checkout@v2
|
17
|
+
- name: Set up Ruby
|
18
|
+
uses: ruby/setup-ruby@v1
|
19
|
+
with:
|
20
|
+
ruby-version: ${{ matrix.ruby }}
|
21
|
+
bundler-cache: true
|
22
|
+
- name: Run the default task
|
23
|
+
run: |
|
24
|
+
gem install bundler -v 2.2.3
|
25
|
+
bundle install
|
26
|
+
bundle exec rake
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
inherit_mode:
|
2
|
+
merge:
|
3
|
+
- Exclude
|
4
|
+
|
5
|
+
AllCops:
|
6
|
+
TargetRubyVersion: 2.5
|
7
|
+
NewCops: disable
|
8
|
+
Exclude:
|
9
|
+
- bin/rake
|
10
|
+
- bin/rspec
|
11
|
+
- bin/rubocop
|
12
|
+
|
13
|
+
###########################################################################
|
14
|
+
# rubocop defaults are simply WRONG about many rules... Sorry. It's true.
|
15
|
+
|
16
|
+
###########################################################################
|
17
|
+
# Layout: Alignment. I want these to work, I really do...
|
18
|
+
|
19
|
+
# I wish this worked with "table". but that goes wrong sometimes.
|
20
|
+
Layout/HashAlignment: { Enabled: false }
|
21
|
+
|
22
|
+
# This needs to be configurable so parenthesis calls are aligned with first
|
23
|
+
# parameter, and non-parenthesis calls are aligned with fixed indentation.
|
24
|
+
Layout/ParameterAlignment: { Enabled: false }
|
25
|
+
|
26
|
+
###########################################################################
|
27
|
+
# Layout: Empty lines
|
28
|
+
|
29
|
+
Layout/EmptyLineAfterGuardClause: { Enabled: false }
|
30
|
+
Layout/EmptyLineAfterMagicComment: { Enabled: true }
|
31
|
+
Layout/EmptyLineAfterMultilineCondition: { Enabled: false }
|
32
|
+
Layout/EmptyLines: { Enabled: true }
|
33
|
+
Layout/EmptyLinesAroundAccessModifier: { Enabled: true }
|
34
|
+
Layout/EmptyLinesAroundArguments: { Enabled: true }
|
35
|
+
Layout/EmptyLinesAroundBeginBody: { Enabled: true }
|
36
|
+
Layout/EmptyLinesAroundBlockBody: { Enabled: false }
|
37
|
+
Layout/EmptyLinesAroundExceptionHandlingKeywords: { Enabled: true }
|
38
|
+
Layout/EmptyLinesAroundMethodBody: { Enabled: true }
|
39
|
+
|
40
|
+
Layout/EmptyLineBetweenDefs:
|
41
|
+
Enabled: true
|
42
|
+
AllowAdjacentOneLineDefs: true
|
43
|
+
|
44
|
+
Layout/EmptyLinesAroundAttributeAccessor:
|
45
|
+
inherit_mode:
|
46
|
+
merge:
|
47
|
+
- AllowedMethods
|
48
|
+
Enabled: true
|
49
|
+
AllowedMethods:
|
50
|
+
- delegate
|
51
|
+
- def_delegator
|
52
|
+
- def_delegators
|
53
|
+
- def_instance_delegators
|
54
|
+
|
55
|
+
# "empty_lines_special" sometimes does the wrong thing and annoys me.
|
56
|
+
# But I've mostly learned to live with it... mostly. 🙁
|
57
|
+
|
58
|
+
Layout/EmptyLinesAroundClassBody:
|
59
|
+
Enabled: true
|
60
|
+
EnforcedStyle: empty_lines_special
|
61
|
+
|
62
|
+
Layout/EmptyLinesAroundModuleBody:
|
63
|
+
Enabled: true
|
64
|
+
EnforcedStyle: empty_lines_special
|
65
|
+
|
66
|
+
###########################################################################
|
67
|
+
# Layout: Space around, before, inside, etc
|
68
|
+
|
69
|
+
Layout/SpaceAroundEqualsInParameterDefault: { Enabled: false }
|
70
|
+
Layout/SpaceBeforeBlockBraces: { Enabled: false }
|
71
|
+
Layout/SpaceBeforeFirstArg: { Enabled: false }
|
72
|
+
Layout/SpaceInLambdaLiteral: { Enabled: false }
|
73
|
+
Layout/SpaceInsideArrayLiteralBrackets: { Enabled: false }
|
74
|
+
Layout/SpaceInsideHashLiteralBraces: { Enabled: false }
|
75
|
+
|
76
|
+
Layout/SpaceInsideBlockBraces:
|
77
|
+
EnforcedStyle: space
|
78
|
+
EnforcedStyleForEmptyBraces: space
|
79
|
+
SpaceBeforeBlockParameters: false
|
80
|
+
|
81
|
+
# I would enable this if it were a bit better at handling alignment.
|
82
|
+
Layout/ExtraSpacing:
|
83
|
+
Enabled: false
|
84
|
+
AllowForAlignment: true
|
85
|
+
AllowBeforeTrailingComments: true
|
86
|
+
|
87
|
+
###########################################################################
|
88
|
+
# Layout: Misc
|
89
|
+
|
90
|
+
Layout/LineLength:
|
91
|
+
Max: 90 # should stay under 80, but we'll allow a little wiggle-room
|
92
|
+
|
93
|
+
Layout/MultilineOperationIndentation: { Enabled: false }
|
94
|
+
|
95
|
+
Layout/MultilineMethodCallIndentation:
|
96
|
+
EnforcedStyle: indented
|
97
|
+
|
98
|
+
###########################################################################
|
99
|
+
# Lint and Naming: rubocop defaults are mostly good, but...
|
100
|
+
|
101
|
+
Lint/UnusedMethodArgument: { Enabled: false }
|
102
|
+
Naming/BinaryOperatorParameterName: { Enabled: false } # def /(denominator)
|
103
|
+
Naming/RescuedExceptionsVariableName: { Enabled: false }
|
104
|
+
|
105
|
+
###########################################################################
|
106
|
+
# Matrics:
|
107
|
+
|
108
|
+
# Although it may be better to split specs into multiple files...?
|
109
|
+
Metrics/BlockLength:
|
110
|
+
Exclude:
|
111
|
+
- "spec/**/*_spec.rb"
|
112
|
+
|
113
|
+
###########################################################################
|
114
|
+
# Style...
|
115
|
+
|
116
|
+
Style/AccessorGrouping: { Enabled: false }
|
117
|
+
Style/AsciiComments: { Enabled: false } # 👮 can't stop our 🎉🥳🎊🥳!
|
118
|
+
Style/EachWithObject: { Enabled: false }
|
119
|
+
Style/FormatStringToken: { Enabled: false }
|
120
|
+
Style/FloatDivision: { Enabled: false }
|
121
|
+
Style/Lambda: { Enabled: false }
|
122
|
+
Style/LineEndConcatenation: { Enabled: false }
|
123
|
+
Style/MixinGrouping: { Enabled: false }
|
124
|
+
Style/PerlBackrefs: { Enabled: false } # use occasionally/sparingly
|
125
|
+
Style/RescueStandardError: { Enabled: false }
|
126
|
+
Style/SingleLineMethods: { Enabled: false }
|
127
|
+
Style/StabbyLambdaParentheses: { Enabled: false }
|
128
|
+
|
129
|
+
# If rubocop had an option to only enforce this on constants and literals (e.g.
|
130
|
+
# strings, regexp, range), I'd agree.
|
131
|
+
#
|
132
|
+
# But if you are using it e.g. on method arguments of unknown type, in the same
|
133
|
+
# style that ruby uses it with grep, then you are doing exactly the right thing.
|
134
|
+
Style/CaseEquality: { Enabled: false }
|
135
|
+
|
136
|
+
# I'd enable if "require_parentheses_when_complex" considered unary '!' simple.
|
137
|
+
Style/TernaryParentheses:
|
138
|
+
EnforcedStyle: require_parentheses_when_complex
|
139
|
+
Enabled: false
|
140
|
+
|
141
|
+
Style/BlockDelimiters:
|
142
|
+
EnforcedStyle: semantic
|
143
|
+
AllowBracesOnProceduralOneLiners: true
|
144
|
+
|
145
|
+
Style/FormatString:
|
146
|
+
EnforcedStyle: percent
|
147
|
+
|
148
|
+
Style/StringLiterals:
|
149
|
+
Enabled: true
|
150
|
+
EnforcedStyle: double_quotes
|
151
|
+
|
152
|
+
Style/StringLiteralsInInterpolation:
|
153
|
+
Enabled: true
|
154
|
+
EnforcedStyle: double_quotes
|
155
|
+
|
156
|
+
Style/TrailingCommaInHashLiteral:
|
157
|
+
EnforcedStyleForMultiline: consistent_comma
|
158
|
+
|
159
|
+
Style/TrailingCommaInArrayLiteral:
|
160
|
+
EnforcedStyleForMultiline: consistent_comma
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,15 +1,22 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
d_heap (0.2.
|
4
|
+
d_heap (0.2.2)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
8
8
|
specs:
|
9
|
+
ast (2.4.1)
|
9
10
|
diff-lcs (1.4.4)
|
11
|
+
parallel (1.19.2)
|
12
|
+
parser (2.7.2.0)
|
13
|
+
ast (~> 2.4.1)
|
14
|
+
rainbow (3.0.0)
|
10
15
|
rake (13.0.3)
|
11
16
|
rake-compiler (1.1.1)
|
12
17
|
rake
|
18
|
+
regexp_parser (1.8.2)
|
19
|
+
rexml (3.2.3)
|
13
20
|
rspec (3.10.0)
|
14
21
|
rspec-core (~> 3.10.0)
|
15
22
|
rspec-expectations (~> 3.10.0)
|
@@ -23,6 +30,19 @@ GEM
|
|
23
30
|
diff-lcs (>= 1.2.0, < 2.0)
|
24
31
|
rspec-support (~> 3.10.0)
|
25
32
|
rspec-support (3.10.0)
|
33
|
+
rubocop (1.2.0)
|
34
|
+
parallel (~> 1.10)
|
35
|
+
parser (>= 2.7.1.5)
|
36
|
+
rainbow (>= 2.2.2, < 4.0)
|
37
|
+
regexp_parser (>= 1.8)
|
38
|
+
rexml
|
39
|
+
rubocop-ast (>= 1.0.1)
|
40
|
+
ruby-progressbar (~> 1.7)
|
41
|
+
unicode-display_width (>= 1.4.0, < 2.0)
|
42
|
+
rubocop-ast (1.1.1)
|
43
|
+
parser (>= 2.7.1.5)
|
44
|
+
ruby-progressbar (1.10.1)
|
45
|
+
unicode-display_width (1.7.0)
|
26
46
|
|
27
47
|
PLATFORMS
|
28
48
|
ruby
|
@@ -32,6 +52,7 @@ DEPENDENCIES
|
|
32
52
|
rake (~> 13.0)
|
33
53
|
rake-compiler
|
34
54
|
rspec (~> 3.10)
|
55
|
+
rubocop (~> 1.0)
|
35
56
|
|
36
57
|
BUNDLED WITH
|
37
58
|
2.2.3
|
data/README.md
CHANGED
@@ -8,46 +8,46 @@ the nodes have _d_ children instead of 2. This allows for "decrease priority"
|
|
8
8
|
operations to be performed more quickly with the tradeoff of slower delete
|
9
9
|
minimum. Additionally, _d_-ary heaps can have better memory cache behavior than
|
10
10
|
binary heaps, allowing them to run more quickly in practice despite slower
|
11
|
-
worst-case time complexity.
|
11
|
+
worst-case time complexity. In the worst case, a _d_-ary heap requires only
|
12
|
+
`O(log n / log d)` to push, with the tradeoff that pop is `O(d log n / log d)`.
|
12
13
|
|
13
|
-
|
14
|
-
|
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.
|
14
|
+
Although you should probably just stick with the default _d_ value of `4`, it
|
15
|
+
may be worthwhile to benchmark your specific scenario.
|
28
16
|
|
29
17
|
## Motivation
|
30
18
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
19
|
+
Sometimes you just need a priority queue, right? With a regular queue, you
|
20
|
+
expect "FIFO" behavior: first in, first out. With a priority queue, you push
|
21
|
+
with a score (or your elements are comparable), and you want to be able to
|
22
|
+
efficiently pop off the minimum (or maximum) element.
|
23
|
+
|
24
|
+
One obvious approach is to simply maintain an array in sorted order. And
|
25
|
+
ruby's Array class makes it simple to maintain a sorted array by combining
|
26
|
+
`#bsearch_index` with `#insert`. With certain insert/remove workloads that can
|
27
|
+
perform very well, but in the worst-case an insert or delete can result in O(n),
|
28
|
+
since `#insert` may need to `memcpy` or `memmove` a significant portion of the
|
29
|
+
array.
|
30
|
+
|
31
|
+
But the standard way to efficiently and simply solve this problem is using a
|
32
|
+
binary heap. Although it increases the time for `pop`, it converts the
|
33
|
+
amortized time per push + pop from `O(n)` to `O(d log n / log d)`.
|
34
|
+
|
35
|
+
I was surprised to find that, at least under certain benchmarks, my pure ruby
|
36
|
+
heap implementation was usually slower than inserting into a fully sorted
|
37
|
+
array. While this is a testament to ruby's fine-tuned Array implementationw, a
|
38
|
+
heap implementated in C should easily peform faster than `Array#insert`.
|
39
|
+
|
40
|
+
The biggest issue is that it just takes far too much time to call `<=>` from
|
41
|
+
ruby code: A sorted array only requires `log n / log 2` comparisons to insert
|
42
|
+
and no comparisons to pop. However a _d_-ary heap requires `log n / log d` to
|
43
|
+
insert plus an additional `d log n / log d` to pop. If your queue contains only
|
44
|
+
a few hundred items at once, the overhead of those extra calls to `<=>` is far
|
45
|
+
more than occasionally calling `memcpy`.
|
46
|
+
|
47
|
+
It's likely that MJIT will eventually make the C-extension completely
|
48
|
+
unnecessary. This is definitely hotspot code, and the basic ruby implementation
|
49
|
+
would work fine, if not for that `<=>` overhead. Until then... this gem gets
|
50
|
+
the job done.
|
51
51
|
|
52
52
|
## Installation
|
53
53
|
|
@@ -90,6 +90,32 @@ heap.pop.last # => Task[1]
|
|
90
90
|
|
91
91
|
Read the `rdoc` for more detailed documentation and examples.
|
92
92
|
|
93
|
+
## TODOs...
|
94
|
+
|
95
|
+
_TODO:_ In addition to a basic _d_-ary heap class (`DHeap`), this library
|
96
|
+
~~includes~~ _will include_ extensions to `Array`, allowing an Array to be
|
97
|
+
directly handled as a priority queue. These extension methods are meant to be
|
98
|
+
used similarly to how `#bsearch` and `#bsearch_index` might be used.
|
99
|
+
|
100
|
+
_TODO:_ Also ~~included is~~ _will include_ `DHeap::Set`, which augments the
|
101
|
+
basic heap with an internal `Hash`, which maps a set of values to scores.
|
102
|
+
loosely inspired by go's timers. e.g: It lazily sifts its heap after deletion
|
103
|
+
and adjustments, to achieve faster average runtime for *add* and *cancel*
|
104
|
+
operations.
|
105
|
+
|
106
|
+
_TODO:_ Also ~~included is~~ _will include_ `DHeap::Timers`, which contains some
|
107
|
+
features that are loosely inspired by go's timers. e.g: It lazily sifts its
|
108
|
+
heap after deletion and adjustments, to achieve faster average runtime for *add*
|
109
|
+
and *cancel* operations.
|
110
|
+
|
111
|
+
Additionally, I was inspired by reading go's "timer.go" implementation to
|
112
|
+
experiment with a 4-ary heap instead of the traditional binary heap. In the
|
113
|
+
case of timers, new timers are usually scheduled to run after most of the
|
114
|
+
existing timers. And timers are usually canceled before they have a chance to
|
115
|
+
run. While a binary heap holds 50% of its elements in its last layer, 75% of a
|
116
|
+
4-ary heap will have no children. That diminishes the extra comparison overhead
|
117
|
+
during sift-down.
|
118
|
+
|
93
119
|
## Benchmarks
|
94
120
|
|
95
121
|
_TODO: put benchmarks here._
|
data/Rakefile
CHANGED
@@ -1,14 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "bundler/gem_tasks"
|
2
4
|
require "rspec/core/rake_task"
|
3
5
|
|
4
6
|
RSpec::Core::RakeTask.new(:spec)
|
5
7
|
|
8
|
+
require "rubocop/rake_task"
|
9
|
+
|
10
|
+
RuboCop::RakeTask.new
|
11
|
+
|
6
12
|
require "rake/extensiontask"
|
7
13
|
|
8
|
-
task :
|
14
|
+
task build: :compile
|
9
15
|
|
10
16
|
Rake::ExtensionTask.new("d_heap") do |ext|
|
11
17
|
ext.lib_dir = "lib/d_heap"
|
12
18
|
end
|
13
19
|
|
14
|
-
task :
|
20
|
+
task default: %i[clobber compile spec rubocop]
|
data/bin/console
CHANGED
data/bin/rubocop
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 'rubocop' 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("rubocop", "rubocop")
|
data/d_heap.gemspec
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/d_heap/version"
|
2
4
|
|
3
5
|
Gem::Specification.new do |spec|
|
4
6
|
spec.name = "d_heap"
|
@@ -6,7 +8,7 @@ Gem::Specification.new do |spec|
|
|
6
8
|
spec.authors = ["nicholas a. evans"]
|
7
9
|
spec.email = ["nicholas.evans@gmail.com"]
|
8
10
|
|
9
|
-
spec.summary =
|
11
|
+
spec.summary = "A d-ary heap implementation, for priority queues"
|
10
12
|
spec.description = <<~DESC
|
11
13
|
A C extension implementation of a d-ary heap data structure, suitable for
|
12
14
|
use in e.g. priority queues or Djikstra's algorithm.
|
@@ -21,11 +23,11 @@ Gem::Specification.new do |spec|
|
|
21
23
|
|
22
24
|
# Specify which files should be added to the gem when it is released.
|
23
25
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
24
|
-
spec.files
|
25
|
-
`git ls-files -z`.split("\x0").reject {
|
26
|
-
|
26
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) {
|
27
|
+
`git ls-files -z`.split("\x0").reject {|f| f.match(%r{^(test|spec|features)/}) }
|
28
|
+
}
|
27
29
|
spec.bindir = "exe"
|
28
|
-
spec.executables = spec.files.grep(%r{^exe/}) {
|
30
|
+
spec.executables = spec.files.grep(%r{^exe/}) {|f| File.basename(f) }
|
29
31
|
spec.require_paths = ["lib"]
|
30
32
|
spec.extensions = ["ext/d_heap/extconf.rb"]
|
31
33
|
end
|
data/ext/d_heap/d_heap.c
CHANGED
@@ -25,10 +25,10 @@ ID id_ivar_d;
|
|
25
25
|
|
26
26
|
#define DHEAP_Check_d_size(d) \
|
27
27
|
if (d < 2) { \
|
28
|
-
rb_raise(
|
28
|
+
rb_raise(rb_eArgError, "DHeap d=%d is too small", d); \
|
29
29
|
} \
|
30
30
|
if (d > DHEAP_MAX_D) { \
|
31
|
-
rb_raise(
|
31
|
+
rb_raise(rb_eArgError, "DHeap d=%d is too large", d); \
|
32
32
|
}
|
33
33
|
|
34
34
|
#define DHEAP_Check_Sift_Idx(sift_index, last_index) \
|
@@ -51,33 +51,31 @@ ID id_ivar_d;
|
|
51
51
|
VALUE
|
52
52
|
dheap_ary_sift_up(VALUE heap_array, int d, long sift_index) {
|
53
53
|
DHEAP_Check_Sift_Args(heap_array, d, sift_index);
|
54
|
-
struct cmp_opt_data cmp_opt = { 0, 0 };
|
55
54
|
// sift it up to where it belongs
|
56
55
|
for (long parent_index; 0 < sift_index; sift_index = parent_index) {
|
57
|
-
|
56
|
+
debug(rb_sprintf("sift up(%"PRIsVALUE", %d, %ld)", heap_array, d, sift_index));
|
58
57
|
parent_index = IDX_PARENT(sift_index);
|
59
58
|
VALUE parent_score = DHEAP_SCORE(heap_array, parent_index);
|
60
59
|
|
61
60
|
// parent is smaller: heap is restored
|
62
|
-
if (CMP_LTE(parent_score, sift_score
|
61
|
+
if (CMP_LTE(parent_score, sift_score)) break;
|
63
62
|
|
64
63
|
// parent is larger: swap and continue sifting up
|
65
64
|
VALUE parent_value = DHEAP_VALUE(heap_array, parent_index);
|
66
65
|
DHEAP_ASSIGN(heap_array, sift_index, parent_score, parent_value);
|
67
66
|
DHEAP_ASSIGN(heap_array, parent_index, sift_score, sift_value);
|
68
67
|
}
|
69
|
-
|
68
|
+
debug(rb_sprintf("sifted (%"PRIsVALUE", %d, %ld)", heap_array, d, sift_index));
|
70
69
|
return LONG2NUM(sift_index);
|
71
70
|
}
|
72
71
|
|
73
72
|
VALUE
|
74
73
|
dheap_ary_sift_down(VALUE heap_array, int d, long sift_index) {
|
75
74
|
DHEAP_Check_Sift_Args(heap_array, d, sift_index);
|
76
|
-
struct cmp_opt_data cmp_opt = { 0, 0 };
|
77
75
|
|
78
76
|
// iteratively sift it down to where it belongs
|
79
77
|
for (long child_index; sift_index < last_index; sift_index = child_index) {
|
80
|
-
|
78
|
+
debug(rb_sprintf("sift dn(%"PRIsVALUE", %d, %ld)", heap_array, d, sift_index));
|
81
79
|
// find first child index, and break if we've reached the last layer
|
82
80
|
long child_idx0 = child_index = IDX_CHILD0(sift_index);
|
83
81
|
if (last_index < child_idx0) break;
|
@@ -92,63 +90,81 @@ dheap_ary_sift_down(VALUE heap_array, int d, long sift_index) {
|
|
92
90
|
|
93
91
|
VALUE sibling_score = DHEAP_SCORE(heap_array, sibling_index);
|
94
92
|
|
95
|
-
if (CMP_LT(sibling_score, child_score
|
93
|
+
if (CMP_LT(sibling_score, child_score)) {
|
96
94
|
child_score = sibling_score;
|
97
95
|
child_index = sibling_index;
|
98
96
|
}
|
99
97
|
}
|
100
98
|
|
101
99
|
// child is larger: heap is restored
|
102
|
-
if (CMP_LTE(sift_score, child_score
|
100
|
+
if (CMP_LTE(sift_score, child_score)) break;
|
103
101
|
|
104
102
|
// child is smaller: swap and continue sifting down
|
105
103
|
VALUE child_value = DHEAP_VALUE(heap_array, child_index);
|
106
104
|
DHEAP_ASSIGN(heap_array, sift_index, child_score, child_value);
|
107
105
|
DHEAP_ASSIGN(heap_array, child_index, sift_score, sift_value);
|
108
106
|
}
|
109
|
-
|
107
|
+
debug(rb_sprintf("sifted (%"PRIsVALUE", %d, %ld)", heap_array, d, sift_index));
|
110
108
|
return LONG2NUM(sift_index);
|
111
109
|
}
|
112
110
|
|
113
111
|
#define DHEAP_Load_Sift_Vals(heap_array, dval, idxval) \
|
114
112
|
Check_Type(dval, T_FIXNUM); \
|
115
|
-
int
|
116
|
-
long
|
113
|
+
int dint = FIX2INT(dval); \
|
114
|
+
long idx = NUM2LONG(idxval);
|
117
115
|
|
118
116
|
/*
|
119
|
-
*
|
120
|
-
*
|
117
|
+
* Treats a +heap_array+ as a +d+-ary heap and sifts up from +sift_index+ to
|
118
|
+
* restore the heap property for all nodes between it and the root of the tree.
|
121
119
|
*
|
122
|
-
*
|
123
|
-
*
|
120
|
+
* The array is interpreted as holding two entries for each node, a score and a
|
121
|
+
* value. The scores will held in every even-numbered array index and the
|
122
|
+
* values in every odd numbered index. The array is flat, not an array of
|
123
|
+
* length=2 arrays.
|
124
124
|
*
|
125
|
-
* Time complexity: O(
|
126
|
-
* into the bottom layer (e.g. new timers), this can avg O(1).
|
125
|
+
* Time complexity: <b>O(log n / log d)</b> <i>(worst-case)</i>
|
127
126
|
*
|
127
|
+
* @param heap_array [Array] the array which is treated a heap.
|
128
|
+
* @param d [Integer] the maximum number of children per parent
|
129
|
+
* @param sift_index [Integer] the index to start sifting from
|
130
|
+
* @return [Integer] the new index for the object that starts at +sift_index+.
|
128
131
|
*/
|
129
132
|
static VALUE
|
130
|
-
dheap_sift_up_s(VALUE unused, VALUE heap_array, VALUE
|
131
|
-
DHEAP_Load_Sift_Vals(heap_array,
|
132
|
-
return dheap_ary_sift_up(heap_array,
|
133
|
+
dheap_sift_up_s(VALUE unused, VALUE heap_array, VALUE d, VALUE sift_index) {
|
134
|
+
DHEAP_Load_Sift_Vals(heap_array, d, sift_index);
|
135
|
+
return dheap_ary_sift_up(heap_array, dint, idx);
|
133
136
|
}
|
134
137
|
|
135
138
|
/*
|
136
|
-
* call-seq:
|
137
|
-
* DHeap.array_sift_down(heap_array, d, sift_index)
|
138
|
-
*
|
139
139
|
* Treats +heap_array+ as a +d+-ary heap and sifts down from +sift_index+ to
|
140
|
-
* restore the heap property.
|
140
|
+
* restore the heap property. If all _d_ subtrees below +sift_index+ are already
|
141
|
+
* heaps, this method ensures the entire subtree rooted at +sift_index+ will be
|
142
|
+
* a heap.
|
143
|
+
*
|
144
|
+
* The array is interpreted as holding two entries for each node, a score and a
|
145
|
+
* value. The scores will held in every even-numbered array index and the
|
146
|
+
* values in every odd numbered index. The array is flat, not an array of
|
147
|
+
* length=2 arrays.
|
141
148
|
*
|
142
|
-
* Time complexity: O(d log n / log d)
|
143
|
-
* sorts into the bottom layer (e.g. canceled timers), this can avg O(1).
|
149
|
+
* Time complexity: <b>O(d log n / log d)</b> <i>(worst-case)</i>
|
144
150
|
*
|
151
|
+
* @param heap_array [Array] the array which is treated a heap.
|
152
|
+
* @param d [Integer] the maximum number of children per parent
|
153
|
+
* @param sift_index [Integer] the index to start sifting down from
|
154
|
+
* @return [Integer] the new index for the object that starts at +sift_index+.
|
145
155
|
*/
|
146
156
|
static VALUE
|
147
|
-
dheap_sift_down_s(VALUE unused, VALUE heap_array, VALUE
|
148
|
-
DHEAP_Load_Sift_Vals(heap_array,
|
149
|
-
return dheap_ary_sift_down(heap_array,
|
157
|
+
dheap_sift_down_s(VALUE unused, VALUE heap_array, VALUE d, VALUE sift_index) {
|
158
|
+
DHEAP_Load_Sift_Vals(heap_array, d, sift_index);
|
159
|
+
return dheap_ary_sift_down(heap_array, dint, idx);
|
150
160
|
}
|
151
161
|
|
162
|
+
/*
|
163
|
+
* @overload initialize(d = DHeap::DEFAULT_D)
|
164
|
+
* Initialize a _d_-ary min-heap.
|
165
|
+
*
|
166
|
+
* @param d [Integer] maximum number of children per parent
|
167
|
+
*/
|
152
168
|
static VALUE
|
153
169
|
dheap_initialize(int argc, VALUE *argv, VALUE self) {
|
154
170
|
rb_check_arity(argc, 0, 1);
|
@@ -162,20 +178,35 @@ dheap_initialize(int argc, VALUE *argv, VALUE self) {
|
|
162
178
|
return self;
|
163
179
|
}
|
164
180
|
|
181
|
+
/*
|
182
|
+
* @return [Integer] the number of elements in the heap
|
183
|
+
*/
|
165
184
|
static VALUE dheap_size(VALUE self) {
|
166
185
|
VALUE ary = DHEAP_GET_A(self);
|
167
186
|
long size = DHEAP_SIZE(ary);
|
168
187
|
return LONG2NUM(size);
|
169
188
|
}
|
170
189
|
|
190
|
+
/*
|
191
|
+
* @return [Boolean] is the heap empty?
|
192
|
+
*/
|
171
193
|
static VALUE dheap_empty_p(VALUE self) {
|
172
194
|
VALUE ary = DHEAP_GET_A(self);
|
173
195
|
long size = DHEAP_SIZE(ary);
|
174
196
|
return size == 0 ? Qtrue : Qfalse;
|
175
197
|
}
|
176
198
|
|
199
|
+
/*
|
200
|
+
* @return [Integer] the maximum number of children per parent
|
201
|
+
*/
|
177
202
|
static VALUE dheap_attr_d(VALUE self) { return DHEAP_GET_D(self); }
|
178
203
|
|
204
|
+
/*
|
205
|
+
* Freezes the heap as well as its underlying array, but does <i>not</i>
|
206
|
+
* deep-freeze the elements in the heap.
|
207
|
+
*
|
208
|
+
* @return [self]
|
209
|
+
*/
|
179
210
|
static VALUE
|
180
211
|
dheap_freeze(VALUE self) {
|
181
212
|
VALUE ary = DHEAP_GET_A(self);
|
@@ -193,10 +224,19 @@ dheap_ary_push(VALUE ary, int d, VALUE val, VALUE scr)
|
|
193
224
|
}
|
194
225
|
|
195
226
|
/*
|
196
|
-
*
|
227
|
+
* @overload push(score, value = score)
|
228
|
+
*
|
229
|
+
* Push a value onto heap, using a score to determine sort-order.
|
230
|
+
*
|
231
|
+
* Ideally, the score should be a frozen value that can be efficiently compared
|
232
|
+
* to other scores, e.g. an Integer or Float or (maybe) a String
|
233
|
+
*
|
234
|
+
* Time complexity: <b>O(log n / log d)</b> <i>(worst-case)</i>
|
197
235
|
*
|
198
|
-
*
|
236
|
+
* @param score [#<=>] a value that can be compared to other scores.
|
237
|
+
* @param value [Object] an object that is associated with the score.
|
199
238
|
*
|
239
|
+
* @return [Integer] the index of the value's final position.
|
200
240
|
*/
|
201
241
|
static VALUE
|
202
242
|
dheap_push(int argc, VALUE *argv, VALUE self) {
|
@@ -212,16 +252,18 @@ dheap_push(int argc, VALUE *argv, VALUE self) {
|
|
212
252
|
}
|
213
253
|
|
214
254
|
/*
|
215
|
-
*
|
255
|
+
* Pushes a comparable value onto the heap.
|
216
256
|
*
|
217
|
-
*
|
257
|
+
* The value will be its own score.
|
218
258
|
*
|
219
|
-
*
|
259
|
+
* Time complexity: <b>O(log n / log d)</b> <i>(worst-case)</i>
|
220
260
|
*
|
261
|
+
* @param value [#<=>] a value that can be compared to other heap members.
|
262
|
+
* @return [self]
|
221
263
|
*/
|
222
264
|
static VALUE
|
223
|
-
dheap_left_shift(VALUE self, VALUE
|
224
|
-
dheap_push(1, &
|
265
|
+
dheap_left_shift(VALUE self, VALUE value) {
|
266
|
+
dheap_push(1, &value, self);
|
225
267
|
return self;
|
226
268
|
}
|
227
269
|
|
@@ -238,6 +280,12 @@ dheap_left_shift(VALUE self, VALUE val) {
|
|
238
280
|
DHEAP_DROP_LAST(ary); \
|
239
281
|
dheap_ary_sift_down(ary, FIX2INT(dval), 0);
|
240
282
|
|
283
|
+
/*
|
284
|
+
* Returns the next value on the heap to be popped without popping it.
|
285
|
+
*
|
286
|
+
* Time complexity: <b>O(1)</b> <i>(worst-case)</i>
|
287
|
+
* @return [Object] the next value to be popped without popping it.
|
288
|
+
*/
|
241
289
|
static VALUE
|
242
290
|
dheap_peek(VALUE self) {
|
243
291
|
VALUE ary = DHEAP_GET_A(self);
|
@@ -245,11 +293,9 @@ dheap_peek(VALUE self) {
|
|
245
293
|
}
|
246
294
|
|
247
295
|
/*
|
248
|
-
* Pops the minimum value from the top of the heap
|
249
|
-
* heap property.
|
250
|
-
*
|
251
|
-
* Time complexity: O(d log n / log d).
|
296
|
+
* Pops the minimum value from the top of the heap
|
252
297
|
*
|
298
|
+
* Time complexity: <b>O(d log n / log d)</b> <i>(worst-case)</i>
|
253
299
|
*/
|
254
300
|
static VALUE
|
255
301
|
dheap_pop(VALUE self) {
|
@@ -262,42 +308,40 @@ dheap_pop(VALUE self) {
|
|
262
308
|
}
|
263
309
|
|
264
310
|
/*
|
265
|
-
* Pops the minimum value
|
266
|
-
* heap property.
|
311
|
+
* Pops the minimum value only if it is less than or equal to a max score.
|
267
312
|
*
|
268
|
-
*
|
313
|
+
* @param max_score [#<=>] the maximum score to be popped
|
269
314
|
*
|
315
|
+
* @see #pop
|
270
316
|
*/
|
271
317
|
static VALUE
|
272
|
-
dheap_pop_lte(VALUE self, VALUE
|
318
|
+
dheap_pop_lte(VALUE self, VALUE max_score) {
|
273
319
|
DHEAP_Pop_Init(self);
|
274
320
|
if (last_index < 0) return Qnil;
|
275
321
|
VALUE pop_value = DHEAP_VALUE(ary, 0);
|
276
322
|
|
277
323
|
VALUE pop_score = DHEAP_SCORE(ary, 0);
|
278
|
-
|
279
|
-
if (below_score && !CMP_LTE(pop_score, below_score, cmp_opt)) return Qnil;
|
324
|
+
if (max_score && !CMP_LTE(pop_score, max_score)) return Qnil;
|
280
325
|
|
281
326
|
DHEAP_Pop_SwapLastAndSiftDown(ary, dval, last_index, sift_value);
|
282
327
|
return pop_value;
|
283
328
|
}
|
284
329
|
|
285
330
|
/*
|
286
|
-
* Pops the minimum value
|
287
|
-
* heap property.
|
331
|
+
* Pops the minimum value only if it is less than a max score.
|
288
332
|
*
|
289
|
-
*
|
333
|
+
* @param max_score [#<=>] the maximum score to be popped
|
290
334
|
*
|
335
|
+
* Time complexity: <b>O(d log n / log d)</b> <i>(worst-case)</i>
|
291
336
|
*/
|
292
337
|
static VALUE
|
293
|
-
dheap_pop_lt(VALUE self, VALUE
|
338
|
+
dheap_pop_lt(VALUE self, VALUE max_score) {
|
294
339
|
DHEAP_Pop_Init(self);
|
295
340
|
if (last_index < 0) return Qnil;
|
296
341
|
VALUE pop_value = DHEAP_VALUE(ary, 0);
|
297
342
|
|
298
343
|
VALUE pop_score = DHEAP_SCORE(ary, 0);
|
299
|
-
|
300
|
-
if (below_score && !CMP_LT(pop_score, below_score, cmp_opt)) return Qnil;
|
344
|
+
if (max_score && !CMP_LT(pop_score, max_score)) return Qnil;
|
301
345
|
|
302
346
|
DHEAP_Pop_SwapLastAndSiftDown(ary, dval, last_index, sift_value);
|
303
347
|
return pop_value;
|
@@ -311,6 +355,9 @@ Init_d_heap(void)
|
|
311
355
|
id_ivar_d = rb_intern_const("d");
|
312
356
|
|
313
357
|
rb_cDHeap = rb_define_class("DHeap", rb_cObject);
|
358
|
+
rb_define_const(rb_cDHeap, "MAX_D", INT2NUM(DHEAP_MAX_D));
|
359
|
+
rb_define_const(rb_cDHeap, "DEFAULT_D", INT2NUM(DHEAP_DEFAULT_D));
|
360
|
+
|
314
361
|
rb_define_singleton_method(rb_cDHeap, "heap_sift_down", dheap_sift_down_s, 3);
|
315
362
|
rb_define_singleton_method(rb_cDHeap, "heap_sift_up", dheap_sift_up_s, 3);
|
316
363
|
|
data/ext/d_heap/d_heap.h
CHANGED
@@ -11,64 +11,55 @@
|
|
11
11
|
// comparisons as d gets further from 4.
|
12
12
|
#define DHEAP_MAX_D 32
|
13
13
|
|
14
|
+
VALUE rb_cDHeap;
|
14
15
|
|
15
|
-
#define CMP_LT(a, b,
|
16
|
-
|
17
|
-
#define
|
18
|
-
|
19
|
-
#define CMP_GT(a, b, cmp_opt) \
|
20
|
-
(OPTIMIZED_CMP(a, b, cmp_opt) > 0)
|
21
|
-
#define CMP_GTE(a, b, cmp_opt) \
|
22
|
-
(OPTIMIZED_CMP(a, b, cmp_opt) >= 0)
|
16
|
+
#define CMP_LT(a, b) (optimized_cmp(a, b) < 0)
|
17
|
+
#define CMP_LTE(a, b) (optimized_cmp(a, b) <= 0)
|
18
|
+
#define CMP_GT(a, b) (optimized_cmp(a, b) > 0)
|
19
|
+
#define CMP_GTE(a, b) (optimized_cmp(a, b) >= 0)
|
23
20
|
|
24
|
-
|
21
|
+
// <=>
|
25
22
|
ID id_cmp;
|
26
23
|
|
27
|
-
// from internal/numeric.h
|
28
|
-
#ifndef INTERNAL_NUMERIC_H
|
29
|
-
int rb_float_cmp(VALUE x, VALUE y);
|
30
|
-
#endif /* INTERNAL_NUMERIC_H */
|
31
|
-
|
32
24
|
// from internal/compar.h
|
33
|
-
#ifndef INTERNAL_COMPAR_H
|
34
25
|
#define STRING_P(s) (RB_TYPE_P((s), T_STRING) && CLASS_OF(s) == rb_cString)
|
35
26
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
27
|
+
/*
|
28
|
+
* short-circuit evaluation for a few basic types.
|
29
|
+
*
|
30
|
+
* Only Integer, Float, and String are optimized,
|
31
|
+
* and only when both arguments are the same type.
|
32
|
+
*/
|
33
|
+
static inline int
|
34
|
+
optimized_cmp(VALUE a, VALUE b) {
|
35
|
+
if (a == b) // Fixnum equality and object equality
|
36
|
+
return 0;
|
37
|
+
if (FIXNUM_P(a) && FIXNUM_P(b))
|
38
|
+
return (FIX2LONG(a) < FIX2LONG(b)) ? -1 : 1;
|
39
|
+
if (RB_FLOAT_TYPE_P(a) && RB_FLOAT_TYPE_P(b))
|
40
|
+
{
|
41
|
+
double x, y;
|
42
|
+
x = RFLOAT_VALUE(a);
|
43
|
+
y = RFLOAT_VALUE(b);
|
44
|
+
if (isnan(x) || isnan(y)) rb_cmperr(a, b); // raise ArgumentError
|
45
|
+
return (x < y) ? -1 : ((x == y) ? 0 : 1);
|
46
|
+
}
|
47
|
+
if (RB_TYPE_P(a, T_BIGNUM) && RB_TYPE_P(b, T_BIGNUM))
|
48
|
+
return FIX2INT(rb_big_cmp(a, b));
|
49
|
+
if (STRING_P(a) && STRING_P(b))
|
50
|
+
return rb_str_cmp(a, b);
|
47
51
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
#define CMP_OPTIMIZABLE(data, type) \
|
52
|
-
(((data).opt_inited & CMP_OPTIMIZABLE_BIT(type)) ? \
|
53
|
-
((data).opt_methods & CMP_OPTIMIZABLE_BIT(type)) : \
|
54
|
-
(((data).opt_inited |= CMP_OPTIMIZABLE_BIT(type)), \
|
55
|
-
rb_method_basic_definition_p(TOKEN_PASTE(rb_c,type), id_cmp) && \
|
56
|
-
((data).opt_methods |= CMP_OPTIMIZABLE_BIT(type))))
|
57
|
-
|
58
|
-
#define OPTIMIZED_CMP(a, b, data) \
|
59
|
-
((FIXNUM_P(a) && FIXNUM_P(b) && CMP_OPTIMIZABLE(data, Integer)) ? \
|
60
|
-
(((long)a > (long)b) ? 1 : ((long)a < (long)b) ? -1 : 0) : \
|
61
|
-
(STRING_P(a) && STRING_P(b) && CMP_OPTIMIZABLE(data, String)) ? \
|
62
|
-
rb_str_cmp(a, b) : \
|
63
|
-
(RB_FLOAT_TYPE_P(a) && RB_FLOAT_TYPE_P(b) && CMP_OPTIMIZABLE(data, Float)) ? \
|
64
|
-
rb_float_cmp(a, b) : \
|
65
|
-
rb_cmpint(rb_funcallv(a, id_cmp, 1, &b), a, b))
|
52
|
+
// give up on an optimized version and just call (a <=> b)
|
53
|
+
return rb_cmpint(rb_funcallv(a, id_cmp, 1, &b), a, b);
|
54
|
+
}
|
66
55
|
|
67
|
-
#
|
56
|
+
#ifdef __D_HEAP_DEBUG
|
57
|
+
#define debug(v) { \
|
68
58
|
ID sym_puts = rb_intern("puts"); \
|
69
59
|
rb_funcall(rb_mKernel, sym_puts, 1, v); \
|
70
60
|
}
|
71
|
-
|
72
|
-
#
|
61
|
+
#else
|
62
|
+
#define debug(v)
|
63
|
+
#endif
|
73
64
|
|
74
65
|
#endif /* D_HEAP_H */
|
data/ext/d_heap/extconf.rb
CHANGED
data/lib/d_heap.rb
CHANGED
@@ -1,6 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "d_heap/d_heap"
|
2
4
|
require "d_heap/version"
|
3
5
|
|
6
|
+
# A fast _d_-ary heap implementation for ruby, useful in priority queues and graph
|
7
|
+
# algorithms.
|
8
|
+
#
|
9
|
+
# The _d_-ary heap data structure is a generalization of the binary heap, in which
|
10
|
+
# the nodes have _d_ children instead of 2. This allows for "decrease priority"
|
11
|
+
# operations to be performed more quickly with the tradeoff of slower delete
|
12
|
+
# minimum. Additionally, _d_-ary heaps can have better memory cache behavior than
|
13
|
+
# binary heaps, allowing them to run more quickly in practice despite slower
|
14
|
+
# worst-case time complexity.
|
15
|
+
#
|
4
16
|
class DHeap
|
5
17
|
|
6
18
|
def initialize_copy(other)
|
data/lib/d_heap/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: d_heap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- nicholas a. evans
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-12-
|
11
|
+
date: 2020-12-27 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: |
|
14
14
|
A C extension implementation of a d-ary heap data structure, suitable for
|
@@ -20,8 +20,10 @@ extensions:
|
|
20
20
|
- ext/d_heap/extconf.rb
|
21
21
|
extra_rdoc_files: []
|
22
22
|
files:
|
23
|
+
- ".github/workflows/main.yml"
|
23
24
|
- ".gitignore"
|
24
25
|
- ".rspec"
|
26
|
+
- ".rubocop.yml"
|
25
27
|
- ".travis.yml"
|
26
28
|
- CODE_OF_CONDUCT.md
|
27
29
|
- Gemfile
|
@@ -32,6 +34,7 @@ files:
|
|
32
34
|
- bin/console
|
33
35
|
- bin/rake
|
34
36
|
- bin/rspec
|
37
|
+
- bin/rubocop
|
35
38
|
- bin/setup
|
36
39
|
- d_heap.gemspec
|
37
40
|
- ext/d_heap/d_heap.c
|
@@ -61,7 +64,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
61
64
|
- !ruby/object:Gem::Version
|
62
65
|
version: '0'
|
63
66
|
requirements: []
|
64
|
-
rubygems_version: 3.
|
67
|
+
rubygems_version: 3.1.4
|
65
68
|
signing_key:
|
66
69
|
specification_version: 4
|
67
70
|
summary: A d-ary heap implementation, for priority queues
|