davidrichards-just_enumerable_stats 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|