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.
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 'rspec', '~> 2.12.0'
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
@@ -2,31 +2,36 @@ require 'crosscounter/util'
2
2
 
3
3
  module Crosscounter
4
4
  module Compute
5
- def self.compute(enumerable, properties)
6
- enumerable.count do |object|
7
- properties.all? do |key, value|
8
- extracted = (object[key] || object[key.sub('_', '')])
9
-
10
- if extracted.kind_of?(Array)
11
- extracted.include?(value)
12
- else
13
- extracted == value
14
- end
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 self.compute_all(enumerable, rows, columns)
20
- enumerable = Crosscounter::Util.stringify_all(enumerable)
23
+ def compute_all(enumerable, rows, columns)
24
+ setified = enumerable.map { |hash| Util.hashify(hash) }
25
+ scolumns = Util.stringify(columns)
21
26
 
22
- Crosscounter::Util.tuplize(rows).map do |tuple|
23
- initial = [tuple.last, compute(enumerable, tuple.first => tuple.last)]
27
+ Util.stringify(rows).map do |row|
28
+ initial = [row, compute(setified, row)]
24
29
 
25
- Crosscounter::Util.tuplize(columns).inject(initial) do |rows, column|
26
- rows << compute(enumerable,
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
- def self.expand(keywords, expansions)
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 self.replace(enumerable, replacements)
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 self.resolved(expansions, keyword)
22
+ def resolved(expansions, keyword)
21
23
  value = expansions.fetch(keyword)
22
24
 
23
25
  value.respond_to?(:call) ? value.call : value
@@ -1,22 +1,23 @@
1
+ require 'set'
2
+
1
3
  module Crosscounter
2
4
  module Util
3
- @@tuples = {}
5
+ extend self
4
6
 
5
- def self.stringify_keys(hash)
6
- hash.keys.each do |key|
7
- hash[key.to_s] = hash.delete(key)
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 self.tuplize(hash)
18
- @@tuples[hash] ||= hash.flat_map do |tuple|
19
- tuple.last.map { |value| [tuple.first.to_s, value] }
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
@@ -1,3 +1,3 @@
1
1
  module Crosscounter
2
- VERSION = '0.3.0'
2
+ VERSION = '0.4.0'
3
3
  end
data/lib/crosscounter.rb CHANGED
@@ -1,6 +1,7 @@
1
- require 'crosscounter/version'
2
- require 'crosscounter/expansion'
3
1
  require 'crosscounter/compute'
2
+ require 'crosscounter/expansion'
3
+ require 'crosscounter/util'
4
+ require 'crosscounter/version'
4
5
 
5
6
  module Crosscounter
6
7
  end
@@ -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
- { 'age' => 18, 'gender' => 'male' },
10
- { 'age' => 19, 'gender' => 'female' },
11
- { 'age' => 18, 'gender' => 'male' }
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' => 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
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, 2, 1, 2, 1],
60
- [19, 2, 1, 1, 1],
61
- ['male', 3, 1, 3, 1],
62
- ['female', 1, 1, 0, 1],
63
- ['happy', 2, 2, 1, 1],
64
- ['sad', 3, 1, 3, 1],
65
- ['mad', 2, 1, 1, 2]
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 = mock(gender: 'male')
23
- female_object = mock(gender: 'female')
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 '.tuplize' do
5
- it 'unzips the hash into key/value tuples' do
6
- Crosscounter::Util.tuplize(age: [18, 19, 20]).should == [['age', 18], ['age', 19], ['age', 20]]
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
@@ -0,0 +1,6 @@
1
+ RSpec.configure do |config|
2
+ config.treat_symbols_as_metadata_keys_with_true_values = true
3
+ config.run_all_when_everything_filtered = true
4
+ config.filter_run :focus
5
+ config.order = 'random'
6
+ end
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.3.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: 2013-02-21 00:00:00.000000000 Z
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: 2.12.0
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: 2.12.0
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
- - .travis.yml
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: 1.8.23
103
+ rubygems_version: 2.2.0
81
104
  signing_key:
82
- specification_version: 3
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: