observance 0.0.1 → 0.0.2

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: 7dfc295009d53d249c379697a871e38abd6f3ec1
4
- data.tar.gz: 1abc3656a6c554a43402e77f204e643d3bbe5369
3
+ metadata.gz: 553f83a148f0ff71cd4f7e0172afc68fc359d5ab
4
+ data.tar.gz: c56c3391312d58cf455d69c1121cf079aaa5ef8b
5
5
  SHA512:
6
- metadata.gz: d5cb644af3b4d4173d01d7ec6f0bf5fdc1368b54f10fc12707f9723f9e4247978f9a367cf5eff62c987d03d5d3daed30299fac4f57716f1e2550a7504a182136
7
- data.tar.gz: b76a12662b91601b5b0e41a2811b2ea50ce5c3e6d924af4d97a9279691c4a491deebddbca01c034c4451e88449de873749a06e262195ffbf3688a603ab6ff7a2
6
+ metadata.gz: 3083ccf959eec5e7bb8f1e4e84b25f2e18308c0066abd1e79eaf447ec0cb33b780f48b5b896e04e160353c98625c6cc914891acfb5758ccf4040ae72ee4f636c
7
+ data.tar.gz: 09894761a0ca773960edb616d7d03b19be6ccef4032c9c19cc7f9b09310682c17066fed76022024e784eac43ca08b507bb64d6a3c463439fffe2add4d8cf9050
data/README.md CHANGED
@@ -1,61 +1,124 @@
1
1
  # Observance
2
2
 
3
- Given multiple observations returns the most likely.
3
+ Given a collection of observations it returns the most likely. An observation is anything that responds to `to_a` Array or that responds to `to_h`.
4
4
 
5
- Imagine a person flashing at a distance a card
6
- displaying a number from 1 to 10. Let us say five people
7
- are observing this remote scene and note their observations during 4 rounds.
5
+ As an example imagine a person flashing at a distance a card
6
+ displaying a number from 1 to 10. If five people are observing
7
+ this remote scene and note their observations during 4 rounds we have
8
8
 
9
+ ```ruby
9
10
  observer_1 = {first: 8, second: 6, third: 1, fourth: 4}
10
11
  observer_2 = {first: 8, second: 6, third: 7, fourth: 4}
11
12
  observer_3 = {first: 8, second: 9, third: 7, fourth: 4}
12
13
  observer_4 = {first: 8, second: 9, third: 2, fourth: 8}
13
14
  observer_5 = {first: 0, second: 2, third: 0, fourth: 1}
15
+ ```
16
+
17
+ The probable outcome seems to be `8-(6 or 9)-7-4`. So the best observation
18
+ would either be `8-6-7-4` or `8-9-7-4`.
19
+
20
+ Using `Observance` we determine the most likely observation(s) by doing:
21
+
22
+ ```ruby
23
+ observations = Observance.run(observer_1, observer_2, observer_3,
24
+ observer_4, observer_5)
14
25
 
15
- The probable outcome seems to be 8-(6 or 9)-7-4. So the probable observation
16
- would either be 8-6-7-4 or 8-9-7-4.
26
+ observations[0].rating #=> 0.55
27
+ observations[0].object #=> observer_2
28
+
29
+ observations[1].rating #=> 0.55
30
+ observations[1].object #=> observer_3
31
+
32
+ observations[2].rating #=> 0.5
33
+ observations[3].rating #=> 0.4
34
+ observations[4].rating #=> 0.2
35
+ ```
36
+
37
+ As expected it gives us that `observer_2` and `observer_3` are equally the most likely to have happened.
17
38
 
18
39
  ## Installation
19
40
 
20
41
  Add `gem 'observance'` to your Gemfile or run `gem install observance`
21
42
 
43
+ ## Testing
44
+
45
+ This gem uses minitest. Just run `bundle exec rake` to test.
46
+
47
+ ## Features
48
+
49
+ * Handle observations of **different sizes** (although makes less sense)
50
+ * Observations can be **JSON documents** since `Observance` handles nested hashes
51
+ * Filter which set of keys to run `Observance` on
52
+
22
53
  ## Usage
23
54
 
