d_heap 0.3.0 → 0.7.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.
@@ -2,10 +2,52 @@
2
2
 
3
3
  require "mkmf"
4
4
 
5
- # if /darwin/ =~ RUBY_PLATFORM
6
- # $CFLAGS << " -D__D_HEAP_DEBUG"
7
- # end
5
+ # For testing in CI (because I don't otherwise have easy access to Mac OS):
6
+ # $CFLAGS << " -D__D_HEAP_DEBUG" if /darwin/ =~ RUBY_PLATFORM
7
+ # $CFLAGS << " -debug inline-debug-info "
8
+ # $CFLAGS << " -g -ginline-points "
9
+ # $CFLAGS << " -fno-omit-frame-pointer "
10
+
11
+ # Use `rake compile -- --enable-debug`
12
+ debug_mode = enable_config("debug", ENV["EXTCONF_DEBUG"] == "1")
13
+
14
+ # Use `rake compile -- --enable-development`
15
+ devel_mode = enable_config("development") || debug_mode
16
+
17
+ if debug_mode
18
+ $stderr.puts "Building in debug mode." # rubocop:disable Style/StderrPuts
19
+ CONFIG["warnflags"] \
20
+ << " -ggdb" \
21
+ << " -DDEBUG"
22
+ end
23
+
24
+ if devel_mode
25
+ $stderr.puts "Building in development mode." # rubocop:disable Style/StderrPuts
26
+ CONFIG["warnflags"] \
27
+ << " -Wall " \
28
+ << " -Wpedantic" \
29
+ # There are warnings on MacOS that are annoying to debug (I don't have a Mac).
30
+ unless RbConfig::CONFIG["target_os"] =~ /darwin/
31
+ CONFIG["warnflags"] << " -Werror"
32
+ end
33
+ end
34
+
35
+ # Use `rake compile -- --enable-heapmap`
36
+ if enable_config("heapmap", true)
37
+ $stderr.puts "Building with DHeap::Map support." # rubocop:disable Style/StderrPuts
38
+ $defs.push "-DDHEAP_MAP"
39
+ end
8
40
 
9
41
  have_func "rb_gc_mark_movable" # since ruby-2.7
10
42
 
43
+ check_sizeof("long")
44
+ check_sizeof("unsigned long long")
45
+ check_sizeof("long double")
46
+ check_sizeof("double")
47
+
48
+ unless have_macro("LDBL_MANT_DIG", "float.h")
49
+ raise NotImplementedError, "Missing LDBL_MANT_DIG."
50
+ end
51
+
52
+ create_header
11
53
  create_makefile("d_heap/d_heap")
data/images/push_n.png ADDED
Binary file
Binary file
Binary file
Binary file
data/lib/d_heap.rb CHANGED
@@ -10,13 +10,126 @@ require "d_heap/version"
10
10
  # the nodes have _d_ children instead of 2. This allows for "decrease priority"
11
11
  # operations to be performed more quickly with the tradeoff of slower delete
12
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
13
+ # binary heaps, allowing them to pop more quickly in practice despite slower
14
14
  # worst-case time complexity.
15
15
  #
