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 CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
2
  :major: 0
3
3
  :minor: 0
4
- :patch: 3
4
+ :patch: 4
@@ -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 self.is_numeric?
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 self.is_numeric?
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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: davidrichards-just_enumerable_stats
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Richards