geom2d 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3edb20280c7781756b3e384e9882aea7c3a98ef0803a33c801cf6580a9d4e3ef
4
- data.tar.gz: f1f8dc432c32de394ef313536b1faac5e7719f88ef68d1138c2abc7aec56309a
3
+ metadata.gz: ef94841dc1fefba33e2abbd4303cea33d3d1cfd4db5727a828300b12e7e405d6
4
+ data.tar.gz: d65cf9ae9017e07c0faf92cccd21cc918d323c31479bfc2f3a99b0a65162d33b
5
5
  SHA512:
6
- metadata.gz: 429eae46087970b5624c6cefad3f907a44d812093be921f831675cb92cb17901f7efc1c825d844137684156b16e7d9479957d4edd9310242394a448415f21e66
7
- data.tar.gz: 3302aa1ebe48790c07c72d6c8882bd6fae780596bf641e0ad4b82852ed76370f6f0599d93ab92155033ed657548fa837539a3d74a3df4a28ccca8cbd314f9ae8
6
+ metadata.gz: 18dfb48248517e32415185c17e1bc6bf5eab076a6ae43d5302dbc972c88c1da0bb00c70ac1889dcc7d9f8b3ed3596a485e89e4ca4b4b7168b348914ee16b9143
7
+ data.tar.gz: 93ed02cc3c1037a04bfbe97274c0acc65f2f1ada3417f3de8b47b517664069793757df8a7e6b1ea2cd455a6b569f36ef1aabfed67c39bd07140ace59901fc2b0
@@ -0,0 +1,15 @@
1
+ ## 0.2.0 - Unreleased
2
+
3
+ ### Changed
4
+
5
+ * Make Segment#intersect ~1.71x faster by avoiding unnecessary object creation
6
+ * Refactor and simplify the sorted list implementation used by the polygon
7
+ operations, making the latter ~1.15x faster
8
+
9
+ ### Fixed
10
+
11
+ * Fix off-by-one error in Polygon#ccw? calculation
12
+
13
+ ## 0.1.0 - 2018-03-28
14
+
15
+ * Initial release
@@ -1,3 +1,3 @@
1
1
  Count Name
2
2
  ======= ====
3
- 8 Thomas Leitner <t_leitner@gmx.at>
3
+ 16 Thomas Leitner <t_leitner@gmx.at>
data/Rakefile CHANGED
@@ -21,6 +21,7 @@ namespace :dev do
21
21
  'lib/**/*.rb',
22
22
  'test/**/*',
23
23
  'Rakefile',
24
+ 'CHANGELOG.md',
24
25
  'LICENSE',
25
26
  'VERSION',
26
27
  'CONTRIBUTERS',
@@ -51,6 +52,7 @@ namespace :dev do
51
52
 
52
53
  s.require_path = 'lib'
53
54
  s.required_ruby_version = '>= 2.4'
55
+ s.add_development_dependency('rubocop', '~> 0.58', '>= 0.58.2')
54
56
 
55
57
  s.author = 'Thomas Leitner'
56
58
  s.email = 't_leitner@gmx.at'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.2.0
@@ -29,7 +29,7 @@ module Geom2D
29
29
  # Creates a new Point object from the given coordinates.
30
30
  #
31
31
  # See: Point.new
32
- def self::Point(x, y = nil)
32
+ def self.Point(x, y = nil)
33
33
  if x.kind_of?(Point)
34
34
  x
35
35
  elsif y
@@ -43,7 +43,7 @@ module Geom2D
43
43
  # +start_point+ to +start_point+ + +vector+.
44
44
  #
45
45
  # See: Segment.new
46
- def self::Segment(start_point, end_point = nil, vector: nil)
46
+ def self.Segment(start_point, end_point = nil, vector: nil)
47
47
  if end_point
48
48
  Segment.new(start_point, end_point)
49
49
  elsif vector
@@ -56,14 +56,14 @@ module Geom2D
56
56
  # Creates a new Polygon object from the given vertices.
57
57
  #
58
58
  # See: Polygon.new
59
- def self::Polygon(*vertices)
59
+ def self.Polygon(*vertices)
60
60
  Polygon.new(vertices)
61
61
  end
62
62
 
63
63
  # Creates a PolygonSet from the given array of Polygon instances.
64
64
  #
65
65
  # See: PolygonSet.new
66
- def self::PolygonSet(*polygons)
66
+ def self.PolygonSet(*polygons)
67
67
  PolygonSet.new(polygons)