16
+ # Although _d_ can be configured when creating the heap, it's usually best to
17
+ # keep the default value of 4, because d=4 gives the smallest coefficient for
18
+ # <tt>(d + 1) log n / log d</tt> result. As always, use benchmarks for your
19
+ # particular use-case.
20
+ #
21
+ # @example Basic push, peek, and pop
22
+ # # create some example objects to place in our heap
23
+ # Task = Struct.new(:id, :time) do
24
+ # def to_f; time.to_f end
25
+ # end
26
+ # t1 = Task.new(1, Time.now + 5*60)
27
+ # t2 = Task.new(2, Time.now + 50)
28
+ # t3 = Task.new(3, Time.now + 60)
29
+ # t4 = Task.new(4, Time.now + 5)
30
+ #
31
+ # # create the heap
32
+ # require "d_heap"
33
+ # heap = DHeap.new
34
+ #
35
+ # # push with an explicit score (which might be extrinsic to the value)
36
+ # heap.push t1, t1.to_f
37
+ #
38
+ # # the score will be implicitly cast with Float, so any object with #to_f
39
+ # heap.push t2, t2
40
+ #
41
+ # # if the object has an intrinsic score via #to_f, "<<" is the simplest API
42
+ # heap << t3 << t4
43
+ #
44
+ # # pop returns the lowest scored item, and removes it from the heap
45
+ # heap.pop # => #<struct Task id=4, time=2021-01-17 17:02:22.5574 -0500>
46
+ # heap.pop # => #<struct Task id=2, time=2021-01-17 17:03:07.5574 -0500>
47
+ #
48
+ # # peek returns the lowest scored item, without removing it from the heap
49
+ # heap.peek # => #<struct Task id=3, time=2021-01-17 17:03:17.5574 -0500>
50
+ # heap.pop # => #<struct Task id=3, time=2021-01-17 17:03:17.5574 -0500>
51
+ #
52
+ # # pop_lte handles the common "h.pop if h.peek_score < max" pattern
53
+ # heap.pop_lte(Time.now + 65) # => nil
54
+ #
55
+ # # the heap size can be inspected with size and empty?
56
+ # heap.empty? # => false
57
+ # heap.size # => 1
58
+ # heap.pop # => #<struct Task id=1, time=2021-01-17 17:07:17.5574 -0500>
59
+ # heap.empty? # => true
60
+ # heap.size # => 0
61
+ #
62
+ # # popping from an empty heap returns nil
63
+ # heap.pop # => nil
64
+ #
16
65
  class DHeap
66
+ alias deq pop
67
+ alias shift pop
68
+ alias next pop
69
+ alias pop_all_lt pop_all_below
70
+ alias pop_below pop_lt
71
+
72
+ alias enq push
73
+
74
+ alias first peek
75
+
76
+ alias length size
77
+ alias count size
78
+
79
+ # Initialize a _d_-ary min-heap.
80
+ #
81
+ # @param d [Integer] Number of children for each parent node.
82
+ # Higher values generally speed up push but slow down pop.
83
+ # If all pushes are popped, the default is probably best.
84
+ # @param capacity [Integer] initial capacity of the heap.
85
+ def initialize(d: DEFAULT_D, capacity: DEFAULT_CAPA) # rubocop:disable Naming/MethodParameterName
86
+ __init_without_kw__(d, capacity, false)
87
+ end
88
+
89
+ # Consumes the heap by popping each minumum value until it is empty.
90
+ #
91
+ # If you want to iterate over the heap without consuming it, you will need to
92
+ # first call +#dup+
93
+ #
94
+ # @param with_score [Boolean] if scores shoul also be yielded
95
+ #
96
+ # @yieldparam value [Object] each value that would be popped
97
+ # @yieldparam score [Numeric] each value's score, if +with_scores+ is true
98
+ #
99
+ # @return [Enumerator] if no block is given
100
+ # @return [nil] if a block is given
101
+ def each_pop(with_scores: false)
102
+ return to_enum(__method__, with_scores: with_scores) unless block_given?
103
+ if with_scores
104
+ yield(*pop_with_score) until empty?
105
+ else
106
+ yield pop until empty?
107
+ end
108
+ nil
109
+ end
110
+
111
+ if defined?(Map)
112
+
113
+ # Unlike {DHeap}, an object can only be added into a {DHeap::Map} once. Any
114
+ # subsequent addition simply rescores the already existing member. Objects
115
+ # are identified by their {#hash} value, just like with Hash.
116
+ class Map
117
+ alias score :[]
118
+ alias rescore :[]=
119
+ alias update :[]=
120
+
121
+ # Initialize a _d_-ary min-heap which can map objects to scores.
122
+ #
123
+ # @param d [Integer] Number of children for each parent node.
124
+ # Higher values generally speed up push but slow down pop.
125
+ # If all pushes are popped, the default is probably best.
126
+ # @param capacity [Integer] initial capacity of the heap.
127
+ def initialize(d: DEFAULT_D, capacity: DEFAULT_CAPA) # rubocop:disable Naming/MethodParameterName
128
+ __init_without_kw__(d, capacity, true)
129
+ end
130
+
131
+ end
17
132
 
