symmetry_axis 0.1.2 → 0.1.3

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
  SHA1:
3
- metadata.gz: 7ac00b6ca3f12a6cf9ff5f8f34ef6e440bbc8e6d
4
- data.tar.gz: 0da763b5764c2e3098aabce450ce456f1074f31d
3
+ metadata.gz: ec1f5b69d4fc36f4c0c526330060b996ed5c5718
4
+ data.tar.gz: 91850dc12093fc4b9fb4adb0174807fda74e7658
5
5
  SHA512:
6
- metadata.gz: 4bad29fbf34ede0252c1db5ec83af74531e3aa1ea9c8c32ab723c287095ea0f0faf26215443cd88480e861e17ab73a4a401ca60db1e2e924b9bb5655771383fd
7
- data.tar.gz: cabbcf950fb5891d326775907f34c86c1193dea00f94f0da442175a6420c77b8327af5d05861aa975a718b746f0832d867478b92615f43e895c9e845bcb434e9
6
+ metadata.gz: 838a5ebaca4fbe9137b3dd4f91bae665ca0385a088a35b2e1199b224570f9d86e430be2940d45981909cc169dfa9982b6fb876370c6c31da2e3a5888aa7f10e8
7
+ data.tar.gz: b0255602df7641adb7eeb17e4ffe2e8edf2f9fd812efce39e868dcb07cdc8677c603cd7cd83cfcbca6174879e3946a5e99e1e813eadad084d451efd8a12f1803
@@ -0,0 +1,85 @@
1
+ module SymmetryAxis
2
+ # The algorithm, based on principle, every point have a pair.
3
+ # It uses the first got point and iterates over +(N - 1)+
4
+ # other points to find a line,
5
+ # that another points lie by twos on lines parallel to.
6
+ # The perpendicular line to this line is the symmetry axis.
7
+ class Finder
8
+ attr_reader :line
9
+
10
+ def initialize(point_array)
11
+ @input = point_array
12
+ return if @input.size.odd?
13
+ @impossible_angles = []
14
+ @two_points_cursor = 1
15
+ loop do
16
+ return unless take_two_points # step 1
17
+ break if points_are_on_parallel_lines # step 2
18
+ end
19
+ find_axis # step 3
20
+ end
21
+
22
+ private
23
+
24
+ def take_two_points
25
+ @two_points = [@input.first]
26
+ (@two_points_cursor...(@input.size)).each do |i|
27
+ rad = Line.for_points(@two_points[0], @input[i]).angle_rad
28
+ next if impossible?(rad)
29
+ choose_point i
30
+ return true
31
+ end
32
+ false
33
+ end
34
+
35
+ def choose_point(i)
36
+ @two_points_cursor = i + 1
37
+ @two_points[1] = @input[i]
38
+ @two_points_line = Line.for_points(@two_points[0], @input[i])
39
+ end
40
+
41
+ def impossible?(rad)
42
+ return false if @impossible_angles.empty?
43
+ impossible_rad = @impossible_angles.min_by { |e| (e - rad).abs }
44
+ (rad - impossible_rad).abs < (2 * Line.EPS)
45
+ end
46
+
47
+ def points_are_on_parallel_lines
48
+ other_points = @input.select { |p| !@two_points.include?(p) }
49
+ @axis_perpendicular_points = @two_points.dup
50
+ check_perpendiculars(other_points)
51
+ end
52
+
53
+ def check_perpendiculars(points)
54
+ loop do
55
+ break if points.empty?
56
+ line = @two_points_line.parallel(*points.first)
57
+ size0 = points.size
58
+ on_line = points.select { |p2| line.on_me?(*p2) }
59
+ points -= on_line
60
+ return false if size0 - points.size < 2
61
+ save_perpendicular_points(on_line, line)
62
+ end
63
+ true
64
+ end
65
+
66
+ def save_perpendicular_points(points, line)
67
+ return if line != @two_points_line
68
+ points += @two_points
69
+ @axis_perpendicular_points = pppair(points, line)
70
+ end
71
+
72
+ def pppair(points, line)
73
+ if line.vertical?
74
+ [points.min_by { |p| p[1] }, points.max_by { |p| p[1] }]
75
+ else
76
+ [points.min_by { |p| p[0] }, points.max_by { |p| p[0] }]
77
+ end
78
+ end
79
+
80
+ def find_axis
81
+ p1, p2 = *@axis_perpendicular_points
82
+ @line = Line.symmetryof(p1[0], p1[1], p2[0], p2[1])
83
+ end
84
+ end
85
+ end
@@ -1,126 +1,128 @@
1
- # Every line here defined by the coefficients of the equation:
2
- # y = a + bx
3
- # But this equation can not describe vertical lines (infinite +b+).
4
- # So the vertical lines are defined by +x+.
5
- class Line
6
- attr_reader :a, :b, :x
7
- EPS, TOO_BIG_B = 0.000001, 100_000_000
8
-
9
- def initialize(x1, y1, x2, y2)
10
- @vertical = nil
11
- if x1 < x2
12
- resolve_coefs(x1, y1, x2, y2)
13
- else
14
- resolve_coefs(x2, y2, x1, y1)
1
+ module SymmetryAxis
2
+ # The lines here are defined by the coefficients of the equation:
3
+ # y = a + bx
4
+ # But this equation can not describe vertical lines (infinite +b+).
5
+ # So the vertical lines are defined by +x+.
6
+ class Line
7
+ attr_reader :a, :b, :x
8
+ EPS, TOO_BIG_B = 0.000001, 100_000_000
9
+
10
+ def initialize(x1, y1, x2, y2)
11
+ @vertical = nil
12
+ if x1 < x2
13
+ resolve_coefs(x1, y1, x2, y2)
14
+ else
15
+ resolve_coefs(x2, y2, x1, y1)
16
+ end
15
17
  end