68
68
  end
69
69
 
@@ -66,12 +66,12 @@ module Geom2D
66
66
  attr_accessor :prev_in_result
67
67
 
68
68
  # Creates a new SweepEvent.
69
- def initialize(left, point, polygon_type, other_event: nil, edge_type: :normal)
69
+ def initialize(left, point, polygon_type, other_event = nil)
70
70
  @left = left
71
71
  @point = point
72
72
  @other_event = other_event
73
73
  @polygon_type = polygon_type
74
- @edge_type = edge_type
74
+ @edge_type = :normal
75
75
  end
76
76
 
77
77
  # Returns +true+ if this event's line #segment is below the point +p+.
@@ -184,9 +184,9 @@ module Geom2D
184
184
  @operation = operation
185
185
 
186
186
  @result = PolygonSet.new
187
- @event_queue = Utils::SortedLinkedList.new {|a, b| a.process_after?(b) }
187
+ @event_queue = Utils::SortedList.new {|a, b| a.process_after?(b) }
188
188
  # @sweep_line should really be a sorted data structure with O(log(n)) for insert/search!
189
- @sweep_line = Utils::SortedLinkedList.new {|a, b| a.segment_below?(b) }
189
+ @sweep_line = Utils::SortedList.new {|a, b| a.segment_below?(b) }
190
190
  @sorted_events = []
191
191
  end
192
192
 
@@ -212,9 +212,7 @@ module Geom2D
212
212
 
213
213
  @event_queue.pop
214
214
  if event.left # the segment hast to be inserted into status line
215
- node = @sweep_line.insert(event)
216
- prev_event = (node.prev_node.anchor? ? nil : node.prev_node.value)
217
- next_event = (node.next_node.anchor? ? nil : node.next_node.value)
215
+ prevprev_event, prev_event, next_event = @sweep_line.insert(event)
218
216
 
219
217
  compute_event_fields(event, prev_event)
220
218
  if next_event && possible_intersection(event, next_event) == 2
@@ -222,19 +220,14 @@ module Geom2D
222
220
  compute_event_fields(next_event, event)
223
221
  end
224
222
  if prev_event && possible_intersection(prev_event, event) == 2
225
- prevprev_ev = (node.prev_node.prev_node.anchor? ? nil : node.prev_node.prev_node.value)
226
- compute_event_fields(prev_event, prevprev_ev)
223
+ compute_event_fields(prev_event, prevprev_event)
227
224
  compute_event_fields(event, prev_event)
228
225
  end
229
226
  else # the segment has to be removed from the status line
230
227
  event = event.other_event # use left event
231
- node = @sweep_line.find_node(event)
232
-
233
- next_node = node.next_node
234
- prev_node = node.prev_node
235
- node.delete
236
- unless prev_node.anchor? || next_node.anchor?
237
- possible_intersection(prev_node.value, next_node.value)
228
+ prev_ev, next_ev = @sweep_line.delete(event)
229
+ if prev_ev && next_ev
230
+ possible_intersection(prev_ev, next_ev)
238
231
  end
239
232
  end
240
233
  end
@@ -271,7 +264,7 @@ module Geom2D
271
264
  return if segment.degenerate?
272
265
  start_point_is_left = (segment.start_point == segment.min)
273
266
  e1 = SweepEvent.new(start_point_is_left, segment.start_point, polygon_type)
274
- e2 = SweepEvent.new(!start_point_is_left, segment.end_point, polygon_type, other_event: e1)
267
+ e2 = SweepEvent.new(!start_point_is_left, segment.end_point, polygon_type, e1)
275
268
  e1.other_event = e2
276
269
  @event_queue.push(e1).push(e2)
277
270
  end
@@ -363,8 +356,8 @@ module Geom2D
363
356
  # Divides the event's segment at the given point (which has to be inside the segment) and adds
364
357
  # the resulting events to the event queue.
365
358
  def divide_segment(event, point)
366
- right = SweepEvent.new(false, point, event.polygon_type, other_event: event)
367
- left = SweepEvent.new(true, point, event.polygon_type, other_event: event.other_event)
359
+ right = SweepEvent.new(false, point, event.polygon_type, event)
360
+ left = SweepEvent.new(true, point, event.polygon_type, event.other_event)
368
361
  event.other_event.other_event = left
369
362
  event.other_event = right
370
363
  @event_queue.push(left).push(right)
@@ -64,10 +64,8 @@ module Geom2D
64
64
  return to_enum(__method__) unless block_given?