18
- def initialize_copy(other)
19
- raise NotImplementedError, "initialize_copy should deep copy array"
20
133
  end
21
134
 
22
135
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class DHeap
4
- VERSION = "0.3.0"
4
+ VERSION = "0.7.0"
5
5
 
6
6
  end
metadata CHANGED
@@ -1,17 +1,31 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: d_heap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.7.0
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-29 00:00:00.000000000 Z
11
+ date: 2021-02-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: rspec-benchmark
14
+ name: benchmark_driver
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: ruby-prof
15
29
  requirement: !ruby/object:Gem::Requirement
16
30
  requirements:
17
31
  - - ">="
@@ -34,26 +48,28 @@ extensions:
34
48
  - ext/d_heap/extconf.rb
35
49
  extra_rdoc_files: []
36
50
  files:
51
+ - ".clang-format"
37
52
  - ".github/workflows/main.yml"
38
53
  - ".gitignore"
39
- - ".rspec"
40
54
  - ".rubocop.yml"
41
- - ".travis.yml"
55
+ - ".yardopts"
56
+ - CHANGELOG.md
42
57
  - CODE_OF_CONDUCT.md
43
- - Gemfile
44
- - Gemfile.lock
58
+ - D
45
59
  - LICENSE.txt
46
60
  - README.md
47
- - Rakefile
48
- - bin/console
49
- - bin/rake
50
- - bin/rspec
51
- - bin/rubocop
52
- - bin/setup
53
61
  - d_heap.gemspec
62
+ - docs/benchmarks-2.txt
63
+ - docs/benchmarks-mem.txt
64
+ - docs/benchmarks.txt
65
+ - docs/profile.txt
66
+ - ext/d_heap/.rubocop.yml
54
67
  - ext/d_heap/d_heap.c
55
- - ext/d_heap/d_heap.h
56
68
  - ext/d_heap/extconf.rb
69
+ - images/push_n.png
70
+ - images/push_n_pop_n.png
71
+ - images/push_pop.png
72
+ - images/wikipedia-min-heap.png
57
73
  - lib/d_heap.rb
58
74
  - lib/d_heap/version.rb
59
75
  homepage: https://github.com/nevans/d_heap
@@ -62,7 +78,7 @@ licenses:
62
78
  metadata:
63
79
  homepage_uri: https://github.com/nevans/d_heap
64
80
  source_code_uri: https://github.com/nevans/d_heap
65
- changelog_uri: https://github.com/nevans/d_heap/blob/master/Changelog.md
81
+ changelog_uri: https://github.com/nevans/d_heap/blob/master/CHANGELOG.md
66
82
  post_install_message:
67
83
  rdoc_options: []
68
84
  require_paths:
@@ -71,7 +87,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
71
87
  requirements:
72
88
  - - ">="
73
89
  - !ruby/object:Gem::Version
74
- version: 2.5.0
90
+ version: 2.4.0
75
91
  required_rubygems_version: !ruby/object:Gem::Requirement
76
92
  requirements:
77
93
  - - ">="
@@ -81,5 +97,5 @@ requirements: []
81
97
  rubygems_version: 3.1.4
82
98
  signing_key:
83
99
  specification_version: 4
