flash_math 0.0.1

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.
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