65
65
  return unless @vertices.size > 1
66
66
 
67
- 0.upto(@vertices.size - 2) do |i|
68
- yield(Geom2D::Segment(@vertices[i], @vertices[i + 1]))
69
- end
70
- yield(Geom2D::Segment(@vertices[-1], @vertices[0]))
67
+ @vertices.each_cons(2) {|v1, v2| yield(Geom2D::Segment.new(v1, v2)) }
68
+ yield(Geom2D::Segment.new(@vertices[-1], @vertices[0]))
71
69
  end
72
70
 
73
71
  # Returns the BoundingBox of this polygon, or an empty BoundingBox if the polygon has no
@@ -83,7 +81,7 @@ module Geom2D
83
81
  def ccw?
84
82
  return true if @vertices.empty?
85
83
  area = @vertices[-1].wedge(@vertices[0])
86
- 1.upto(@vertices.size - 2) {|i| area += @vertices[i].wedge(@vertices[i + 1]) }
84
+ 0.upto(@vertices.size - 2) {|i| area += @vertices[i].wedge(@vertices[i + 1]) }
87
85
  area >= 0
88
86
  end
89
87
 
@@ -110,27 +110,32 @@ module Geom2D
110
110
  def intersect(segment)
111
111
  p0 = start_point
112
112
  p1 = segment.start_point
113
- d0 = direction
114
- d1 = segment.direction
115
- e = p1 - p0
113
+ d0x = end_point.x - start_point.x
114
+ d0y = end_point.y - start_point.y
115
+ d1x = segment.end_point.x - segment.start_point.x
116
+ d1y = segment.end_point.y - segment.start_point.y
117
+ ex = p1.x - p0.x
118
+ ey = p1.y - p0.y
116
119
 
117
- cross = d0.wedge(d1).to_f # cross product of direction vectors
120
+ cross = (d0x * d1y - d1x * d0y).to_f # cross product of direction vectors
118
121
 
119
122
  if cross.abs > Utils.precision # segments are not parallel
120
- s = e.wedge(d1) / cross
123
+ s = (ex * d1y - d1x * ey) / cross
121
124
  return nil if s < 0 || s > 1
122
- t = e.wedge(d0) / cross
125
+ t = (ex * d0y - d0x * ey) / cross
123
126
  return nil if t < 0 || t > 1
124
127
 
125
- result = p0 + [s * d0.x, s * d0.y]
126
- result = start_point if result == start_point
127
- result = end_point if result == end_point
128
- result = segment.start_point if result == segment.start_point
129
- result = segment.end_point if result == segment.end_point
130
- return result
128
+ result = p0 + Point.new(s * d0x, s * d0y)
129
+ return case result
130
+ when start_point then start_point
131
+ when end_point then end_point
132
+ when segment.start_point then segment.start_point
133
+ when segment.end_point then segment.end_point
134
+ else result
135
+ end
131
136
  end
132
137
 
133
- return nil if e.wedge(d0).abs > Utils.precision # non-intersecting parallel segment lines
138
+ return nil if (ex * d0y - d0x * ey).abs > Utils.precision # non-intersecting parallel segment lines
134
139
 
135
140
  e0 = end_point
136
141
  e1 = segment.end_point
@@ -13,7 +13,7 @@ module Geom2D
13
13
  # Contains utility methods and classes.
14
14
  module Utils
15
15
 
16
- autoload(:SortedLinkedList, 'geom2d/utils/sorted_linked_list')
16
+ autoload(:SortedList, 'geom2d/utils/sorted_list')
17
17
 
18
18
  # The precision when comparing two floats, defaults to 1e-10.
19
19
  singleton_class.attr_accessor :precision