16
- end
17
18
 
18
- # Shorthand for constructor.
19
- # @return [Line]
20
- def self.for_points(p1, p2)
21
- Line.new(p1[0], p1[1], p2[0], p2[1])
22
- end
19
+ # Shorthand for constructor.
20
+ # @return [Line]
21
+ def self.for_points(p1, p2)
22
+ Line.new(p1[0], p1[1], p2[0], p2[1])
23
+ end
23
24
 
24
- def ==(other)
25
- self.class.aeq(self, other) && self.class.beq(self, other)
26
- end
25
+ def ==(other)
26
+ self.class.aeq(self, other) && self.class.beq(self, other)
27
+ end
27
28
 
28
- # @return [String]
29
- def to_s
30
- if !vertical?
31
- "(a=#{@a}, b=#{@b})"
32
- else
33
- "(x=#{@x})"
29
+ # @return [String]
30
+ def to_s
31
+ if !vertical?
32
+ "(a=#{@a}, b=#{@b})"
33
+ else
34
+ "(x=#{@x})"
35
+ end
34
36
  end
35
- end
36
37
 
37
- # Is the line strictly vertical. This line could be created from
38
- # almost vertical pair of points (+b+ coefficient was just Too Big).
39
- #
40
- # If +true+, you cannot use +a+ and +b+ fields
41
- # (you can read +x+ field instead).
42
- def vertical?
43
- @vertical = (nil == @b) || (TOO_BIG_B <= @b.abs) if nil == @vertical
44
- @vertical
45
- end
38
+ # Is the line strictly vertical. This line could be created from
39
+ # almost vertical pair of points (+b+ coefficient was just Too Big).
40
+ #
41
+ # If +true+, you cannot use +a+ and +b+ fields
42
+ # (you can read +x+ field instead).
43
+ def vertical?
44
+ @vertical = (nil == @b) || (TOO_BIG_B <= @b.abs) if nil == @vertical
45
+ @vertical
46
+ end
46
47
 
47
- # Zero means three o'clock, +pi/2+ means noon.
48
- # @return [Float]
49
- def angle_rad
50
- return (Math::PI / 2) if vertical?
51
- Math.atan(@b)
52
- end
48
+ # Zero means three o'clock, +pi/2+ means noon.
49
+ # @return [Float]
50
+ def angle_rad
51
+ return (Math::PI / 2) if vertical?
52
+ Math.atan(@b)
53
+ end
53
54
 
54
- # Zero means three o'clock, 90 means noon.
55
- # @return [Float]
56
- def angle_degrees
57
- angle_rad / Math::PI * 180
58
- end
55
+ # Zero means three o'clock, 90 means noon.
56
+ # @return [Float]
57
+ def angle_degrees
58
+ angle_rad / Math::PI * 180
59
+ end
59
60
 