24
- This gem runs on given sets of observations. An observation is anything
25
- that responds to `to_h` or `to_hash`.
55
+ ### Observance input
56
+
57
+ `Observance` needs as input collections of observations. An observation is anything
58
+ that responds to `to_a` or `to_h`.
59
+
60
+ For instance Hashes or Arrays:
26
61
 
27
62
  ```ruby
28
63
  o1 = {first_toss: 'head', second_toss: 'tail', third_toss: 'tail'}
29
64
  o2 = {first_toss: 'head', second_toss: 'tail', third_toss: 'tail'}
30
65
  o3 = {first_toss: 'tail', second_toss: 'tail', third_toss: 'tail'}
31
66
 
32
- Observance.observed(o1, o2, o3)
67
+ Observance.run(o1, o2, o3)
68
+
69
+ o1 = ['head', 'tail', 'tail']
70
+ o2 = ['head', 'tail', 'tail']
71
+ o3 = ['tail', 'tail', 'tail']
72
+
73
+ Observance.run(o1, o2, o3)
74
+
33
75
  ```
34
76
 
35
- Since a observation object is anything that responds to `to_h` or `to_hash`, observations
36
- can be a Hash, a parsed JSON, or a slightly modified Struct
77
+ Since a observation object is anything that responds to `to_a` or `to_h`, it
78
+ can also be a parsed JSON, or a slightly modified Struct as follows:
37
79
 
38
80
  ```ruby
39
81
  # Observation as a slightly modified Struct
40
- Scores = Struct.new(:period_1, :period_2, :period_3, period_4) do
82
+ Scores = Struct.new(:period_1, :period_2, :period_3, :period_4) do
41
83
  def to_h
42
84
  Hash[members.zip(to_a)]
43
85
  end
44
86
  end
45
87
 
46
88
  tom_observations = Scores.new(5, 6, 7, 4)
47
- jane_observations = Scores.new(5, 6, 7, 4)
89
+ jane_observations = Scores.new(5, 5, 7, 4)
90
+ bill_observations = Scores.new(5, 6, 6, 4)
48
91
 
49
- Observance.observed(tom_observations, jane_observations)
92
+ results = Observance.run(tom_observations, jane_observations, bill_observations)
93
+ results.first #=> Tom sample is the most likely with a rating of 0.8333
50
94
  ```
51
95
 
52
- The result returned is an ordered collection of Observance::Observed objects
96
+ ### Observance output
53
97
 