@@ -0,0 +1,86 @@
1
+ # -*- frozen_string_literal: true -*-
2
+ #
3
+ #--
4
+ # geom2d - 2D Geometric Objects and Algorithms
5
+ # Copyright (C) 2018 Thomas Leitner <t_leitner@gmx.at>
6
+ #
7
+ # This software may be modified and distributed under the terms
8
+ # of the MIT license. See the LICENSE file for details.
9
+ #++
10
+
11
+ module Geom2D
12
+ module Utils
13
+
14
+ # A list that keeps its items sorted. Currently only used by
15
+ # Geom2D::Algorithms::PolygonOperation and therefore with special methods for the latter.
16
+ class SortedList
17
+
18
+ include Enumerable
19
+
20
+ # Creates a new SortedList using the +comparator+ or the given block as compare function.
21
+ #
22
+ # The comparator has to respond to +call(a, b)+ where +a+ is the value to be inserted and +b+
23
+ # is the value to which it is compared. The return value should be +true+ if the value +a+
24
+ # should be inserted before +b+, i.e. at the position of +b+.
25
+ def initialize(comparator = nil, &block)
26
+ @list = []
27
+ @comparator = comparator || block
28
+ end
29
+
30
+ # Returns +true+ if the list is empty?
31
+ def empty?
32
+ @list.empty?
33
+ end
34
+
35
+ # Returns the last value in the list.
36
+ def last
37
+ @list.last
38
+ end
39
+
40
+ # Yields each value in sorted order.
41
+ #
42
+ # If no block is given, an enumerator is returned.
43
+ def each(&block) #:yield: value
44
+ @list.each(&block)
45
+ end
46
+
47
+ # Inserts the value and returns self.
48
+ def push(value)
49
+ insert(value)
50
+ self
51
+ end
52
+
53
+ # Inserts a new value into the list (at a position decided by the compare function) and
54
+ # returns the previous-previous, previous and next values.
55
+ def insert(value)
56
+ i = @list.bsearch_index {|el| @comparator.call(value, el) } || @list.size
57
+ @list.insert(i, value)
58
+ [(i <= 1 ? nil : @list[i - 2]), (i == 0 ? nil : @list[i - 1]), @list[i + 1]]
59
+ end
60
+
61
+ # Deletes the given value and returns the previous and next values.
62
+ def delete(value)
63
+ i = @list.index(value)
64
+ result = [(i == 0 ? nil : @list[i - 1]), @list[i + 1]]
65
+ @list.delete_at(i)
66
+ result
67
+ end
68
+
69
+ # Clears the list.
70
+ def clear
71
+ @list.clear
72
+ end
73
+
74
+ # Removes the top value from the list and returns it.
75
+ def pop
76
+ @list.pop
77
+ end
78
+
79
+ def inspect #:nodoc:
80
+ "#<#{self.class.name}:0x#{object_id.to_s(16).rjust(0.size * 2, '0')} #{to_a}>"
81
+ end
82
+
83
+ end
84
+
85
+ end
86
+ end
@@ -11,6 +11,6 @@
11
11
  module Geom2D
12
12
 
13
13
  # The version of Geom2D
14
- VERSION = '0.1.0'
14
+ VERSION = '0.2.0'
15
15
 
16
16
  end
@@ -7,7 +7,7 @@ describe Geom2D::Algorithms::PolygonOperation::SweepEvent do
7
7
  before do
8
8
  @klass = Geom2D::Algorithms::PolygonOperation::SweepEvent
9
9
  @left = @klass.new(true, Geom2D::Point(5, 3), :subject)
10
- @right = @klass.new(false, Geom2D::Point(8, 5), :subject, other_event: @left)
10
+ @right = @klass.new(false, Geom2D::Point(8, 5), :subject, @left)
11
11
  @left.other_event = @right
12
12
  end
13
13
 
@@ -29,7 +29,7 @@ describe Geom2D::Algorithms::PolygonOperation::SweepEvent do
29
29
  end
30
30
 
31
31
  it "detects vertical segments" do
32
- assert(@klass.new(false, Geom2D::Point(5, 8), :subject, other_event: @left).vertical?)
32
+ assert(@klass.new(false, Geom2D::Point(5, 8), :subject, @left).vertical?)
33
33
  end
34
34
  end
35
35
 
@@ -56,6 +56,8 @@ describe Geom2D::Polygon do
56
56
  assert(@polygon.ccw?)
57
57
  @polygon.reverse!
58
58
  refute(@polygon.ccw?)
59
+ refute(Geom2D::Polygon([2, 2], [20, 5], [10, 3.0]).ccw?)
60
+ assert(Geom2D::Polygon([2, 2], [20, 5], [10, 3.4]).ccw?)
59
61
  end
60
62
 
61
63
  it "reverses the vertex list" do
@@ -1,29 +1,38 @@
1
1
  # -*- frozen_string_literal: true -*-
2
2
 
3
3
  require 'test_helper'
4
- require 'geom2d/utils/sorted_linked_list'
4
+ require 'geom2d/utils/sorted_list'
5
5
 
6
- describe Geom2D::Utils::SortedLinkedList do
6
+ describe Geom2D::Utils::SortedList do
7
7
  before do
