d_heap 0.3.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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")