60
- # Does the point lie on the line.
61
- def on_me?(x, y)
62
- if vertical?
63
- (@x - x).abs < EPS
64
- else
65
- f = @a + @b * x
66
- (f - y).abs < on_me_eps(x)
61
+ # Does the point lie on the line.
62
+ def on_me?(x, y)
63
+ if vertical?
64
+ (@x - x).abs < EPS
65
+ else
66
+ f = @a + @b * x
67
+ (f - y).abs < on_me_eps(x)
68
+ end
67
69
  end
68
- end
69
70
 
70
- def on_me_eps(x)
71
- e = EPS
72
- e += (EPS / @b).abs if @b != 0
73
- e += (EPS / x).abs if x != 0
74
- e
75
- end
71
+ def on_me_eps(x)
72
+ e = EPS
73
+ e += (EPS / @b).abs if @b != 0
74
+ e += (EPS / x).abs if x != 0
75
+ e
76
+ end
76
77
 
77
- # Symmetry line of two points.
78
- # The perpendicular line drawn through the center
79
- # of the line segment between the points.
80
- # @return [Line]
81
- def self.symmetryof(x1, y1, x2, y2)
82
- line = Line.new(x1, y1, x2, y2)
83
- x, y = (x1 + x2) / 2, (y1 + y2) / 2
84
- if line.vertical?
85
- Line.new(0, y, 1, y)
86
- else
87
- r = line.angle_rad + (Math::PI / 2)
88
- return Line.new(x, 0, x, 1) if Math::PI / 2 == r.abs
89
- return Line.new(x, y, x + 1, y + Math.tan(r))
78
+ # Symmetry line of two points.
79
+ # The perpendicular line drawn through the center
80
+ # of the line segment between the points.
81
+ # @return [Line]
82
+ def self.symmetryof(x1, y1, x2, y2)
83
+ line = Line.new(x1, y1, x2, y2)
84
+ x, y = (x1 + x2) / 2, (y1 + y2) / 2
85
+ if line.vertical?
86
+ Line.new(0, y, 1, y)
87
+ else
88
+ r = line.angle_rad + (Math::PI / 2)
89
+ return Line.new(x, 0, x, 1) if Math::PI / 2 == r.abs
90
+ return Line.new(x, y, x + 1, y + Math.tan(r))
91
+ end
90
92
  end
91
- end
92
93
 
93
- # Draw a parallel line through the point.
94
- # @return [Line]
95
- def parallel(x, y)
96
- return Line.new(x, y, x, y + 1) if vertical?
97
- Line.new(x, y, x + 1, y + @b)
98
- end
94
+ # Draw a parallel line through the point.
95
+ # @return [Line]
96
+ def parallel(x, y)
97
+ return Line.new(x, y, x, y + 1) if vertical?
98
+ Line.new(x, y, x + 1, y + @b)
99
+ end
99
100
 
100
- private :on_me_eps
101
+ private :on_me_eps
101
102
 
102
- private
103
+ private
103
104
 
104
- def resolve_coefs(xmin, yxmin, xmax, yxmax)
105
- if 0 == (xmax - xmin).abs
106
- @b, @a = nil, yxmin
107
- else
108
- @b = (yxmax - yxmin) / Float(xmax - xmin)
109
- @a = yxmin - @b * xmin
105
+ def resolve_coefs(xmin, yxmin, xmax, yxmax)
106
+ if 0 == (xmax - xmin).abs
107
+ @b, @a = nil, yxmin
108
+ else
109
+ @b = (yxmax - yxmin) / Float(xmax - xmin)
110
+ @a = yxmin - @b * xmin
111
+ end
112
+ @x, @a = xmin, 0 if vertical?
110
113
  end
111
- @x, @a = xmin, 0 if vertical?
112
- end
113
114
 
114
- def self.aeq(o1, o2)
115
- (o1.a - o2.a).abs < (2 * EPS)
116
- end
115
+ def self.aeq(o1, o2)
116
+ (o1.a - o2.a).abs < (2 * EPS)
117
+ end
117
118
 