84
- summary: A d-ary heap implementation, for priority queues
100
+ summary: very fast min-heap priority queue
85
101
  test_files: []
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --color
3
- --require spec_helper
data/.travis.yml DELETED
@@ -1,6 +0,0 @@
1
- ---
2
- language: ruby
3
- cache: bundler
4
- rvm:
5
- - 2.7.2
6
- before_install: gem install bundler -v 2.1.4
data/Gemfile DELETED
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source "https://rubygems.org"
4
-
5
- # Specify your gem's dependencies in d_heap.gemspec
6
- gemspec
7
-
8
- gem "rake", "~> 13.0"
9
- gem "rake-compiler"
10
- gem "rspec", "~> 3.10"
11
- gem "rubocop", "~> 1.0"
data/Gemfile.lock DELETED
@@ -1,67 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- d_heap (0.3.0)
5
-
6
- GEM
7
- remote: https://rubygems.org/
8
- specs:
9
- ast (2.4.1)
10
- benchmark-malloc (0.2.0)
11
- benchmark-perf (0.6.0)
12
- benchmark-trend (0.4.0)
13
- diff-lcs (1.4.4)
14
- parallel (1.19.2)
15
- parser (2.7.2.0)
16
- ast (~> 2.4.1)
17
- rainbow (3.0.0)
18
- rake (13.0.3)
19
- rake-compiler (1.1.1)
20
- rake
21
- regexp_parser (1.8.2)
22
- rexml (3.2.3)
23
- rspec (3.10.0)
24
- rspec-core (~> 3.10.0)
25
- rspec-expectations (~> 3.10.0)
26
- rspec-mocks (~> 3.10.0)
27
- rspec-benchmark (0.6.0)
28
- benchmark-malloc (~> 0.2)
29
- benchmark-perf (~> 0.6)
30
- benchmark-trend (~> 0.4)
31
- rspec (>= 3.0)
32
- rspec-core (3.10.0)
33
- rspec-support (~> 3.10.0)
34
- rspec-expectations (3.10.0)
35
- diff-lcs (>= 1.2.0, < 2.0)
36
- rspec-support (~> 3.10.0)
37
- rspec-mocks (3.10.0)
38
- diff-lcs (>= 1.2.0, < 2.0)
39
- rspec-support (~> 3.10.0)
40
- rspec-support (3.10.0)
41
- rubocop (1.2.0)
42
- parallel (~> 1.10)
43
- parser (>= 2.7.1.5)
44
- rainbow (>= 2.2.2, < 4.0)
45
- regexp_parser (>= 1.8)
46
- rexml
47
- rubocop-ast (>= 1.0.1)
48
- ruby-progressbar (~> 1.7)
49
- unicode-display_width (>= 1.4.0, < 2.0)
50
- rubocop-ast (1.1.1)
51
- parser (>= 2.7.1.5)
52
- ruby-progressbar (1.10.1)
53
- unicode-display_width (1.7.0)
54
-
55
- PLATFORMS
56
- ruby
57
-
58
- DEPENDENCIES
59
- d_heap!
60
- rake (~> 13.0)
61
- rake-compiler
62
- rspec (~> 3.10)
63
- rspec-benchmark
64
- rubocop (~> 1.0)
65
-
66
- BUNDLED WITH
67
- 2.2.3
data/Rakefile DELETED
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "bundler/gem_tasks"
4
- require "rspec/core/rake_task"
5
-
6
- RSpec::Core::RakeTask.new(:spec)
7
-
8
- require "rubocop/rake_task"
9
-
10
- RuboCop::RakeTask.new
11
-
12
- require "rake/extensiontask"
13
-
14
- task build: :compile
15
-
16
- Rake::ExtensionTask.new("d_heap") do |ext|
17
- ext.lib_dir = "lib/d_heap"
18
- end
19
-
20
- task default: %i[clobber compile spec rubocop]
data/bin/console DELETED
@@ -1,15 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require "bundler/setup"
5
- require "d_heap"
6
-
7
- # You can add fixtures and/or initialization code here to make experimenting
8
- # with your gem easier. You can also use a different console, if you like.
9
-
10
- # (If you use this, don't forget to add pry to your Gemfile!)
11
- # require "pry"
12
- # Pry.start
13
-
14
- require "irb"
15
- IRB.start(__FILE__)
data/bin/rake DELETED
@@ -1,29 +0,0 @@
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")