geom2d 0.1.0 → 0.2.0

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