crosscounter 0.3.0 → 0.4.0
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 +7 -0
- data/.rspec +3 -0
- data/.travis.yml +2 -0
- data/NOTES.txt +102 -58
- data/README.md +44 -2
- data/Rakefile +1 -1
- data/bench/cols.json +1 -0
- data/bench/compute.rb +28 -0
- data/bench/data.json +1 -0
- data/bench/rows.json +1 -0
- data/crosscounter.gemspec +4 -2
- data/lib/crosscounter/compute.rb +23 -18
- data/lib/crosscounter/expansion.rb +5 -3
- data/lib/crosscounter/util.rb +14 -13
- data/lib/crosscounter/version.rb +1 -1
- data/lib/crosscounter.rb +3 -2
- data/spec/crosscounter/compute_spec.rb +14 -37
- data/spec/crosscounter/expansion_spec.rb +2 -2
- data/spec/crosscounter/util_spec.rb +12 -3
- data/spec/spec_helper.rb +6 -0
- metadata +46 -23
data/bench/rows.json
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
{"days":["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],"times":["12 AM","01 AM","02 AM","03 AM","04 AM","05 AM","06 AM","07 AM","08 AM","09 AM","10 AM","11 AM","12 PM","01 PM","02 PM","03 PM","04 PM","05 PM","06 PM","07 PM","08 PM","09 PM","10 PM","11 PM"],"answers":["Q4 This is a big step forward.","Q4 This is a little step forward.","Q4 This is a big step back.","Q4 This is a little step back.","Q4 I'm not sure yet!","Q6 Apple is leading.","Q6 Apple is following.","Q9 constantly (many times daily)","Q9 often (daily)","Q9 sometimes (at least once a week)","Q9 infrequently (less than once a week)","Q9 very rarely (or never)"],"scales":[],"group":["closed - awesome","closed - new address","closed - not awesome","partials"],"tags":["00: stuff","01: wacky","02: data","03: parking","0: parking lot","0: weird","1: air drop","1: battery life","1: control center","1: data detectors","1: fingerprint","1: folders","1: home screen","1: integration","1: lock screen","1: mulltitasking","1: notification center","1: other","1: ringtones","1: settings","1: siri","1: slide to","1: spotlight","2: app store","2: calendar","2: camera","2: clock","2: compass","2: facetime","2: game center","2: mail","2: maps","2: messages","2: music","2: notes","2: other","2: passbook","2: phone","2: photos","2: reminders","2: safari","2: videos","2: weather","3: backgrounds","3: colors","3: icons","3: motion","3: other","3: transparency","3: typography","4: bug/crash/etc","4: functionality","4: other","4: privacy","4: usability","5: aesthetics","5: integration","5: learning/context/automation","5: none","5: options/settings","5: other","5: revert","6: can't update","6: fantastic","6: gratuitious","at: android","at: apple","at: clean","at: color","at: control center","at: flashlight","at: flat","at: home","at: icon","at: multitasking","at: pretty","at: swipe","auto: color"],"scout":["Aaron M","Abby S","Albert Q","Allison M","Alyssa D","Alyssa S","Amber O","Andrea C","Andrew G","Andrew L","Andrew P","Ann C","Anna P","Anquinette B","Bekki H","Bianca B","Brett G","Brittany B","Brittany W","Bryant B","Carl C","Caroline H","Chris H","Chris J","Christina H","Clayton B","Connor T","Dan W","Daniel S","Danielle D","David O","Denise T","Diane P","Drew M","Duncan K","Emilee M","Erin S","Faridah B","Flo C","Gina C","Glori M","Harry T","Irene O","Jamie C","Jamie N","Jeff P","Jennifer B","Jennifer B","Jennifer H","Jesse G","Jesse G","Joe H","Jonathan D","Jordan B","Jordan F","Jordan P","Jose R","Julia M","Julio L","Katherine L","Laura G","Laura K","Lauren M","Leah Z","Lisa T","Lynn S","Marcus K","Maria A","Matt S","Mayling C","Megan B","Mehmet D","Melissa D","Melissa H","Melissa S","Meredith G","Meredith Y","Michael B","Michael H","Michael K","Michael L","Michelle G","Michelle M","Mike M","Nadyne R","Philip K","Rajeev D","Raman M","Rosalinda C","Ryan F","Sandra D","Scott F","Shannah S","Sharon E","Shawna M","Simon M","Stephen H","Steven S","Stevie S","Tysa S","Vincent C","Waqas M","francis r","grace d","jason s"],"gender":["male","female","NA"],"age":["Age 13","Age 14","Age 15","Age 16","Age 17","Age 18","Age 19","Age 20","Age 21","Age 22","Age 23","Age 24","Age 25","Age 26","Age 27","Age 28","Age 29","Age 30","Age 31","Age 32","Age 33","Age 34","Age 35","Age 36","Age 37","Age 38","Age 39","Age 40","Age 41","Age 42","Age 43","Age 44","Age 45","Age 46","Age 47","Age 48","Age 49","Age 50","Age 51","Age 52","Age 53","Age 54","Age 55","Age 56","Age 57","Age 58","Age 59","Age 60","Age 61","Age 62","Age 63","Age 64","Age 65","Age 66","Age 67","Age 68","Age 69","Age 70","Age 71","Age 72","Age 73","Age 74","Age 75","Age 76","Age 77","Age 78","Age 79","Age 80","Age NA"]}
|
data/crosscounter.gemspec
CHANGED
@@ -8,15 +8,17 @@ Gem::Specification.new do |gem|
|
|
8
8
|
gem.version = Crosscounter::VERSION
|
9
9
|
gem.authors = ['Parker Selbert']
|
10
10
|
gem.email = ['parker@sorentwo.com']
|
11
|
+
gem.homepage = 'https://github.com/sorentwo/crosscounter'
|
11
12
|
gem.description = %(Functionally create cross tabulations)
|
12
13
|
gem.summary = %(
|
13
14
|
Crosscounter allows you to create a simple pipeline for defining cross
|
14
15
|
tabulated output)
|
15
|
-
gem.homepage = 'https://github.com/sorentwo/crosscounter'
|
16
16
|
|
17
17
|
gem.files = `git ls-files`.split($/)
|
18
18
|
gem.test_files = gem.files.grep(%r{^(spec)/})
|
19
19
|
gem.require_paths = ['lib']
|
20
20
|
|
21
|
-
gem.add_development_dependency '
|
21
|
+
gem.add_development_dependency 'rake', '~> 10.1'
|
22
|
+
gem.add_development_dependency 'rspec', '~> 2.99.0.beta1'
|
23
|
+
gem.add_development_dependency 'ruby-prof'
|
22
24
|
end
|
data/lib/crosscounter/compute.rb
CHANGED
@@ -2,31 +2,36 @@ require 'crosscounter/util'
|
|
2
2
|
|
3
3
|
module Crosscounter
|
4
4
|
module Compute
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def compute(hashes, prop_a, prop_b = nil)
|
8
|
+
count = 0
|
9
|
+
index = 0
|
10
|
+
length = hashes.length
|
11
|
+
|
12
|
+
while index < length
|
13
|
+
if hashes[index].member?(prop_a) && (!prop_b || hashes[index].member?(prop_b))
|
14
|
+
count += 1
|
15
15
|
end
|
16
|
+
|
17
|
+
index += 1
|
16
18
|
end
|
19
|
+
|
20
|
+
count
|
17
21
|
end
|
18
22
|
|
19
|
-
def
|
20
|
-
|
23
|
+
def compute_all(enumerable, rows, columns)
|
24
|
+
setified = enumerable.map { |hash| Util.hashify(hash) }
|
25
|
+
scolumns = Util.stringify(columns)
|
21
26
|
|
22
|
-
|
23
|
-
initial = [
|
27
|
+
Util.stringify(rows).map do |row|
|
28
|
+
initial = [row, compute(setified, row)]
|
24
29
|
|
25
|
-
|
26
|
-
|
27
|
-
tuple.first => tuple.last,
|
28
|
-
"_#{column.first}" => column.last)
|
30
|
+
scolumns.each do |col|
|
31
|
+
initial << compute(setified, row, col)
|
29
32
|
end
|
33
|
+
|
34
|
+
initial
|
30
35
|
end
|
31
36
|
end
|
32
37
|
end
|
@@ -1,13 +1,15 @@
|
|
1
1
|
module Crosscounter
|
2
2
|
module Expansion
|
3
|
-
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def expand(keywords, expansions)
|
4
6
|
keywords.inject({}) do |hash, keyword|
|
5
7
|
hash[keyword] = resolved(expansions, keyword)
|
6
8
|
hash
|
7
9
|
end
|
8
10
|
end
|
9
11
|
|
10
|
-
def
|
12
|
+
def replace(enumerable, replacements)
|
11
13
|
enumerable.map do |object|
|
12
14
|
replacements.inject({}) do |hash, replacement|
|
13
15
|
hash[replacement.first] = replacement.last.call(object)
|
@@ -17,7 +19,7 @@ module Crosscounter
|
|
17
19
|
end
|
18
20
|
end
|
19
21
|
|
20
|
-
def
|
22
|
+
def resolved(expansions, keyword)
|
21
23
|
value = expansions.fetch(keyword)
|
22
24
|
|
23
25
|
value.respond_to?(:call) ? value.call : value
|
data/lib/crosscounter/util.rb
CHANGED
@@ -1,22 +1,23 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
1
3
|
module Crosscounter
|
2
4
|
module Util
|
3
|
-
|
5
|
+
extend self
|
4
6
|
|
5
|
-
def
|
6
|
-
hash.
|
7
|
-
|
7
|
+
def hashify(hash)
|
8
|
+
stringify(hash).inject({}) do |memo, key|
|
9
|
+
memo[key] = 0
|
10
|
+
memo
|
8
11
|
end
|
9
|
-
|
10
|
-
hash
|
11
|
-
end
|
12
|
-
|
13
|
-
def self.stringify_all(array)
|
14
|
-
array.map { |object| Crosscounter::Util.stringify_keys(object) }
|
15
12
|
end
|
16
13
|
|
17
|
-
def
|
18
|
-
|
19
|
-
|
14
|
+
def stringify(hash, sep = '|')
|
15
|
+
hash.flat_map do |key, value|
|
16
|
+
if value.kind_of?(Enumerable)
|
17
|
+
value.map { |elem| "#{key}#{sep}#{elem}" }
|
18
|
+
else
|
19
|
+
["#{key}#{sep}#{value}"]
|
20
|
+
end
|
20
21
|
end
|
21
22
|
end
|
22
23
|
end
|
data/lib/crosscounter/version.rb
CHANGED
data/lib/crosscounter.rb
CHANGED
@@ -6,38 +6,15 @@ describe Crosscounter::Compute do
|
|
6
6
|
describe '.compute' do
|
7
7
|
it 'counts the number of cross occurring values between all properties' do
|
8
8
|
enumerable = [
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
Set.new(['age-18', 'gender-male']),
|
10
|
+
Set.new(['age-19', 'gender-female']),
|
11
|
+
Set.new(['age-18', 'gender-male'])
|
12
12
|
]
|
13
13
|
|
14
|
-
computer.compute(enumerable, 'age'
|
15
|
-
computer.compute(enumerable, 'age'
|
16
|
-
computer.compute(enumerable, 'age'
|
17
|
-
computer.compute(enumerable, 'age'
|
18
|
-
end
|
19
|
-
|
20
|
-
it 'matches against regular expressions' do
|
21
|
-
enumerable = [
|
22
|
-
{ 'age' => 18, 'tags' => %w[happy sad] },
|
23
|
-
{ 'age' => 19, 'tags' => %w[happy mad] },
|
24
|
-
{ 'age' => 18, 'tags' => %w[mad sad] },
|
25
|
-
{ 'age' => 18, 'tags' => %w[sad] }
|
26
|
-
]
|
27
|
-
|
28
|
-
computer.compute(enumerable, 'age' => 18, 'tags' => 'sad').should == 3
|
29
|
-
computer.compute(enumerable, 'age' => 18, 'tags' => 'happy').should == 1
|
30
|
-
end
|
31
|
-
|
32
|
-
it 'compensates for duplicate keys by normalizing leading underscores' do
|
33
|
-
enumerable = [
|
34
|
-
{ 'age' => '18', 'tags' => %w[happy sad] },
|
35
|
-
{ 'age' => '19', 'tags' => %w[happy mad] },
|
36
|
-
{ 'age' => '18', 'tags' => %w[mad sad] },
|
37
|
-
{ 'age' => '18', 'tags' => %w[sad] }
|
38
|
-
]
|
39
|
-
|
40
|
-
computer.compute(enumerable, 'tags' => 'sad', '_tags' => 'happy').should == 1
|
14
|
+
computer.compute(enumerable, 'age-18').should == 2
|
15
|
+
computer.compute(enumerable, 'age-18', 'gender-male').should == 2
|
16
|
+
computer.compute(enumerable, 'age-19', 'gender-male').should == 0
|
17
|
+
computer.compute(enumerable, 'age-19', 'gender-female').should == 1
|
41
18
|
end
|
42
19
|
end
|
43
20
|
|
@@ -56,13 +33,13 @@ describe Crosscounter::Compute do
|
|
56
33
|
)
|
57
34
|
|
58
35
|
computed.should == [
|
59
|
-
[18,
|
60
|
-
[19,
|
61
|
-
['male',
|
62
|
-
['female',
|
63
|
-
['happy',
|
64
|
-
['sad',
|
65
|
-
['mad',
|
36
|
+
['age|18', 2, 1, 2, 1],
|
37
|
+
['age|19', 2, 1, 1, 1],
|
38
|
+
['gender|male', 3, 1, 3, 1],
|
39
|
+
['gender|female', 1, 1, 0, 1],
|
40
|
+
['tags|happy', 2, 2, 1, 1],
|
41
|
+
['tags|sad', 3, 1, 3, 1],
|
42
|
+
['tags|mad', 2, 1, 1, 2]
|
66
43
|
]
|
67
44
|
end
|
68
45
|
end
|
@@ -19,8 +19,8 @@ describe Crosscounter::Expansion do
|
|
19
19
|
|
20
20
|
describe '.replace' do
|
21
21
|
it 'replaces all objects with mapped values' do
|
22
|
-
male_object =
|
23
|
-
female_object =
|
22
|
+
male_object = double(gender: 'male')
|
23
|
+
female_object = double(gender: 'female')
|
24
24
|
|
25
25
|
replaced = expansion.replace([male_object, female_object],
|
26
26
|
gender: -> object { object.gender.downcase })
|
@@ -1,9 +1,18 @@
|
|
1
1
|
require 'crosscounter/util'
|
2
2
|
|
3
3
|
describe Crosscounter::Util do
|
4
|
-
describe '.
|
5
|
-
it 'unzips
|
6
|
-
Crosscounter::Util.
|
4
|
+
describe '.stringify' do
|
5
|
+
it 'unzips a hash into key-value strings' do
|
6
|
+
expect(Crosscounter::Util.stringify(age: [18, 19, 20])).to eq([
|
7
|
+
'age|18', 'age|19', 'age|20'
|
8
|
+
])
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'unzips a key/value hash into key/value tuples' do
|
12
|
+
expect(Crosscounter::Util.stringify(gender: 'male', name: 'Tom')).to eq([
|
13
|
+
'gender|male',
|
14
|
+
'name|Tom'
|
15
|
+
])
|
7
16
|
end
|
8
17
|
end
|
9
18
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,32 +1,57 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: crosscounter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.4.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Parker Selbert
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2014-01-31 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '10.1'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '10.1'
|
14
27
|
- !ruby/object:Gem::Dependency
|
15
28
|
name: rspec
|
16
29
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
30
|
requirements:
|
19
|
-
- - ~>
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 2.99.0.beta1
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 2.99.0.beta1
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: ruby-prof
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
20
46
|
- !ruby/object:Gem::Version
|
21
|
-
version:
|
47
|
+
version: '0'
|
22
48
|
type: :development
|
23
49
|
prerelease: false
|
24
50
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
51
|
requirements:
|
27
|
-
- -
|
52
|
+
- - ">="
|
28
53
|
- !ruby/object:Gem::Version
|
29
|
-
version:
|
54
|
+
version: '0'
|
30
55
|
description: Functionally create cross tabulations
|
31
56
|
email:
|
32
57
|
- parker@sorentwo.com
|
@@ -34,13 +59,18 @@ executables: []
|
|
34
59
|
extensions: []
|
35
60
|
extra_rdoc_files: []
|
36
61
|
files:
|
37
|
-
- .gitignore
|
38
|
-
- .
|
62
|
+
- ".gitignore"
|
63
|
+
- ".rspec"
|
64
|
+
- ".travis.yml"
|
39
65
|
- Gemfile
|
40
66
|
- LICENSE.txt
|
41
67
|
- NOTES.txt
|
42
68
|
- README.md
|
43
69
|
- Rakefile
|
70
|
+
- bench/cols.json
|
71
|
+
- bench/compute.rb
|
72
|
+
- bench/data.json
|
73
|
+
- bench/rows.json
|
44
74
|
- crosscounter.gemspec
|
45
75
|
- lib/crosscounter.rb
|
46
76
|
- lib/crosscounter/compute.rb
|
@@ -53,33 +83,26 @@ files:
|
|
53
83
|
- spec/spec_helper.rb
|
54
84
|
homepage: https://github.com/sorentwo/crosscounter
|
55
85
|
licenses: []
|
86
|
+
metadata: {}
|
56
87
|
post_install_message:
|
57
88
|
rdoc_options: []
|
58
89
|
require_paths:
|
59
90
|
- lib
|
60
91
|
required_ruby_version: !ruby/object:Gem::Requirement
|
61
|
-
none: false
|
62
92
|
requirements:
|
63
|
-
- -
|
93
|
+
- - ">="
|
64
94
|
- !ruby/object:Gem::Version
|
65
95
|
version: '0'
|
66
|
-
segments:
|
67
|
-
- 0
|
68
|
-
hash: -2417517050912310752
|
69
96
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
-
none: false
|
71
97
|
requirements:
|
72
|
-
- -
|
98
|
+
- - ">="
|
73
99
|
- !ruby/object:Gem::Version
|
74
100
|
version: '0'
|
75
|
-
segments:
|
76
|
-
- 0
|
77
|
-
hash: -2417517050912310752
|
78
101
|
requirements: []
|
79
102
|
rubyforge_project:
|
80
|
-
rubygems_version:
|
103
|
+
rubygems_version: 2.2.0
|
81
104
|
signing_key:
|
82
|
-
specification_version:
|
105
|
+
specification_version: 4
|
83
106
|
summary: Crosscounter allows you to create a simple pipeline for defining cross tabulated
|
84
107
|
output
|
85
108
|
test_files:
|