8
- @list = Geom2D::Utils::SortedLinkedList.new {|a, b| a < b }
8
+ @list = Geom2D::Utils::SortedList.new {|a, b| a < b }
9
9
  end
10
10
 
11
11
  describe "insert" do
12
- it "inserts a value and returns its associated node" do
13
- node = @list.insert(1)
14
- assert_equal(1, node.value)
15
- assert(node.prev_node.anchor?)
16
- assert(node.next_node.anchor?)
12
+ it "inserts a value and returns surrounding values" do
13
+ ppv, pv, nv = @list.insert(1)
14
+ assert_nil(ppv)
15
+ assert_nil(pv)
16
+ assert_nil(nv)
17
17
  end
18
18
 
19
19
  it "uses the comparator for choosing the place to insert the value" do
20
20
  @list.insert(10)
21
21
  @list.insert(5)
22
22
  @list.insert(8)
23
- assert_equal([5, 8, 10], @list.to_a)
23
+ ppv, pv, nv = @list.insert(9)
24
+ assert_equal([5, 8, 10], [ppv, pv, nv])
25
+ assert_equal([5, 8, 9, 10], @list.to_a)
24
26
  end
25
27
  end
26
28
 
29
+ it "deletes a value from the list and returns the neighbouring values" do
30
+ @list.push(8).push(5).push(3).push(4).push(6)
31
+ assert_equal([6, nil], @list.delete(8))
32
+ assert_equal([nil, 4], @list.delete(3))
33
+ assert_equal([4, 6], @list.delete(5))
34
+ end
35
+
27
36
  it "returns whether it is empty" do
28
37
  assert(@list.empty?)
29
38
  @list.insert(5)
@@ -41,14 +50,6 @@ describe Geom2D::Utils::SortedLinkedList do
41
50
  assert_equal(10, @list.last)
42
51
  end
43
52
 
44
- it "returns the first node found for a given value" do
45
- @list.push(5).push(8)
46
- node = @list.find_node(8)
47
- assert_equal(8, node.value)
48
- assert_equal(5, node.prev_node.value)
49
- assert(node.next_node.anchor?)
50
- end
51
-
52
53
  it "pops the top value of the list" do
53
54
  @list.push(8).push(5)
54
55
  assert_equal(8, @list.pop)
@@ -60,11 +61,6 @@ describe Geom2D::Utils::SortedLinkedList do
60
61
  assert(@list.empty?)
61
62
  end
62
63
 
63
- it "deletes a value from the list" do
64
- @list.push(8).push(5)
65
- assert_equal(8, @list.delete(8))
66
- end
67
-
68
64
  it "can be inspected" do
69
65
  @list.push(8).push(5)
70
66
  assert_match(/[5, 8]/, @list.inspect)
metadata CHANGED
@@ -1,21 +1,42 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: geom2d
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas Leitner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-03-28 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2018-12-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rubocop
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.58'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 0.58.2
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '0.58'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 0.58.2
13
33
  description:
14
34
  email: t_leitner@gmx.at
15
35
  executables: []
16
36
  extensions: []
17
37
  extra_rdoc_files: []
18
38
  files:
39
+ - CHANGELOG.md
19
40
  - CONTRIBUTERS
20
41
  - LICENSE
21
42
  - README.md
@@ -30,7 +51,7 @@ files:
30
51
  - lib/geom2d/polygon_set.rb
31
52
  - lib/geom2d/segment.rb
32
53
  - lib/geom2d/utils.rb
33
- - lib/geom2d/utils/sorted_linked_list.rb
54
+ - lib/geom2d/utils/sorted_list.rb
34
55
  - lib/geom2d/version.rb
35
56
  - test/geom2d/algorithms/test_polygon_operation.rb
36
57
  - test/geom2d/test_algorithms.rb
@@ -39,7 +60,7 @@ files:
39
60
  - test/geom2d/test_polygon.rb
40
61
  - test/geom2d/test_polygon_set.rb
41
62
  - test/geom2d/test_segment.rb
42
- - test/geom2d/utils/test_sorted_linked_list.rb
63
+ - test/geom2d/utils/test_sorted_list.rb
43
64
  - test/test_helper.rb
44
65
  homepage: https://geom2d.gettalong.org
45
66
  licenses:
