speculation 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 +4 -4
- data/.rubocop.yml +13 -0
- data/.travis.yml +2 -4
- data/README.md +22 -12
- data/Rakefile +5 -9
- data/bin/console +54 -7
- data/examples/codebreaker.rb +246 -0
- data/examples/spec_guide.rb +1288 -0
- data/lib/speculation.rb +145 -146
- data/lib/speculation/error.rb +4 -3
- data/lib/speculation/gen.rb +51 -47
- data/lib/speculation/identifier.rb +7 -7
- data/lib/speculation/namespaced_symbols.rb +26 -19
- data/lib/speculation/pmap.rb +9 -10
- data/lib/speculation/spec_impl/and_spec.rb +3 -4
- data/lib/speculation/spec_impl/every_spec.rb +24 -24
- data/lib/speculation/spec_impl/f_spec.rb +32 -35
- data/lib/speculation/spec_impl/hash_spec.rb +33 -41
- data/lib/speculation/spec_impl/merge_spec.rb +2 -3
- data/lib/speculation/spec_impl/nilable_spec.rb +8 -9
- data/lib/speculation/spec_impl/or_spec.rb +5 -7
- data/lib/speculation/spec_impl/regex_spec.rb +2 -3
- data/lib/speculation/spec_impl/spec.rb +3 -5
- data/lib/speculation/spec_impl/tuple_spec.rb +8 -10
- data/lib/speculation/test.rb +126 -101
- data/lib/speculation/utils.rb +31 -5
- data/lib/speculation/version.rb +1 -1
- data/speculation.gemspec +0 -1
- metadata +30 -44
- data/lib/speculation/conj.rb +0 -32
- data/lib/speculation/utils_specs.rb +0 -57
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 19dd9abfde4552ea62d19dda2714f9dd0e6f33a8
|
4
|
+
data.tar.gz: 112bed767778c508470b5d3303937580158bd649
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1e0d5b36cb7c3e868004e4e62b408bc1a80c144759791c2ae138fefcef90a345106fde0780c7ac8b1fee36c0049c3e8f922af8a72e9ceeed9c133337d494514f
|
7
|
+
data.tar.gz: fb3eb18c1a8a4e1be72a0223f30c11dd05170a1b9225bfd73bebb6a2f27bda4255d51ae952597bf5bf3dd8301ce6990cac1fbe481d3897caf9f54e5b531cc7ef
|
data/.rubocop.yml
CHANGED
@@ -3,6 +3,7 @@ AllCops:
|
|
3
3
|
- 'bin/*'
|
4
4
|
- '*.*'
|
5
5
|
- 'vendor/**/*'
|
6
|
+
- 'examples/**/*'
|
6
7
|
TargetRubyVersion: 2.4
|
7
8
|
|
8
9
|
Metrics/ClassLength:
|
@@ -85,3 +86,15 @@ Style/NumericLiterals:
|
|
85
86
|
|
86
87
|
Style/NumericPredicate:
|
87
88
|
Enabled: false
|
89
|
+
|
90
|
+
Style/SpecialGlobalVars:
|
91
|
+
Enabled: false
|
92
|
+
|
93
|
+
Style/RescueModifier:
|
94
|
+
Enabled: false
|
95
|
+
|
96
|
+
Performance/RedundantBlockCall:
|
97
|
+
Enabled: false
|
98
|
+
|
99
|
+
Style/MethodName:
|
100
|
+
Enabled: false
|
data/.travis.yml
CHANGED
@@ -2,6 +2,8 @@ sudo: false
|
|
2
2
|
language: ruby
|
3
3
|
cache: bundler
|
4
4
|
rvm:
|
5
|
+
- 2.0.0
|
6
|
+
- 2.1.10
|
5
7
|
- 2.2.6
|
6
8
|
- 2.3.3
|
7
9
|
- 2.4.0
|
@@ -10,7 +12,3 @@ rvm:
|
|
10
12
|
before_install:
|
11
13
|
- gem update --system
|
12
14
|
- gem install bundler
|
13
|
-
matrix:
|
14
|
-
allow_failures:
|
15
|
-
- rvm: 2.2.6
|
16
|
-
- rvm: jruby-9.0.5.0
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
# Speculation
|
1
|
+
# Speculation [![Build Status](https://travis-ci.org/english/speculation.svg?branch=master)](https://travis-ci.org/english/speculation)
|
2
2
|
|
3
|
-
A Ruby port of Clojure's `clojure.spec`. See [clojure.spec - Rationale and Overview](https://clojure.org/about/spec).
|
3
|
+
A Ruby port of Clojure's `clojure.spec`. See [clojure.spec - Rationale and Overview](https://clojure.org/about/spec). All advantages/disadvantages for clojure.spec should apply to Speculation too. This library is largely a copy-and-paste from clojure.spec so all credit goes to the clojure.spec authors.
|
4
4
|
|
5
5
|
## Installation
|
6
6
|
|
@@ -18,9 +18,18 @@ Or install it yourself as:
|
|
18
18
|
|
19
19
|
$ gem install speculation
|
20
20
|
|
21
|
+
## Project Goals
|
22
|
+
|
23
|
+
The goal of this project is to match clojure.spec as closely as possible, from design to features to API. This decision comes with the trade-off that the library may not necessarily be idiomatic Ruby, however there's nothing stopping other libraries from being built atop Speculation to bring a more Ruby-like feel. This library won't introduce features that do not exist in clojure.spec.
|
24
|
+
|
25
|
+
## Examples
|
26
|
+
|
27
|
+
- [spec_guide.rb](examples/spec_guide.rb): Speculation port of Clojure's [spec guide](https://clojure.org/guides/spec)
|
28
|
+
- [codebreaker.rb](examples/codebreaker.rb): Speculation port of the 'codebreaker' game described in [Interactive development with clojure.spec](http://blog.cognitect.com/blog/2016/10/5/interactive-development-with-clojurespec)
|
29
|
+
|
21
30
|
## Usage
|
22
31
|
|
23
|
-
The API is more-or-less the same as `clojure.spec`. If you're already familiar with
|
32
|
+
The API is more-or-less the same as `clojure.spec`. If you're already familiar with then you should be write at home with Speculation. Clojure and Ruby and quite different languages, so naturally there are some differences:
|
24
33
|
|
25
34
|
### Built in predicates
|
26
35
|
|
@@ -35,25 +44,25 @@ S.valid?(/^\d+$/, "123")
|
|
35
44
|
S.valid?(Set[:foo, :bar, :baz], :foo)
|
36
45
|
```
|
37
46
|
|
38
|
-
### Namespaced keywords
|
47
|
+
### Namespaced keywords/symbols
|
39
48
|
|
40
49
|
Namespaced keywords are at the core of `clojure.spec`. Since spec utilises a global spec registry, namespaced keywords allow libraries to register specs with the same names but under different namespaces, thus removing accidental collisions. Ruby's equivalent to Clojure's keywords are Symbols. Ruby Symbol's don't have namespaces.
|
41
50
|
|
42
|
-
In order keep the global spec registry architecture in Speculation, we utilise
|
51
|
+
In order keep the global spec registry architecture in Speculation, we utilise a helper method `ns` achieve similar behaviour:
|
43
52
|
|
44
53
|
```rb
|
45
|
-
|
54
|
+
extend Speculation::NamespacedSymbols
|
46
55
|
|
47
|
-
p :foo
|
56
|
+
p ns(:foo)
|
48
57
|
# => :"MyModule/foo"
|
49
58
|
|
50
|
-
p
|
59
|
+
p ns(AnotherModule, :foo)
|
51
60
|
# => :"AnotherModule/foo"
|
52
61
|
```
|
53
62
|
|
54
63
|
### FSpecs
|
55
64
|
|
56
|
-
#### Symbols
|
65
|
+
#### Symbols/Methods
|
57
66
|
|
58
67
|
Clojure uses Symbols to refer to functions. To refer to a method in Ruby, we must use the `method` method.
|
59
68
|
|
@@ -96,8 +105,6 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/englis
|
|
96
105
|
## TODO
|
97
106
|
|
98
107
|
- tidy up tests
|
99
|
-
- write up comparison with clojure.spec
|
100
|
-
- write guide
|
101
108
|
|
102
109
|
### clojure.spec features
|
103
110
|
|
@@ -108,7 +115,10 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/englis
|
|
108
115
|
|
109
116
|
### Improvements
|
110
117
|
|
111
|
-
-
|
118
|
+
- Explore alternative generator library
|
119
|
+
- Build up a library of generators around Rantly in the meantime?
|
120
|
+
- Generate documentation from specs
|
121
|
+
- perhaps integrating with [Pry's documentation browsing](https://github.com/pry/pry/wiki/Documentation-browsing)?
|
112
122
|
- Profile and optimise
|
113
123
|
|
114
124
|
## License
|
data/Rakefile
CHANGED
@@ -9,17 +9,13 @@ task :rubocop do
|
|
9
9
|
end
|
10
10
|
|
11
11
|
task :test do
|
12
|
-
|
13
|
-
$LOAD_PATH.unshift(File.expand_path("../test", __FILE__))
|
12
|
+
$LOAD_PATH.unshift(File.expand_path("../test", __FILE__))
|
14
13
|
|
15
|
-
|
16
|
-
|
17
|
-
end
|
18
|
-
|
19
|
-
raise unless Minitest.run
|
20
|
-
else
|
21
|
-
sh "TEST_QUEUE_SPLIT_GROUPS=1 bundle exec ruby -r minitest/autorun -I test -S minitest-queue $(find test -name *_test.rb)"
|
14
|
+
FileList["test/**/*.rb"].each do |test_file|
|
15
|
+
require "./#{test_file}"
|
22
16
|
end
|
17
|
+
|
18
|
+
raise unless Minitest.run
|
23
19
|
end
|
24
20
|
|
25
21
|
task :doc do
|
data/bin/console
CHANGED
@@ -3,11 +3,11 @@
|
|
3
3
|
$LOAD_PATH.unshift File.expand_path("../../test", __FILE__)
|
4
4
|
|
5
5
|
require "bundler/setup"
|
6
|
+
require "pry"
|
6
7
|
require 'rubocop'
|
7
8
|
require "speculation"
|
8
9
|
require "speculation/test"
|
9
10
|
require "speculation/gen"
|
10
|
-
require "speculation/utils_specs"
|
11
11
|
|
12
12
|
def reload!
|
13
13
|
Minitest::Test.reset
|
@@ -18,7 +18,6 @@ def reload!
|
|
18
18
|
end
|
19
19
|
|
20
20
|
load "./lib/speculation/namespaced_symbols.rb"
|
21
|
-
load "./lib/speculation/conj.rb"
|
22
21
|
load "./lib/speculation/pmap.rb"
|
23
22
|
load "./lib/speculation/identifier.rb"
|
24
23
|
load "./lib/speculation/utils.rb"
|
@@ -33,7 +32,6 @@ def reload!
|
|
33
32
|
load "./lib/speculation.rb"
|
34
33
|
load "./lib/speculation/gen.rb"
|
35
34
|
load "./lib/speculation/test.rb"
|
36
|
-
load "./lib/speculation/utils_specs.rb"
|
37
35
|
|
38
36
|
Dir["test/**/*.rb"].each do |f|
|
39
37
|
load f
|
@@ -44,7 +42,7 @@ def reload!
|
|
44
42
|
Object.const_set(:Gen, Speculation::Gen)
|
45
43
|
Object.const_set(:U, Speculation::Utils)
|
46
44
|
|
47
|
-
|
45
|
+
STest.instrument
|
48
46
|
end
|
49
47
|
|
50
48
|
def t
|
@@ -61,10 +59,59 @@ require "minitest"
|
|
61
59
|
include Minitest::Assertions
|
62
60
|
def self.assertions; 1; end
|
63
61
|
def self.assertions=(_x); end
|
64
|
-
|
62
|
+
|
63
|
+
extend Speculation::NamespacedSymbols
|
64
|
+
|
65
|
+
def self.test_check_utils
|
66
|
+
S.fdef(U.method(:hash?),
|
67
|
+
:args => S.tuple(ns(S, :any)),
|
68
|
+
:ret => ns(S, :boolean))
|
69
|
+
|
70
|
+
S.fdef(U.method(:array?),
|
71
|
+
:args => S.tuple(ns(S, :any)),
|
72
|
+
:ret => ns(S, :boolean))
|
73
|
+
|
74
|
+
S.fdef(U.method(:collection?),
|
75
|
+
:args => S.tuple(ns(S, :any)),
|
76
|
+
:ret => ns(S, :boolean))
|
77
|
+
|
78
|
+
S.fdef(U.method(:itself),
|
79
|
+
:args => S.cat(:x => ns(S, :any)),
|
80
|
+
:ret => ns(S, :any),
|
81
|
+
:fn => ->(x) { x[:args][:x].equal?(x[:ret]) })
|
82
|
+
|
83
|
+
# S.fdef(U.method(:complement),
|
84
|
+
# :args => ns(S, :empty),
|
85
|
+
# :block => S.fspec(:args => S.zero_or_more(ns(S, :any)),
|
86
|
+
# :ret => ns(S, :any)),
|
87
|
+
# :ret => S.fspec(:args => S.zero_or_more(ns(S, :any)),
|
88
|
+
# :ret => ns(S, :boolean)))
|
89
|
+
|
90
|
+
S.fdef(U.method(:constantly),
|
91
|
+
:args => S.cat(:x => ns(S, :any)),
|
92
|
+
:ret => Proc,
|
93
|
+
:fn => ->(x) { x[:args][:x].equal?(x[:ret].call) })
|
94
|
+
|
95
|
+
S.fdef(U.method(:distinct?),
|
96
|
+
:args => S.cat(:coll => Enumerable),
|
97
|
+
:ret => ns(S, :boolean))
|
98
|
+
|
99
|
+
S.fdef(U.method(:ident?),
|
100
|
+
:args => S.cat(:x => ns(S, :any)),
|
101
|
+
:ret => ns(S, :boolean))
|
102
|
+
|
103
|
+
S.fdef(U.method(:method?),
|
104
|
+
:args => S.cat(:x => ns(S, :any)),
|
105
|
+
:ret => ns(S, :boolean))
|
106
|
+
|
107
|
+
S.fdef(U.method(:empty),
|
108
|
+
:args => S.cat(:coll => Enumerable),
|
109
|
+
:ret => S.and(Enumerable, ->(coll) { coll.empty? }),
|
110
|
+
:fn => ->(x) { x[:args][:coll].class == x[:ret].class })
|
111
|
+
|
112
|
+
STest.summarize_results(STest.check(STest.enumerate_methods(Speculation::Utils), :num_tests => 50))
|
113
|
+
end
|
65
114
|
|
66
115
|
reload!
|
67
116
|
|
68
|
-
# (If you use this, don't forget to add pry to your Gemfile!)
|
69
|
-
require "pry"
|
70
117
|
Pry.start
|
@@ -0,0 +1,246 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# See http://blog.cognitect.com/blog/2016/10/5/interactive-development-with-clojurespec
|
3
|
+
|
4
|
+
require "bundler/inline"
|
5
|
+
require "set"
|
6
|
+
|
7
|
+
gemfile true do
|
8
|
+
gem "speculation",
|
9
|
+
:git => "https://github.com/english/speculation.git",
|
10
|
+
:require => ["speculation", "speculation/test", "speculation/gen"]
|
11
|
+
end
|
12
|
+
|
13
|
+
S = Speculation
|
14
|
+
Gen = S::Gen
|
15
|
+
STest = S::Test
|
16
|
+
|
17
|
+
extend Speculation::NamespacedSymbols
|
18
|
+
|
19
|
+
peg = Set[:y, :g, :r, :c, :w, :b]
|
20
|
+
|
21
|
+
S.def ns(:code), S.coll_of(peg, :min_count => 4, :max_count => 6)
|
22
|
+
|
23
|
+
def score; end
|
24
|
+
|
25
|
+
S.fdef method(:score),
|
26
|
+
:args => S.cat(:secret => ns(:code), :guess => ns(:code))
|
27
|
+
|
28
|
+
S.exercise S.get_spec(method(:score)).args
|
29
|
+
|
30
|
+
# [[[[:c, :g, :y, :w, :g, :y], [:y, :y, :c, :r, :y, :y]], {:secret=>[:c, :g, :y, :w, :g, :y], :guess=>[:y, :y, :c, :r, :y, :y]}],
|
31
|
+
# [[[:b, :b, :y, :y], [:b, :r, :y, :y, :y]], {:secret=>[:b, :b, :y, :y], :guess=>[:b, :r, :y, :y, :y]}],
|
32
|
+
# [[[:y, :y, :w, :g, :r, :w], [:r, :w, :c, :b, :r, :y]], {:secret=>[:y, :y, :w, :g, :r, :w], :guess=>[:r, :w, :c, :b, :r, :y]}],
|
33
|
+
# ...
|
34
|
+
|
35
|
+
S.fdef method(:score),
|
36
|
+
:args => S.and(S.cat(:secret => ns(:code), :guess => ns(:code)),
|
37
|
+
->(args) { args[:secret].count == args[:guess].count })
|
38
|
+
|
39
|
+
S.exercise S.get_spec(method(:score)).args
|
40
|
+
|
41
|
+
# [[[[:b, :w, :b, :c, :b], [:y, :r, :y, :y, :b]], {:secret=>[:b, :w, :b, :c, :b], :guess=>[:y, :r, :y, :y, :b]}],
|
42
|
+
# [[[:c, :y, :w, :c], [:c, :r, :c, :c]], {:secret=>[:c, :y, :w, :c], :guess=>[:c, :r, :c, :c]}],
|
43
|
+
# [[[:g, :g, :y, :g], [:b, :y, :w, :b]], {:secret=>[:g, :g, :y, :g], :guess=>[:b, :y, :w, :b]}],
|
44
|
+
# ...
|
45
|
+
|
46
|
+
S.def ns(:exact_matches), ns(S, :natural_integer)
|
47
|
+
S.def ns(:loose_matches), ns(S, :natural_integer)
|
48
|
+
|
49
|
+
S.fdef method(:score),
|
50
|
+
:args => S.and(S.cat(:secret => ns(:code), :guess => ns(:code)),
|
51
|
+
->(args) { args[:secret].count == args[:guess].count }),
|
52
|
+
:ret => S.keys(:req => [ns(:exact_matches), ns(:loose_matches)])
|
53
|
+
|
54
|
+
S.exercise S.get_spec(method(:score)).ret
|
55
|
+
# [[{:"main/exact_matches"=>301501626008109845, :"main/loose_matches"=>1592567845535536138},
|
56
|
+
# {:"main/exact_matches"=>301501626008109845, :"main/loose_matches"=>1592567845535536138}],
|
57
|
+
# [{:"main/exact_matches"=>705260057025726755, :"main/loose_matches"=>4979282811122408}, {:"main/exact_matches"=>705260057025726755, :"main/loose_matches"=>4979282811122408}],
|
58
|
+
# [{:"main/exact_matches"=>905565787875512744, :"main/loose_matches"=>965211463791348650},
|
59
|
+
# ...
|
60
|
+
|
61
|
+
S.fdef method(:score),
|
62
|
+
:args => S.and(S.cat(:secret => ns(:code), :guess => ns(:code)),
|
63
|
+
->(args) { args[:secret].count == args[:guess].count }),
|
64
|
+
:ret => S.keys(:req => [ns(:exact_matches), ns(:loose_matches)]),
|
65
|
+
:fn => ->(fn) {
|
66
|
+
sum_matches = fn[:ret].values.reduce(&:+)
|
67
|
+
sum_matches.between?(0, fn[:args][:secret].count)
|
68
|
+
}
|
69
|
+
|
70
|
+
def self.score(secret, guess)
|
71
|
+
{ ns(:exact_matches) => 0,
|
72
|
+
ns(:loose_matches) => 0 }
|
73
|
+
end
|
74
|
+
|
75
|
+
STest.check method(:score)
|
76
|
+
# [{:spec=>Speculation::FSpec(main.score), :"Speculation::Test/ret"=>{:num_tests=>1000, :result=>true}, :method=>#<Method: main.score>}]
|
77
|
+
|
78
|
+
def self.score(secret, guess)
|
79
|
+
{ ns(:exact_matches) => 4,
|
80
|
+
ns(:loose_matches) => 3 }
|
81
|
+
end
|
82
|
+
|
83
|
+
# STest.check method(:score)
|
84
|
+
|
85
|
+
# [{:spec=>Speculation::FSpec(main.score),
|
86
|
+
# :"Speculation::Test/ret"=>
|
87
|
+
# {:fail=>[[:y, :b, :r, :r, :b], [:y, :w, :g, :r, :g]],
|
88
|
+
# :block=>nil,
|
89
|
+
# :num_tests=>1,
|
90
|
+
# :result=>
|
91
|
+
# #<Speculation::Error: {:"Speculation/problems"=>
|
92
|
+
# [{:path=>[:fn],
|
93
|
+
# :val=>
|
94
|
+
# {:args=>{:secret=>[:y, :b, :r, :r, :b], :guess=>[:y, :w, :g, :r, :g]},
|
95
|
+
# :block=>nil,
|
96
|
+
# :ret=>{:"main/exact_matches"=>4, :"main/loose_matches"=>3}},
|
97
|
+
# :via=>[],
|
98
|
+
|
99
|
+
def self.score(secret, guess)
|
100
|
+
{ ns(:exact_matches) => secret.zip(guess).count { |(a, b)| a.equal?(b) },
|
101
|
+
ns(:loose_matches) => 0 }
|
102
|
+
end
|
103
|
+
|
104
|
+
S.exercise_fn method(:score)
|
105
|
+
|
106
|
+
# [[[[:c, :w, :r, :c, :c, :b], [:c, :y, :y, :r, :c, :b]], {:"main/exact_matches"=>3, :"main/loose_matches"=>0}],
|
107
|
+
# [[[:r, :g, :w, :c, :y], [:y, :w, :r, :w, :r]], {:"main/exact_matches"=>0, :"main/loose_matches"=>0}],
|
108
|
+
# [[[:b, :b, :r, :b, :r], [:b, :g, :c, :c, :g]], {:"main/exact_matches"=>1, :"main/loose_matches"=>0}],
|
109
|
+
# [[[:g, :r, :b, :y], [:g, :c, :r, :w]], {:"main/exact_matches"=>1, :"main/loose_matches"=>0}],
|
110
|
+
|
111
|
+
STest.check method(:score)
|
112
|
+
|
113
|
+
# [{:spec=>Speculation::FSpec(main.score), :"Speculation::Test/ret"=>{:num_tests=>1000, :result=>true}, :method=>#<Method: main.score>}]
|
114
|
+
|
115
|
+
def self.score(secret, guess)
|
116
|
+
{ ns(:exact_matches) => exact_matches(secret, guess),
|
117
|
+
ns(:loose_matches) => 0 }
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.exact_matches(secret, guess)
|
121
|
+
secret.zip(guess).count { |(a, b)| a.equal?(b) }
|
122
|
+
end
|
123
|
+
|
124
|
+
S.def ns(:secret_and_guess), S.and(S.cat(:secret => ns(:code), :guess => ns(:code)),
|
125
|
+
->(args) { args[:secret].count == args[:guess].count })
|
126
|
+
|
127
|
+
S.fdef method(:score),
|
128
|
+
:args => ns(:secret_and_guess),
|
129
|
+
:ret => S.keys(:req => [ns(:exact_matches), ns(:loose_matches)]),
|
130
|
+
:fn => ->(fn) {
|
131
|
+
sum_matches = fn[:ret].values.reduce(&:+)
|
132
|
+
sum_matches.between?(0, fn[:args][:secret].count)
|
133
|
+
}
|
134
|
+
|
135
|
+
S.fdef method(:exact_matches),
|
136
|
+
:args => ns(:secret_and_guess),
|
137
|
+
:ret => ns(S, :natural_integer),
|
138
|
+
:fn => ->(fn) { fn[:ret].between?(0, fn[:args][:secret].count) }
|
139
|
+
|
140
|
+
S.exercise_fn method(:exact_matches)
|
141
|
+
|
142
|
+
# [[[[:g, :b, :b, :b, :c], [:w, :y, :c, :y, :b]], 0],
|
143
|
+
# [[[:c, :w, :r, :c], [:y, :r, :y, :g]], 0],
|
144
|
+
# [[[:g, :c, :g, :g, :y], [:g, :g, :c, :c, :r]], 1],
|
145
|
+
# [[[:g, :r, :y, :g, :g], [:g, :y, :y, :r, :w]], 2],
|
146
|
+
|
147
|
+
STest.check method(:exact_matches)
|
148
|
+
# [{:spec=>Speculation::FSpec(main.exact_matches), :"Speculation::Test/ret"=>{:num_tests=>1000, :result=>true}, :method=>#<Method: main.exact_matches>}]
|
149
|
+
|
150
|
+
STest.instrument method(:exact_matches)
|
151
|
+
S.exercise_fn method(:score)
|
152
|
+
|
153
|
+
# [[[[:y, :r, :y, :r], [:g, :w, :b, :g]], {:"main/exact_matches"=>0, :"main/loose_matches"=>0}],
|
154
|
+
# [[[:c, :g, :g, :y, :b, :c], [:w, :y, :g, :b, :y, :c]], {:"main/exact_matches"=>2, :"main/loose_matches"=>0}],
|
155
|
+
# [[[:r, :b, :r, :g, :w, :r], [:c, :w, :y, :g, :g, :y]], {:"main/exact_matches"=>1, :"main/loose_matches"=>0}],
|
156
|
+
# [[[:r, :y, :c, :y, :y, :b], [:g, :r, :b, :c, :r, :y]], {:"main/exact_matches"=>0, :"main/loose_matches"=>0}],
|
157
|
+
|
158
|
+
def self.score(secret, guess)
|
159
|
+
{ ns(:exact_matches) => exact_matches(secret, guess.take(3)),
|
160
|
+
ns(:loose_matches) => 0 }
|
161
|
+
end
|
162
|
+
|
163
|
+
# S.exercise_fn method(:score)
|
164
|
+
|
165
|
+
# Speculation::Error: Call to 'main.exact_matches' did not conform to spec:
|
166
|
+
# In: [1] val: [:w, :y, :c] fails spec: :"Object/code" at: [:args, :guess] predicate: [#<Method: Speculation::Utils.count_between?>, [[:w, :y, :c], 4, 6]]
|
167
|
+
# Speculation/args [[:r, :b, :c, :y, :b, :r], [:w, :y, :c]]
|
168
|
+
# Speculation/failure :instrument
|
169
|
+
# Speculation::Test/caller "(pry):69:in `score'"
|
170
|
+
|
171
|
+
def self.score(secret, guess)
|
172
|
+
{ ns(:exact_matches) => exact_matches(secret, guess),
|
173
|
+
ns(:loose_matches) => 0 }
|
174
|
+
end
|
175
|
+
|
176
|
+
def match_count; end
|
177
|
+
|
178
|
+
S.fdef method(:match_count),
|
179
|
+
:args => ns(:secret_and_guess),
|
180
|
+
:ret => ns(S, :natural_integer),
|
181
|
+
:fn => ->(fn) { fn[:ret].between?(0, fn[:args][:secret].count) }
|
182
|
+
|
183
|
+
S.exercise_fn method(:exact_matches), :n => 10, :fspec => S.get_spec(method(:match_count))
|
184
|
+
|
185
|
+
# [[[[:r, :b, :g, :w, :b], [:b, :c, :c, :r, :w]], 0],
|
186
|
+
# [[[:c, :r, :c, :g, :g, :y], [:y, :c, :b, :y, :y, :r]], 0],
|
187
|
+
# [[[:c, :g, :r, :y, :y], [:w, :y, :y, :c, :w]], 0],
|
188
|
+
|
189
|
+
STest.check_method method(:exact_matches), S.get_spec(method(:match_count))
|
190
|
+
|
191
|
+
# {:spec=>Speculation::FSpec(main.match_count), :"Speculation::Test/ret"=>{:num_tests=>1000, :result=>true}, :method=>#<Method: main.exact_matches>}
|
192
|
+
|
193
|
+
STest.instrument method(:exact_matches), :spec => { method(:exact_matches) => S.get_spec(method(:match_count)) }
|
194
|
+
|
195
|
+
S.exercise_fn method(:score)
|
196
|
+
|
197
|
+
# [[[[:y, :r, :y, :c, :y], [:b, :w, :c, :g, :b]], {:"main/exact_matches"=>0, :"main/loose_matches"=>0}],
|
198
|
+
# [[[:r, :b, :w, :c, :w, :y], [:w, :r, :w, :g, :b, :r]], {:"main/exact_matches"=>1, :"main/loose_matches"=>0}],
|
199
|
+
# [[[:c, :w, :r, :w, :g, :c], [:g, :g, :c, :c, :c, :b]], {:"main/exact_matches"=>0, :"main/loose_matches"=>0}],
|
200
|
+
|
201
|
+
STest.check method(:score)
|
202
|
+
|
203
|
+
# [{:spec=>Speculation::FSpec(main.score), :"Speculation::Test/ret"=>{:num_tests=>1000, :result=>true}, :method=>#<Method: main.score>}]
|
204
|
+
|
205
|
+
def self.all_matches(secret, guess)
|
206
|
+
frequencies = ->(xs) { xs.group_by(&:itself).transform_values(&:count) }
|
207
|
+
select_keys = ->(h, ks) { Hash[ks.zip(h.values_at(*ks))].compact }
|
208
|
+
|
209
|
+
select_keys.call(frequencies.call(secret), guess).
|
210
|
+
merge(select_keys.call(frequencies.call(guess), secret)) { |k, a, b| [a, b].min }.
|
211
|
+
values.
|
212
|
+
reduce(0, &:+)
|
213
|
+
end
|
214
|
+
|
215
|
+
S.exercise_fn method(:all_matches), :n => 10, :fspec => S.get_spec(method(:match_count))
|
216
|
+
|
217
|
+
# [[[[:c, :b, :w, :w], [:b, :c, :g, :y]], 2],
|
218
|
+
# [[[:y, :r, :g, :r, :b], [:r, :g, :w, :b, :r]], 4],
|
219
|
+
# [[[:r, :g, :r, :g, :g], [:r, :g, :g, :b, :c]], 3],
|
220
|
+
|
221
|
+
def self.score(secret, guess)
|
222
|
+
exact = exact_matches(secret, guess)
|
223
|
+
all = all_matches(secret, guess)
|
224
|
+
|
225
|
+
{ ns(:exact_matches) => exact,
|
226
|
+
ns(:loose_matches) => all - exact }
|
227
|
+
end
|
228
|
+
|
229
|
+
STest.instrument [method(:exact_matches), method(:all_matches)],
|
230
|
+
:spec => { method(:exact_matches) => S.get_spec(method(:exact_matches)),
|
231
|
+
method(:all_matches) => S.get_spec(method(:exact_matches)) }
|
232
|
+
|
233
|
+
S.exercise_fn method(:score)
|
234
|
+
# [[[[:w, :r, :w, :c, :c, :c], [:r, :y, :r, :w, :c, :b]], {:"main/exact_matches"=>1, :"main/loose_matches"=>2}],
|
235
|
+
# [[[:r, :c, :w, :y, :c], [:g, :c, :y, :y, :y]], {:"main/exact_matches"=>2, :"main/loose_matches"=>0}],
|
236
|
+
# [[[:y, :y, :b, :g, :b], [:g, :w, :b, :c, :g]], {:"main/exact_matches"=>1, :"main/loose_matches"=>1}],
|
237
|
+
# [[[:c, :b, :r, :y, :g], [:g, :w, :r, :y, :y]], {:"main/exact_matches"=>2, :"main/loose_matches"=>1}],
|
238
|
+
|
239
|
+
STest.summarize_results STest.check method(:score)
|
240
|
+
|
241
|
+
# {:total=>1, :check_passed=>1}
|
242
|
+
|
243
|
+
result = STest.summarize_results STest.check(method(:score))
|
244
|
+
result[:total] == result[:check_passed] && !result.key?(:check_failed)
|
245
|
+
|
246
|
+
# true
|