54
- ```ruby
55
- results = Observance.observed(o1, o2, o3, o4, o5)
56
- observed_1, observed_2, _ = results
98
+ Running Observance, it returns a sorted Array of `Observance::Observation` objects. Each `Observance::Observation` contains the original observation object (usually a hash) along with a **rating** (a float number between 0 and 1).
99
+
100
+ **The higher the rating the better the observation.**
57
101
 
58
- observed_1.factor = 0.7515
59
- observed_1.object == 01
102
+ The results **are sorted by rating** - with the highest rating (most likely) being the first.
60
103
 
104
+ ## Simple example
105
+
106
+ 5 people observe a coin being tossed 5 times. Each sends their observations as an Array
107
+
108
+ ```ruby
109
+ o1 = ['head', 'tail', 'tail', 'head', 'tail']
110
+ o2 = ['tail', 'tail', 'tail', 'head', 'tail']
111
+ o3 = ['head', 'head', 'tail', 'head', 'tail']
112
+ o4 = ['head', 'head', 'head', 'head', 'tail']
113
+ o5 = ['head', 'tail', 'tail', 'tail', 'head']
114
+
115
+ results = Observance.run(o1, o2, o3, o4, o5)
116
+ results.class # => Array
117
+
118
+ puts results
119
+ #<struct Observance::Observation rating=0.76, object=["head", "tail", "tail", "head", "tail"], index=0>
120
+ #<struct Observance::Observation rating=0.72, object=["head", "head", "tail", "head", "tail"], index=2>
121
+ #<struct Observance::Observation rating=0.64, object=["tail", "tail", "tail", "head", "tail"], index=1>
122
+ #<struct Observance::Observation rating=0.6, object=["head", "head", "head", "head", "tail"], index=3>
123
+ #<struct Observance::Observation rating=0.52, object=["head", "tail", "tail", "tail", "head"], index=4>
61
124
  ```
data/Rakefile CHANGED
@@ -2,6 +2,7 @@ require "bundler/gem_tasks"
2
2
  require "rake/testtask"
3
3
 
4
4
  Rake::TestTask.new(:test) do |t|
5
+ t.pattern = "test/**/*_test.rb"
5
6
  t.libs << "test"
6
7
  end
7
8
 
data/lib/observance.rb CHANGED
@@ -1,5 +1,68 @@
1
- require "observance/version"
1
+ require 'observance/version'
2
2
 
3
3
  module Observance
4
- # Your code goes here...
4
+
5
+ Observation = Struct.new(:rating, :object, :index) do
6
+ include Comparable
7
+
8
+ def <=>(other)
9
+ return nil unless other.is_a? self.class
10
+ if other.rating == self.rating
11
+ self.index <=> other.index
12
+ else
13
+ other.rating <=> self.rating
14
+ end
15
+ end
16
+ end
17
+
18
+ def self.run(*observations)
19
+ obs = if observations.first.is_a? Array
20
+ wrapped_in_hashes(observations)
21
+ elsif observations.first.respond_to? :to_h
22
+ observations.map(&:to_h)
23
+ else
24
+ raise "Observations need to be Array or respond to 'to_h'"
25
+ end
26
+ obs.each_with_index.map do |c, index|
27
+ rating = (obs.inject(0) do |acc, o|
28
+ acc = acc + c.similarity_to(o)
29
+ end).fdiv(obs.size)
30
+ Observation.new(rating.round(4), observations[index], index)
31
+ end.sort
32
+ end
33
+
34
+ private
35
+
36
+ def self.wrapped_in_hashes(observations)
37
+ observations.map do |o|
38
+ Hash[(0...o.size).zip(o)]
39
+ end
40
+ end
5
41
  end
42
+
43
+ class Hash
44
+ # Returns a rating f between 0 and 1 that indicates
45
+ # the similarity rating of self to other
46
+ #
47
+ # Calculates the number of keys with the same values
48
+ # then divide the result by the number of keys
49
+ def similarity_to(other)
50
+ smaller_one, bigger_one = [self, other].sort_by(&:size)
51
+ diff_size = bigger_one.size - smaller_one.size
52
+
53
+ ratio = bigger_one.size + diff_size
54
+
55
+ sum = 0
56
+ bigger_one.each_pair do |k, v|
57
+ (sum = sum + 1) if v == smaller_one[k]
58
+ end
59
+ r1 = sum.fdiv(bigger_one.size)
60
+
61
+ sum2 = 0
62
+ smaller_one.each_pair do |k, v|
63
+ (sum2= sum2 + 1) if v == bigger_one[k]
64
+ end
65
+ r2 = (sum2).fdiv(ratio)
66
+ (r1 + r2).fdiv(2)
67
+ end
68
+ end
@@ -1,3 +1,3 @@
1
1
  module Observance
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
data/observance.gemspec CHANGED
@@ -21,4 +21,5 @@ Gem::Specification.new do |spec|
21
21
  spec.add_development_dependency "bundler", "~> 1.7"
22
22
  spec.add_development_dependency "rake", "~> 10.0"
23
23
  spec.add_development_dependency "minitest"
24
+ spec.add_development_dependency "pry"
24
25
  end
@@ -0,0 +1,56 @@
1
+ require 'minitest_helper'
2
+
3
+ class TestHashExtension < MiniTest::Test
4
+
5
+ def test_similarity_in_same_size_hashes
6
+ h = {one: 1, two: 2, three: 3, four: 4, five: 5,
7
+ six: 6, seven: 7, eight: 8, nine: 9, ten: 10}
8
+
9
+ assert_equal 1, h.similarity_to(h)
10
+ assert_equal 0.9, h.similarity_to(h.merge(one: 0))
11
+ assert_equal 0.8, h.similarity_to(h.merge(one: 0, two: 0))
12
+ assert_equal 0.7, h.similarity_to(h.merge(one: 0, two: 0, three: 0))
13
+ assert_equal 0.6, h.similarity_to(h.merge(one: 0, two: 0, three: 0, four: 0))
14
+ assert_equal 0.5, h.similarity_to(h.merge(one: 0, two: 0, three: 0, four: 0, five: 0))
15
+ assert_equal 0.4, h.similarity_to(h.merge(one: 0, two: 0, three: 0, four: 0, five: 0,
16
+ six: 0))
17
+ assert_equal 0.3, h.similarity_to(h.merge(one: 0, two: 0, three: 0, four: 0, five: 0,
18
+ six: 0, seven: 0))
19
+ assert_equal 0.2, h.similarity_to(h.merge(one: 0, two: 0, three: 0, four: 0, five: 0,
20
+ six: 0, seven: 0, eight: 0))
21
+ assert_equal 0.1, h.similarity_to(h.merge(one: 0, two: 0, three: 0, four: 0, five: 0,
22
+ six: 0, seven: 0, eight: 0, nine: 0))
23
+ assert_equal 0.0, h.similarity_to(h.merge(one: 0, two: 0, three: 0, four: 0, five: 0,
24
+ six: 0, seven: 0, eight: 0, nine: 0, ten: 0))
25
+ end
26
+
27
+ def test_similarity_in_different_size_hashes
28
+ h = {one: 1, two: 2, three: 3, four: 4}
29
+
30
+ assert_in_delta 0.733, h.similarity_to({one: 1, two: 2, three: 3, four: 4, five: 5})
31
+ assert_equal 0.675, h.similarity_to({one: 1, two: 2, three: 3})
32
+ assert_in_delta 0.416, h.similarity_to({one: 1, two: 2})
33
+ assert_in_delta 0.208, h.similarity_to({one: 0, two: 2})
34
+ assert_in_delta 0.183, h.similarity_to({one: 0, two: 0, three: 0, four: 4, five: 5})
35
+ assert_equal 0.0, h.similarity_to({one: 0, two: 0, three: 0, four: 0, five: 5})
36
+ end
37
+
38
+ def test_same_size_has_better_similarity_than_different_size
39
+ h = {one: 1, two: 2, three: 3, four: 4}
40
+
41
+ assert h.similarity_to({one: 1, two: 2, three: 3}) <
42
+ h.similarity_to(one: 1, two: 2, three: 3, four: 0)
43
+ end
44
+
45
+ def test_symmetry_of_similarity_operation
46
+ h = {one: 1, two: 2, three: 3, four: 4}
47
+ o = {one: 2, two: 1, three: 3, four: 5}
48
+
49
+ assert_equal h.similarity_to(o), o.similarity_to(h)
50
+
51
+ h = {one: 1, two: 2, three: 3, four: 4}
52
+ o = {one: 1, two: 2, three: 3, four: 4, five: 5}
53
+
54
+ assert_equal h.similarity_to(o), o.similarity_to(h)
55
+ end
56
+ end
@@ -0,0 +1,85 @@
1
+ require 'minitest_helper'
2
+
3
+ class TestObservance < MiniTest::Test
4
+ def test_returns_ordered_observation_from_same_size_hashes
5
+ o1 = {one: 1, two: 2, three: 3, four: 4}
6
+ o2 = {one: 1, two: 2, three: 3, four: 4}
7
+ o3 = {one: 1, two: 2, three: 3, four: 0}
8
+ o4 = {one: 1, two: 2, three: 0, four: 4}
9
+ o5 = {one: 1, two: 2, three: 0, four: 0}
10
+ o6 = {one: 1, two: 0, three: 0, four: 0}
11
+ o7 = {one: 0, two: 0, three: 0, four: 0}
12
+
13
+ observations = Observance.run(o1, o2, o3, o4, o5, o6, o7)
14
+
15
+ assert_equal o5, observations[0].object
16
+ assert_equal o3, observations[1].object
17
+ assert_equal o4, observations[2].object
18
+ assert_equal o1, observations[3].object
19
+ assert_equal o2, observations[4].object
20
+ assert_equal o6, observations[5].object
21
+ assert_equal o7, observations[6].object
22
+
23
+ assert_equal 0.6786, observations[0].rating
24
+ assert_equal 0.6429, observations[1].rating
25
+ assert_equal 0.6429, observations[2].rating
26
+ assert_equal 0.6071, observations[3].rating
27
+ assert_equal 0.6071, observations[4].rating
28
+ assert_equal 0.5714, observations[5].rating
29
+ assert_equal 0.3929, observations[6].rating
30
+ end
31
+
32
+ def test_returns_ordered_observation_from_different_size_hashes
33
+ o1 = {one: 1, two: 2, three: 3, four: 4, five: 5}
34
+ o2 = {one: 1, two: 2, three: 3, four: 4}
35
+ o3 = {zero: 0, one: 1, two: 2, three: 3, four: 0}
36
+ o4 = {one: 1, two: 2, three: 0, four: 4}
37
+ o5 = {one: 1, two: 2, three: 0, four: 0, ten: 10, eleven: 11}
38
+ o6 = {one: 1, two: 0}
39
+ o7 = {one: 0, two: 0, three: 0}
40
+
41
+ observations = Observance.run(o1, o2, o3, o4, o5, o6, o7)
42
+
43
+ assert_equal o4, observations[0].object
44
+ assert_equal o2, observations[1].object
45
+ assert_equal o1, observations[2].object
46
+ assert_equal o3, observations[3].object
47
+ assert_equal o5, observations[4].object
48
+ assert_equal o6, observations[5].object
49
+ assert_equal o7, observations[6].object
50
+
51
+ assert_equal 0.5054, observations[0].rating
52
+ assert_equal 0.5048, observations[1].rating
53
+ assert_equal 0.4793, observations[2].rating
54
+ assert_equal 0.4491, observations[3].rating
55
+ assert_equal 0.3965, observations[4].rating
56
+ assert_equal 0.3095, observations[5].rating
57
+ end
58
+
59
+ def test_returns_ordered_observation_from_same_size_arrays
60
+ o1 = [1, 2, 3, 4]
61
+ o2 = [1, 2, 3, 4]
62
+ o3 = [1, 2, 3, 0]
63
+ o4 = [1, 2, 0, 4]
64
+ o5 = [1, 2, 0, 0]
65
+ o6 = [1, 0, 0, 0]
66
+ o7 = [0, 0, 0, 0]
67
+
68
+ observations = Observance.run(o1, o2, o3, o4, o5, o6, o7)
69
+
70
+ assert_equal o5, observations[0].object
71
+ assert_equal o3, observations[1].object
72
+ assert_equal o4, observations[2].object
73
+ assert_equal o1, observations[3].object
74
+ assert_equal o2, observations[4].object
75
+ assert_equal o6, observations[5].object
76
+ assert_equal o7, observations[6].object
77
+
78
+ assert_equal 0.6786, observations[0].rating
79
+ assert_equal 0.6429, observations[1].rating
80
+ assert_equal 0.6429, observations[2].rating
81
+ assert_equal 0.6071, observations[3].rating
82
+ assert_equal 0.6071, observations[4].rating
83
+ assert_equal 0.5714, observations[5].rating
84
+ end
85
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: observance
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - simcap
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
55
69
  description: Returns the most likely observation given multiple observations
56
70
  email:
57
71
  - simcap@fastmail.com
@@ -60,7 +74,6 @@ extensions: []
60
74
  extra_rdoc_files: []
61
75
  files:
62
76
  - ".gitignore"
63
- - ".travis.yml"
64
77
  - Gemfile
65
78
  - LICENSE.txt
66
79
  - README.md
@@ -68,8 +81,9 @@ files:
68
81
  - lib/observance.rb
69
82
  - lib/observance/version.rb
70
83
  - observance.gemspec
84
+ - test/hash_extensions_test.rb
71
85
  - test/minitest_helper.rb
72
- - test/test_observance.rb
86
+ - test/observance_test.rb
73
87
  homepage: ''
74
88
  licenses:
75
89
  - MIT
@@ -95,5 +109,6 @@ signing_key:
95
109
  specification_version: 4
96
110
  summary: Returns the most likely observation given multiple observations
97
111
  test_files:
112
+ - test/hash_extensions_test.rb
98
113
  - test/minitest_helper.rb
99
- - test/test_observance.rb
114
+ - test/observance_test.rb
data/.travis.yml DELETED
@@ -1,3 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.1.5
@@ -1,11 +0,0 @@
1
- require 'minitest_helper'
2
-
3
- class TestObservance < MiniTest::Unit::TestCase
4
- def test_that_it_has_a_version_number
5
- refute_nil ::Observance::VERSION
6
- end
7
-
8
- def test_it_does_something_useful
9
- assert false
10
- end
11
- end