118
- def self.beq(o1, o2)
119
- if o1.vertical? || o2.vertical?
120
- return false unless o1.vertical? && o2.vertical?
121
- (o1.x - o2.x).abs < (2 * EPS)
122
- else
123
- (o1.b - o2.b).abs < (2 * EPS)
119
+ def self.beq(o1, o2)
120
+ if o1.vertical? || o2.vertical?
121
+ return false unless o1.vertical? && o2.vertical?
122
+ (o1.x - o2.x).abs < (2 * EPS)
123
+ else
124
+ (o1.b - o2.b).abs < (2 * EPS)
125
+ end
124
126
  end
125
127
  end
126
128
  end
data/lib/symmetry_axis.rb CHANGED
@@ -1,85 +1,11 @@
1
- # The algorithm, based on principle, every point have a pair.
2
- # It uses the first got point and iterates over +(N - 1)+
3
- # other points to find a line,
4
- # all points lie by twos on lines parallel to.
5
- # The perpendicular line to this line is the symmetry axis.
6
- class SymmetryAxis
7
- attr_reader :line
8
-
9
- def initialize(point_array)
10
- @input = point_array
11
- return if @input.size.odd?
12
- @impossible_angles = []
13
- @two_points_cursor = 1
14
- loop do
15
- return unless take_two_points # step 1
16
- break if points_are_on_parallel_lines # step 2
17
- end
18
- find_axis # step 3
19
- end
20
-
21
- private
22
-
23
- def take_two_points
24
- @two_points = [@input.first]
25
- (@two_points_cursor...(@input.size)).each do |i|
26
- rad = Line.for_points(@two_points[0], @input[i]).angle_rad
27
- next if impossible?(rad)
28
- choose_point i
29
- return true
30
- end
31
- false
32
- end
33
-
34
- def choose_point(i)
35
- @two_points_cursor = i + 1
36
- @two_points[1] = @input[i]
37
- @two_points_line = Line.for_points(@two_points[0], @input[i])
38
- end
39
-
40
- def impossible?(rad)
41
- return false if @impossible_angles.empty?
42
- impossible_rad = @impossible_angles.min_by { |e| (e - rad).abs }
43
- (rad - impossible_rad).abs < (2 * Line.EPS)
44
- end
45
-
46
- def points_are_on_parallel_lines
47
- other_points = @input.select { |p| !@two_points.include?(p) }
48
- @axis_perpendicular_points = @two_points.dup
49
- check_perpendiculars(other_points)
50
- end
51
-
52
- def check_perpendiculars(points)
53
- loop do
54
- break if points.empty?
55
- line = @two_points_line.parallel(*points.first)
56
- size0 = points.size
57
- on_line = points.select { |p2| line.on_me?(*p2) }
58
- points -= on_line
59
- return false if size0 - points.size < 2
60
- save_perpendicular_points(on_line, line)
61
- end
62
- true
63
- end
64
-
65
- def save_perpendicular_points(points, line)
66
- return if line != @two_points_line
67
- points += @two_points
68
- @axis_perpendicular_points = pppair(points, line)
69
- end
70
-
71
- def pppair(points, line)
72
- if line.vertical?
73
- [points.min_by { |p| p[1] }, points.max_by { |p| p[1] }]
74
- else
75
- [points.min_by { |p| p[0] }, points.max_by { |p| p[0] }]
76
- end
77
- end
78
-
79
- def find_axis
80
- p1, p2 = *@axis_perpendicular_points
81
- @line = Line.symmetryof(p1[0], p1[1], p2[0], p2[1])
1
+ module SymmetryAxis
2
+ # Shorthand method for
3
+ # Finder.new(points).line
4
+ # @return [Line]
5
+ def self.of(points)
6
+ Finder.new(points).line
82
7
  end
83
8
  end
84
9
 
85
10
  require 'symmetry_axis/line'
11
+ require 'symmetry_axis/finder'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: symmetry_axis
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - "Георгий Устинов"
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-08-29 00:00:00.000000000 Z
11
+ date: 2015-09-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -62,6 +62,7 @@ extensions: []
62
62
  extra_rdoc_files: []
63
63
  files:
64
64
  - lib/symmetry_axis.rb
65
+ - lib/symmetry_axis/finder.rb
65
66
  - lib/symmetry_axis/line.rb
66
67
  homepage: https://github.com/georgy7/symmetry_axis
67
68
  licenses: