flash_math 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +1 -0
  3. data/.gitignore +17 -0
  4. data/.rspec +4 -0
  5. data/.travis.yml +13 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +187 -0
  9. data/Rakefile +6 -0
  10. data/flash_math.gemspec +27 -0
  11. data/lib/flash_math.rb +3 -0
  12. data/lib/flash_math/modules/geometry.rb +10 -0
  13. data/lib/flash_math/modules/geometry/geometric_bounding_box.rb +16 -0
  14. data/lib/flash_math/modules/geometry/geometric_distance.rb +20 -0
  15. data/lib/flash_math/modules/geometry/geometric_line.rb +87 -0
  16. data/lib/flash_math/modules/geometry/geometric_point.rb +19 -0
  17. data/lib/flash_math/modules/geometry/geometric_point_in_polygon.rb +63 -0
  18. data/lib/flash_math/modules/geometry/geometric_polygon.rb +36 -0
  19. data/lib/flash_math/modules/geometry/geometric_segment.rb +128 -0
  20. data/lib/flash_math/modules/geometry/geometric_vector.rb +46 -0
  21. data/lib/flash_math/modules/statistics.rb +1 -0
  22. data/lib/flash_math/modules/statistics/statistical_spread.rb +129 -0
  23. data/lib/flash_math/version.rb +3 -0
  24. data/spec/lib/geometry/geometric_bounding_box_spec.rb +67 -0
  25. data/spec/lib/geometry/geometric_distance_spec.rb +55 -0
  26. data/spec/lib/geometry/geometric_line_spec.rb +287 -0
  27. data/spec/lib/geometry/geometric_point_spec.rb +53 -0
  28. data/spec/lib/geometry/geometric_polygon_spec.rb +148 -0
  29. data/spec/lib/geometry/geometric_segment_spec.rb +231 -0
  30. data/spec/lib/geometry/geometric_vector_spec.rb +110 -0
  31. data/spec/lib/statistics/statistics_spread_spec.rb +219 -0
  32. data/spec/spec_helper.rb +4 -0
  33. metadata +168 -0
@@ -0,0 +1,19 @@
1
+ class GeometricPoint < Struct.new(:x, :y)
2
+
3
+ def self.new_by_array(array)
4
+ self.new(array[0], array[1])
5
+ end
6
+
7
+ def to_vector
8
+ GeometricVector.new(x, y)
9
+ end
10
+
11
+ def advance_by(vector)
12
+ GeometricPoint.new(x + vector.x, y + vector.y)
13
+ end
14
+
15
+ def ==(another_point)
16
+ x === another_point.x && y === another_point.y
17
+ end
18
+
19
+ end
@@ -0,0 +1,63 @@
1
+ class GeometricPointInPolygon < Struct.new(:point, :polygon)
2
+ extend Memoist
3
+
4
+ def inside?
5
+ point_location == :inside
6
+ end
7
+
8
+ def outside?
9
+ point_location == :outside
10
+ end
11
+
12
+ def on_the_boundary?
13
+ point_location == :on_the_boundary
14
+ end
15
+
16
+ def point_location
17
+ return :outside unless bounding_box.contains?(point)
18
+ return :on_the_boundary if point_is_vertex? || point_on_edge?
19
+
20
+ intersection_count(choose_good_ray).odd? ? :inside : :outside
21
+ end
22
+
23
+ delegate :vertices, :edges, :bounding_box, to: :polygon
24
+ memoize :point_location, :edges, :bounding_box
25
+
26
+ private
27
+
28
+ def choose_good_ray
29
+ ray = random_ray
30
+ while ! good_ray?(ray) do
31
+ ray = random_ray
32
+ end
33
+ ray
34
+ end
35
+
36
+ def good_ray?(ray)
37
+ edges.none? { |edge| edge.parallel_to?(ray) } && vertices.none? { |vertex| ray.contains_point?(vertex) }
38
+ end
39
+
40
+ def intersection_count(ray)
41
+ edges.select { |edge| edge.intersects_with?(ray) }.size
42
+ end
43
+
44
+ def point_is_vertex?
45
+ vertices.any? { |vertex| vertex == point }
46
+ end
47
+
48
+ def point_on_edge?
49
+ edges.any? { |edge| edge.contains_point?(point) }
50
+ end
51
+
52
+ def random_ray
53
+ random_direction = rand * (2 * Math::PI)
54
+
55
+ ray_endpoint = GeometricPoint.new(sufficient_ray_radius * Math.cos(random_direction), sufficient_ray_radius * Math.sin(random_direction))
56
+ GeometricSegment.new(point, ray_endpoint)
57
+ end
58
+
59
+ def sufficient_ray_radius
60
+ @sufficient_ray_radius ||= bounding_box.diagonal.length * 2
61
+ end
62
+
63
+ end
@@ -0,0 +1,36 @@
1
+ class GeometricPolygon < Struct.new(:vertices)
2
+
3
+ def bounding_box
4
+ leftbottom = GeometricPoint.new(vertices.map(&:x).min, vertices.map(&:y).min)
5
+ righttop = GeometricPoint.new(vertices.map(&:x).max, vertices.map(&:y).max)
6
+
7
+ GeometricBoundingBox.new(leftbottom, righttop)
8
+ end
9
+
10
+ def area
11
+ sum = 0.0
12
+ (0..vertices.length-1).each do |i|
13
+ a = vertices[i-1]
14
+ b = vertices[i]
15
+
16
+ sum = sum + ((a.x * b.y) - (a.y * b.x))
17
+ end
18
+ (sum / 2).abs
19
+ end
20
+
21
+ def contains?(point)
22
+ point_in_polygon = GeometricPointInPolygon.new(point, self)
23
+ point_in_polygon.inside? || point_in_polygon.on_the_boundary?
24
+ end
25
+
26
+ def edges
27
+ edges = []
28
+
29
+ 1.upto(vertices.length - 1) do |vertex_index|
30
+ edges << GeometricSegment.new(vertices[vertex_index - 1], vertices[vertex_index])
31
+ end
32
+
33
+ edges << GeometricSegment.new(vertices.last, vertices.first)
34
+ end
35
+
36
+ end
@@ -0,0 +1,128 @@
1
+ class GeometricSegmentsDoNotIntersect < Exception; end
2
+ class GeometricSegmentsOverlap < Exception; end
3
+
4
+ class GeometricSegment < Struct.new(:point1, :point2)
5
+
6
+ def self.new_by_arrays(point1_coordinates, point2_coordinates)
7
+ self.new(GeometricPoint.new_by_array(point1_coordinates),
8
+ GeometricPoint.new_by_array(point2_coordinates))
9
+ end
10
+
11
+ def bottommost_endpoint
12
+ ((point1.y <=> point2.y) == -1) ? point1 : point2
13
+ end
14
+
15
+ def leftmost_endpoint
16
+ ((point1.x <=> point2.x) == -1) ? point1 : point2
17
+ end
18
+
19
+ def rightmost_endpoint
20
+ ((point1.x <=> point2.x) == 1) ? point1 : point2
21
+ end
22
+
23
+ def topmost_endpoint
24
+ ((point1.y <=> point2.y) == 1) ? point1 : point2
25
+ end
26
+
27
+ def contains_point?(point)
28
+ GeometricDistance.new(point1, point2).distance ===
29
+ GeometricDistance.new(point1, point).distance + GeometricDistance.new(point, point2).distance
30
+ end
31
+
32
+ def distance_to(point)
33
+ q = point.to_vector
34
+ p1 = point1.to_vector
35
+ p2 = point2.to_vector
36
+
37
+ u = p2 - p1
38
+ v = q - p1
39
+
40
+ a = u.scalar_product(v)
41
+ if a < 0
42
+ p = p1
43
+ else
44
+ b = u.scalar_product(u)
45
+ if a > b
46
+ p = p2
47
+ else
48
+ p = p1 + (a.to_f / b * u)
49
+ end
50
+ end
51
+
52
+ return GeometricDistance.new(q, p).distance
53
+ end
54
+
55
+ def intersection_point_with(segment)
56
+ raise GeometricSegmentsDoNotIntersect unless intersects_with?(segment)
57
+ raise GeometricSegmentsOverlap if overlaps?(segment)
58
+
59
+ numerator = (segment.point1.y - point1.y) * (segment.point1.x - segment.point2.x) -
60
+ (segment.point1.y - segment.point2.y) * (segment.point1.x - point1.x);
61
+ denominator = (point2.y - point1.y) * (segment.point1.x - segment.point2.x) -
62
+ (segment.point1.y - segment.point2.y) * (point2.x - point1.x);
63
+
64
+ t = numerator.to_f / denominator;
65
+
66
+ x = point1.x + t * (point2.x - point1.x)
67
+ y = point1.y + t * (point2.y - point1.y)
68
+
69
+ GeometricPoint.new(x, y)
70
+ end
71
+
72
+ def intersects_with?(segment)
73
+ GeometricSegment.have_intersecting_bounds?(self, segment) &&
74
+ lies_on_line_intersecting?(segment) &&
75
+ segment.lies_on_line_intersecting?(self)
76
+ end
77
+
78
+ def length
79
+ GeometricDistance.new(point1, point2).distance
80
+ end
81
+
82
+ def lies_on_one_line_with?(segment)
83
+ GeometricSegment.new(point1, segment.point1).parallel_to?(self) &&
84
+ GeometricSegment.new(point1, segment.point2).parallel_to?(self)
85
+ end
86
+
87
+ def overlaps?(segment)
88
+ GeometricSegment.have_intersecting_bounds?(self, segment) &&
89
+ lies_on_one_line_with?(segment)
90
+ end
91
+
92
+ def parallel_to?(segment)
93
+ to_vector.collinear_with?(segment.to_vector)
94
+ end
95
+
96
+ def to_vector
97
+ GeometricVector.new(point2.x - point1.x, point2.y - point1.y)
98
+ end
99
+
100
+ protected
101
+
102
+ def self.have_intersecting_bounds?(segment1, segment2)
103
+ intersects_on_x_axis =
104
+ (segment1.leftmost_endpoint.x < segment2.rightmost_endpoint.x ||
105
+ segment1.leftmost_endpoint.x == segment2.rightmost_endpoint.x) &&
106
+ (segment2.leftmost_endpoint.x < segment1.rightmost_endpoint.x ||
107
+ segment2.leftmost_endpoint.x == segment1.rightmost_endpoint.x)
108
+
109
+ intersects_on_y_axis =
110
+ (segment1.bottommost_endpoint.y < segment2.topmost_endpoint.y ||
111
+ segment1.bottommost_endpoint.y == segment2.topmost_endpoint.y) &&
112
+ (segment2.bottommost_endpoint.y < segment1.topmost_endpoint.y ||
113
+ segment2.bottommost_endpoint.y == segment1.topmost_endpoint.y)
114
+
115
+ intersects_on_x_axis && intersects_on_y_axis
116
+ end
117
+
118
+ def lies_on_line_intersecting?(segment)
119
+ vector_to_first_endpoint = GeometricSegment.new(self.point1, segment.point1).to_vector
120
+ vector_to_second_endpoint = GeometricSegment.new(self.point1, segment.point2).to_vector
121
+
122
+ #FIXME: '>=' and '<=' method of Fixnum and Float should be overriden too (take precision into account)
123
+ # there is a rare case, when this method is wrong due to precision
124
+ self.to_vector.cross_product(vector_to_first_endpoint) *
125
+ self.to_vector.cross_product(vector_to_second_endpoint) <= 0
126
+ end
127
+
128
+ end
@@ -0,0 +1,46 @@
1
+ class GeometricVector < Struct.new(:x, :y)
2
+
3
+ def self.new_by_array(array)
4
+ self.new(array[0], array[1])
5
+ end
6
+
7
+ def ==(vector)
8
+ x === vector.x && y === vector.y
9
+ end
10
+
11
+ def +(vector)
12
+ GeometricVector.new(x + vector.x, y + vector.y)
13
+ end
14
+
15
+ def -(vector)
16
+ self + (-1) * vector
17
+ end
18
+
19
+ def *(scalar)
20
+ GeometricVector.new(x * scalar, y * scalar)
21
+ end
22
+
23
+ def coerce(scalar)
24
+ if scalar.is_a?(Numeric)
25
+ [self, scalar]
26
+ else
27
+ raise ArgumentError, "GeometricVector: cannot coerce #{scalar.inspect}"
28
+ end
29
+ end
30
+
31
+ def cross_product(vector)
32
+ x * vector.y - y * vector.x
33
+ end
34
+
35
+ def scalar_product(vector)
36
+ x * vector.x + y * vector.y
37
+ end
38
+
39
+ def collinear_with?(vector)
40
+ cross_product(vector) === 0
41
+ end
42
+
43
+ def modulus
44
+ Math.hypot(x ,y)
45
+ end
46
+ end
@@ -0,0 +1 @@
1
+ require 'flash_math/modules/statistics/statistical_spread'
@@ -0,0 +1,129 @@
1
+ class StatisticalSpread < Struct.new(:values)
2
+
3
+ def sum(identity=0, &block)
4
+ if block_given?
5
+ StatisticalSpread.new(values.map(&block)).sum(identity)
6
+ else
7
+ values.inject(:+) || identity
8
+ end
9
+ end
10
+
11
+ def mean
12
+ return if values.length < 1
13
+ sum / values.length.to_f
14
+ end
15
+
16
+ def median
17
+ return if values.length < 1
18
+ sorted = values.sort
19
+
20
+ if values.length % 2 == 0
21
+ (sorted[(values.length / 2) -1] + sorted[values.length / 2]) / 2.0
22
+ else
23
+ sorted[values.length / 2]
24
+ end
25
+ end
26
+
27
+ def mode
28
+ return if values.length < 1
29
+ frequency_distribution = values.inject(Hash.new(0)) { |hash, value| hash[value] += 1; hash }
30
+ top_2 = frequency_distribution.sort { |a,b| b[1] <=> a[1] } .take(2)
31
+ if top_2.length == 1
32
+ top_2.first.first
33
+ elsif top_2.first.last == top_2.last.last
34
+ nil
35
+ else
36
+ top_2.first.first
37
+ end
38
+ end
39
+
40
+ def range
41
+ return if values.length < 1
42
+ sorted = values.sort
43
+ sorted.last - sorted.first
44
+ end
45
+
46
+ def max
47
+ return if values.length < 1
48
+ values.sort.last
49
+ end
50
+
51
+ def min
52
+ return if values.length < 1
53
+ values.sort.first
54
+ end
55
+
56
+ def percentile_from_value(value)
57
+ return if values.length < 1
58
+ (values.sort.index(value) / values.length.to_f * 100).ceil
59
+ end
60
+
61
+ def value_from_percentile(percentile)
62
+ return if values.length < 1
63
+ value_index = (percentile.to_f / 100 * values.length).ceil
64
+ values.sort[value_index]
65
+ end
66
+
67
+ def variance
68
+ return if values.length < 1
69
+ precalculated_mean = StatisticalSpread.new(values).mean
70
+ sum = values.inject(0) { |accumulator, value| accumulator + (value - precalculated_mean) ** 2 }
71
+ sum / (values.length.to_f - 1)
72
+ end
73
+
74
+ def population_variance
75
+ return if values.length < 1
76
+ precalculated_mean = StatisticalSpread.new(values).mean
77
+ sum = values.inject(0) { |accumulator, value| accumulator + (value - precalculated_mean) ** 2 }
78
+ sum / values.length.to_f
79
+ end
80
+
81
+ def standard_deviation
82
+ return if values.length < 2
83
+ Math.sqrt(StatisticalSpread.new(values).variance)
84
+ end
85
+
86
+ def relative_standard_deviation
87
+ return if values.length < 1
88
+ precalculated_mean = StatisticalSpread.new(values).mean
89
+ (StatisticalSpread.new(values).population_standard_deviation / precalculated_mean) * 100.0
90
+ end
91
+
92
+ def population_standard_deviation
93
+ return if values.length < 1
94
+ Math.sqrt(StatisticalSpread.new(values).population_variance)
95
+ end
96
+
97
+ def skewness
98
+ return if values.length == 0
99
+ return 0 if values.length == 1
100
+ StatisticalSpread.new(values).sum_cubed_deviation / ((values.length - 1) * StatisticalSpread.new(values).cubed_standard_deviation.to_f)
101
+ end
102
+
103
+ def kurtosis
104
+ return if values.length == 0
105
+ return 0 if values.length == 1
106
+ StatisticalSpread.new(values).sum_quarted_deviation / ((values.length - 1) * StatisticalSpread.new(values).quarted_standard_deviation.to_f)
107
+ end
108
+
109
+ protected
110
+
111
+ def sum_cubed_deviation
112
+ precalculated_mean = StatisticalSpread.new(values).mean
113
+ values.inject(0) { |sum, value| sum + (value - precalculated_mean) ** 3}
114
+ end
115
+
116
+ def cubed_standard_deviation
117
+ StatisticalSpread.new(values).standard_deviation ** 3
118
+ end
119
+
120
+ def sum_quarted_deviation
121
+ precalculated_mean = StatisticalSpread.new(values).mean
122
+ values.inject(0) { |sum, value| sum + (value - precalculated_mean) ** 4}
123
+ end
124
+
125
+ def quarted_standard_deviation
126
+ StatisticalSpread.new(values).standard_deviation ** 4
127
+ end
128
+
129
+ end
@@ -0,0 +1,3 @@
1
+ module FlashMath
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+
3
+ describe GeometricBoundingBox do
4
+
5
+ describe '#initialize_by_points' do
6
+ point1 = GeometricPoint.new(1, 2)
7
+ point2 = GeometricPoint.new(3, 4)
8
+ bounding_box = GeometricBoundingBox.new(point1, point2)
9
+
10
+ it 'to be equal' do
11
+ expect(bounding_box.leftbottom).to eq(point1)
12
+ expect(bounding_box.righttop).to eq(point2)
13
+ end
14
+
15
+ it 'to be not equal' do
16
+ expect(bounding_box.leftbottom).not_to eq(point2)
17
+ expect(bounding_box.righttop).not_to eq(point1)
18
+ end
19
+ end
20
+
21
+ describe '#initialize_by_arrays' do
22
+ point1 = GeometricPoint.new(1, 2)
23
+ point2 = GeometricPoint.new(3, 4)
24
+ bounding_box = GeometricBoundingBox.new_by_arrays([1, 2], [3, 4])
25
+
26
+ it 'to be equal' do
27
+ expect(bounding_box.leftbottom).to eq(point1)
28
+ expect(bounding_box.righttop).to eq(point2)
29
+ end
30
+
31
+ it 'to be not equal' do
32
+ expect(bounding_box.leftbottom).not_to eq(point2)
33
+ expect(bounding_box.righttop).not_to eq(point1)
34
+ end
35
+ end
36
+
37
+ describe '#contains' do
38
+ bounding_box = GeometricBoundingBox.new(GeometricPoint.new(-1, -1), GeometricPoint.new(1, 1))
39
+
40
+ it 'to be containing point' do
41
+ expect(bounding_box.contains?(GeometricPoint.new(0, 0))).to eq(true)
42
+ end
43
+
44
+ it 'to be not containing point' do
45
+ expect(bounding_box.contains?(GeometricPoint.new(2, 2))).to eq(false)
46
+ expect(bounding_box.contains?(GeometricPoint.new(-2, -2))).to eq(false)
47
+ expect(bounding_box.contains?(GeometricPoint.new(1, 2))).to eq(false)
48
+ expect(bounding_box.contains?(GeometricPoint.new(0, -1.1))).to eq(false)
49
+ end
50
+
51
+ it 'to be not containing point' do
52
+ expect(bounding_box.contains?(GeometricPoint.new(2, 2))).to eq(false)
53
+ expect(bounding_box.contains?(GeometricPoint.new(-2, -2))).to eq(false)
54
+ expect(bounding_box.contains?(GeometricPoint.new(1, 2))).to eq(false)
55
+ expect(bounding_box.contains?(GeometricPoint.new(0, -1.1))).to eq(false)
56
+ end
57
+
58
+ it 'to be containing point at edge' do
59
+ expect(bounding_box.contains?(GeometricPoint.new(1, 0))).to eq(true)
60
+ end
61
+
62
+ it 'to be containing point at vertex' do
63
+ expect(bounding_box.contains?(GeometricPoint.new(1, 1))).to eq(true)
64
+ end
65
+ end
66
+
67
+ end