@@ -1,154 +0,0 @@
1
- # -*- frozen_string_literal: true -*-
2
- #
3
- #--
4
- # geom2d - 2D Geometric Objects and Algorithms
5
- # Copyright (C) 2018 Thomas Leitner <t_leitner@gmx.at>
6
- #
7
- # This software may be modified and distributed under the terms
8
- # of the MIT license. See the LICENSE file for details.
9
- #++
10
-
11
- module Geom2D
12
- module Utils
13
-
14
- # A doubly linked list that keeps its items sorted.
15
- class SortedLinkedList
16
-
17
- include Enumerable
18
-
19
- # A node of the double linked list.
20
- class Node
21
-
22
- AnchorValue = Object.new #:nodoc:
23
-
24
- # Creates a Node object that can be used as the anchor of the doubly linked list.
25
- def self.create_anchor
26
- Node.new(AnchorValue).tap {|anchor| anchor.next_node = anchor.prev_node = anchor }
27
- end
28
-
29
- # The previous node in the list. The first node points to the anchor node.
30
- attr_accessor :prev_node
31
-
32
- # The next node in the list. The last node points to the anchor node.
33
- attr_accessor :next_node
34
-
35
- # The value of the node.
36
- attr_accessor :value
37
-
38
- # Creates a new Node for the given value, with optional previous and next nodes to point to.
39
- def initialize(value, prev_node = nil, next_node = nil)
40
- @prev_node = prev_node
41
- @next_node = next_node
42
- @value = value
43
- end
44
-
45
- # Returns +true+ if this node is an anchor node, i.e. the start and end of this list.
46
- def anchor?
47
- @value == AnchorValue
48
- end
49
-
50
- # Inserts this node before the given node.
51
- def insert_before(node)
52
- @prev_node = node.prev_node
53
- @next_node = node
54
- node.prev_node.next_node = self
55
- node.prev_node = self
56
- self
57
- end
58
-
59
- # Deletes this node from the linked list.
60
- def delete
61
- @prev_node.next_node = @next_node
62
- @next_node.prev_node = @prev_node
63
- @prev_node = @next_node = nil
64
- @value
65
- end
66
-
67
- end
68
-
69
- # Creates a new SortedLinkedList using the +comparator+ or the given block as compare
70
- # function.
71
- #
72
- # The comparator has to respond to +call(a, b)+ where +a+ is the value to be inserted and +b+
73
- # is the value to which it is compared. The return value should be +true+ if the value +a+
74
- # should be inserted before +b+, i.e. at the position of +b+.
75
- def initialize(comparator = nil, &block)
76
- @anchor = Node.create_anchor
77
- @comparator = comparator || block
78
- end
79
-
80
- # Returns +true+ if the list is empty?
81
- def empty?
82
- @anchor.next_node == @anchor
83
- end
84
-
85
- # Returns the last value in the list.
86
- def last
87
- empty? ? nil : @anchor.prev_node.value
88
- end
89
-
90
- # Yields each value in sorted order.
91
- #
92
- # If no block is given, an enumerator is returned.
93
- def each #:yield: value
94
- return to_enum(__method__) unless block_given?
95
- current = @anchor.next_node
96
- while current != @anchor
97
- yield(current.value)
98
- current = current.next_node
99
- end
100
- end
101
-
102
- # Returns the node with the given value, or +nil+ if no such value is found.
103
- def find_node(value)
104
- current = @anchor.next_node
105
- while current != @anchor
106
- return current if current.value == value
107
- current = current.next_node
108
- end
109
- end
110
-
111
- # Inserts the value and returns self.
112
- def push(value)
113
- insert(value)
114
- self
115
- end
116
-
117
- # Inserts a new node with the given value into the list (at a position decided by the compare
118
- # function) and returns it.
119
- def insert(value)
120
- node = Node.new(value)
121
- current = @anchor.next_node
122
- while current != @anchor
123
- if @comparator.call(node.value, current.value)
124
- return node.insert_before(current)
125
- end
126
- current = current.next_node
127
- end
128
- node.insert_before(@anchor)
129
- end
130
-
131
- # Clears the list.
132
- def clear
133
- @anchor = Node.create_anchor
134
- end
135
-
136
- # Removes the top node from the list and returns its value.
137
- def pop
138
- return nil if empty?
139
- @anchor.prev_node.delete
140
- end
141
-
142
- # Deletes the node with the given value from the list and returns its value.
143
- def delete(value)
144
- find_node(value)&.delete
145
- end
146
-
147
- def inspect #:nodoc:
148
- "#<#{self.class.name}:0x#{object_id.to_s(16).rjust(0.size * 2, '0')} #{to_a}>"
149
- end
150
-
151
- end
152
-
153
- end
154
- end