davidrichards-just_enumerable_stats 0.0.3 → 0.0.4
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/VERSION.yml +1 -1
- data/lib/just_enumerable_stats.rb +62 -1
- data/lib/just_enumerable_stats/stats.rb +56 -1
- data/spec/just_enumerable_stats/stats_spec.rb +36 -0
- data/spec/just_enumerable_stats_spec.rb +36 -0
- metadata +1 -1
data/VERSION.yml
CHANGED
@@ -2,6 +2,12 @@
|
|
2
2
|
$:.unshift File.dirname(__FILE__)
|
3
3
|
require 'fixed_range'
|
4
4
|
|
5
|
+
begin
|
6
|
+
require 'facets/dictionary'
|
7
|
+
rescue LoadError => e
|
8
|
+
# Do nothing
|
9
|
+
end
|
10
|
+
|
5
11
|
# Borrowed this from my own gem, sirb
|
6
12
|
|
7
13
|
class Object
|
@@ -215,7 +221,9 @@ module Enumerable
|
|
215
221
|
# For non-numeric values, returns a unique set,
|
216
222
|
# ordered if possible.
|
217
223
|
def categories
|
218
|
-
if
|
224
|
+
if @categories
|
225
|
+
@categories
|
226
|
+
elsif self.is_numeric?
|
219
227
|
self.range_instance.map
|
220
228
|
else
|
221
229
|
self.uniq.sort rescue self.uniq
|
@@ -239,8 +247,61 @@ module Enumerable
|
|
239
247
|
self.range_class
|
240
248
|
end
|
241
249
|
|
250
|
+
# Takes a hash of arrays for categories
|
251
|
+
# If Facets happens to be loaded on the computer, this keeps the order
|
252
|
+
# of the categories straight.
|
253
|
+
def set_range(hash)
|
254
|
+
if defined?(Dictionary)
|
255
|
+
@range_hash = Dictionary.new
|
256
|
+
@range_hash.merge!(hash)
|
257
|
+
@categories = @range_hash.keys
|
258
|
+
else
|
259
|
+
@categories = hash.keys
|
260
|
+
@range_hash = hash
|
261
|
+
end
|
262
|
+
@categories
|
263
|
+
end
|
264
|
+
|
265
|
+
# The hash of lambdas that are used to categorize the enumerable.
|
266
|
+
attr_reader :range_hash
|
267
|
+
|
268
|
+
# The arguments needed to instantiate the custom-defined range class.
|
242
269
|
attr_reader :range_class_args
|
243
270
|
|
271
|
+
# Counts each element where the block evaluates to true
|
272
|
+
# Example:
|
273
|
+
# a = [1,2,3]
|
274
|
+
# a.count_if {|e| e % 2 == 0}
|
275
|
+
def count_if(&block)
|
276
|
+
self.inject(0) do |s, e|
|
277
|
+
s += 1 if block.call(e)
|
278
|
+
s
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
# Returns a Hash or Dictionary (if available) for each category with a
|
283
|
+
# value as the set of matching values as an array.
|
284
|
+
# Because this is supposed to be lean (just enumerables), but this is an
|
285
|
+
# expensive call, I'm going to cache it and offer a parameter to reset
|
286
|
+
# the cache. So, call category_values(true) if you need to reset the
|
287
|
+
# cache.
|
288
|
+
def category_values(reset=false)
|
289
|
+
@category_values = nil if reset
|
290
|
+
return @category_values if @category_values
|
291
|
+
container = defined?(Dictionary) ? Dictionary.new : Hash.new
|
292
|
+
if self.range_hash
|
293
|
+
@category_values = self.categories.inject(container) do |cont, cat|
|
294
|
+
cont[cat] = self.find_all &self.range_hash[cat]
|
295
|
+
cont
|
296
|
+
end
|
297
|
+
else
|
298
|
+
@category_values = self.categories.inject(container) do |cont, cat|
|
299
|
+
cont[cat] = self.find_all {|e| e == cat}
|
300
|
+
cont
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
244
305
|
# When creating a range, what class will it be? Defaults to Range, but
|
245
306
|
# other classes are sometimes useful.
|
246
307
|
def range_class
|
@@ -210,7 +210,9 @@ module JustEnumerableStats #:nodoc:
|
|
210
210
|
# For non-numeric values, returns a unique set,
|
211
211
|
# ordered if possible.
|
212
212
|
def categories
|
213
|
-
if
|
213
|
+
if @categories
|
214
|
+
@categories
|
215
|
+
elsif self.is_numeric?
|
214
216
|
self.range_instance.map
|
215
217
|
else
|
216
218
|
self.uniq.sort rescue self.uniq
|
@@ -234,8 +236,61 @@ module JustEnumerableStats #:nodoc:
|
|
234
236
|
self.range_class
|
235
237
|
end
|
236
238
|
|
239
|
+
# Takes a hash of arrays for categories
|
240
|
+
# If Facets happens to be loaded on the computer, this keeps the order
|
241
|
+
# of the categories straight.
|
242
|
+
def set_range(hash)
|
243
|
+
if defined?(Dictionary)
|
244
|
+
@range_hash = Dictionary.new
|
245
|
+
@range_hash.merge!(hash)
|
246
|
+
@categories = @range_hash.keys
|
247
|
+
else
|
248
|
+
@categories = hash.keys
|
249
|
+
@range_hash = hash
|
250
|
+
end
|
251
|
+
@categories
|
252
|
+
end
|
253
|
+
|
254
|
+
# The hash of lambdas that are used to categorize the enumerable.
|
255
|
+
attr_reader :range_hash
|
256
|
+
|
257
|
+
# The arguments needed to instantiate the custom-defined range class.
|
237
258
|
attr_reader :range_class_args
|
238
259
|
|
260
|
+
# Counts each element where the block evaluates to true
|
261
|
+
# Example:
|
262
|
+
# a = [1,2,3]
|
263
|
+
# a.count_if {|e| e % 2 == 0}
|
264
|
+
def count_if(&block)
|
265
|
+
self.inject(0) do |s, e|
|
266
|
+
s += 1 if block.call(e)
|
267
|
+
s
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
# Returns a Hash or Dictionary (if available) for each category with a
|
272
|
+
# value as the set of matching values as an array.
|
273
|
+
# Because this is supposed to be lean (just enumerables), but this is an
|
274
|
+
# expensive call, I'm going to cache it and offer a parameter to reset
|
275
|
+
# the cache. So, call category_values(true) if you need to reset the
|
276
|
+
# cache.
|
277
|
+
def category_values(reset=false)
|
278
|
+
@category_values = nil if reset
|
279
|
+
return @category_values if @category_values
|
280
|
+
container = defined?(Dictionary) ? Dictionary.new : Hash.new
|
281
|
+
if self.range_hash
|
282
|
+
@category_values = self.categories.inject(container) do |cont, cat|
|
283
|
+
cont[cat] = self.find_all &self.range_hash[cat]
|
284
|
+
cont
|
285
|
+
end
|
286
|
+
else
|
287
|
+
@category_values = self.categories.inject(container) do |cont, cat|
|
288
|
+
cont[cat] = self.find_all {|e| e == cat}
|
289
|
+
cont
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
239
294
|
# When creating a range, what class will it be? Defaults to Range, but
|
240
295
|
# other classes are sometimes useful.
|
241
296
|
def range_class
|
@@ -243,6 +243,42 @@ describe JustEnumerableStats::Stats do
|
|
243
243
|
a.categories.should eql(a)
|
244
244
|
end
|
245
245
|
|
246
|
+
it "should be able to set a range with a hash of lambdas" do
|
247
|
+
@a.set_range({
|
248
|
+
"<= 2" => lambda{ |e| e <= 2 },
|
249
|
+
"> 2" => lambda{ |e| e > 2 }
|
250
|
+
})
|
251
|
+
@a.categories.sort.should eql(["<= 2", "> 2"].sort)
|
252
|
+
end
|
253
|
+
|
254
|
+
it "should be able to get a hash of category values" do
|
255
|
+
@a.set_range({
|
256
|
+
"<= 2" => lambda{ |e| e <= 2 },
|
257
|
+
"> 2" => lambda{ |e| e > 2 }
|
258
|
+
})
|
259
|
+
@a.category_values["<= 2"].should eql([1,2])
|
260
|
+
@a.category_values["> 2"].should eql([3])
|
261
|
+
end
|
262
|
+
|
263
|
+
it "should be able to get category values with a regular range" do
|
264
|
+
@a.category_values[1].should eql([1])
|
265
|
+
@a.category_values[2].should eql([2])
|
266
|
+
@a.category_values[3].should eql([3])
|
267
|
+
end
|
268
|
+
|
269
|
+
it "should be able to get category values with a custom range" do
|
270
|
+
@a.set_range_class(FixedRange, 1, 3, 0.5)
|
271
|
+
@a.category_values[1.0].should eql([1])
|
272
|
+
@a.category_values[1.5].should eql([])
|
273
|
+
@a.category_values[2.0].should eql([2])
|
274
|
+
@a.category_values[2.5].should eql([])
|
275
|
+
@a.category_values[3.0].should eql([3])
|
276
|
+
end
|
277
|
+
|
278
|
+
it "should be able to count conditionally" do
|
279
|
+
@a.count_if {|e| e == 2}.should eql(1)
|
280
|
+
end
|
281
|
+
|
246
282
|
it "should be able to instantiate a range" do
|
247
283
|
@a.range_as_range.should eql(Range.new(1, 3))
|
248
284
|
end
|
@@ -233,6 +233,42 @@ describe "JustEnumerableStats" do
|
|
233
233
|
a.categories.should eql(a)
|
234
234
|
end
|
235
235
|
|
236
|
+
it "should be able to set a range with a hash of lambdas" do
|
237
|
+
@a.set_range({
|
238
|
+
"<= 2" => lambda{ |e| e <= 2 },
|
239
|
+
"> 2" => lambda{ |e| e > 2 }
|
240
|
+
})
|
241
|
+
@a.categories.sort.should eql(["<= 2", "> 2"].sort)
|
242
|
+
end
|
243
|
+
|
244
|
+
it "should be able to get a hash of category values" do
|
245
|
+
@a.set_range({
|
246
|
+
"<= 2" => lambda{ |e| e <= 2 },
|
247
|
+
"> 2" => lambda{ |e| e > 2 }
|
248
|
+
})
|
249
|
+
@a.category_values["<= 2"].should eql([1,2])
|
250
|
+
@a.category_values["> 2"].should eql([3])
|
251
|
+
end
|
252
|
+
|
253
|
+
it "should be able to get category values with a regular range" do
|
254
|
+
@a.category_values[1].should eql([1])
|
255
|
+
@a.category_values[2].should eql([2])
|
256
|
+
@a.category_values[3].should eql([3])
|
257
|
+
end
|
258
|
+
|
259
|
+
it "should be able to get category values with a custom range" do
|
260
|
+
@a.set_range_class(FixedRange, 1, 3, 0.5)
|
261
|
+
@a.category_values[1.0].should eql([1])
|
262
|
+
@a.category_values[1.5].should eql([])
|
263
|
+
@a.category_values[2.0].should eql([2])
|
264
|
+
@a.category_values[2.5].should eql([])
|
265
|
+
@a.category_values[3.0].should eql([3])
|
266
|
+
end
|
267
|
+
|
268
|
+
it "should be able to count conditionally" do
|
269
|
+
@a.count_if {|e| e == 2}.should eql(1)
|
270
|
+
end
|
271
|
+
|
236
272
|
it "should be able to instantiate a range with a block" do
|
237
273
|
@a.range_as_range(&@inverse_matcher).should eql(Range.new(3, 1))
|
238
274
|
end
|