From b4fd476156be481ac352edb90ae5aa4b4af357c2 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Tue, 21 Jun 2016 00:53:03 +1200 Subject: [PATCH] Improve filter, using a block to get specific time for a given value. --- lib/periodical/filter.rb | 55 ++++++++++++++++++++-------------- lib/periodical/version.rb | 2 +- spec/periodical/filter_spec.rb | 8 ++--- 3 files changed, 36 insertions(+), 29 deletions(-) diff --git a/lib/periodical/filter.rb b/lib/periodical/filter.rb index 2a88877..a98f865 100644 --- a/lib/periodical/filter.rb +++ b/lib/periodical/filter.rb @@ -26,35 +26,44 @@ module Periodical module Filter # Keep count sorted objects per period. class Period - KeepOldest = Proc.new do |t1, t2| - t1 > t2 - end - - KeepYoungest = Proc.new do |t1, t2| - t1 < t2 - end - + # Given times a and b, should we prefer a? + ORDER = { + # We want `a` if `a` < `b`, i.e. it's older. + old: ->(a, b){a < b}, + + # We want `a` if `a` > `b`, i.e. it's newer. + new: ->(a, b){a > b} + } + + # @param count the number of items we should retain. def initialize(count) @count = count end - def filter(values, options = {}) + # @param order can be a key in ORDER or a lambda. + # @param block is applied to the value and should typically return a Time instance. + def filter(values, keep: :old, &block) slots = {} - - keep = (options[:keep] == :youngest) ? KeepYoungest : KeepOldest - + + keep = ORDER.fetch(keep, keep) + values.each do |value| - k = key(value) - - # We want to keep the newest backup if possible (<). - next if slots.key?(k) and keep.call(value, slots[k]) - - slots[k] = value + time = block_given? ? yield(value) : value + + granular_key = key(time) + + # We filter out this value if the slot is already full and we prefer the existing value. + if existing_value = slots[granular_key] + existing_time = block_given? ? yield(existing_value) : existing_value + next if keep.call(existing_time, time) + end + + slots[granular_key] = value end - + sorted_values = slots.values.sort - - return sorted_values[0...@count] + + return sorted_values.first(@count) end def key(t) @@ -113,11 +122,11 @@ def <<(period) @periods[period.class] = period end - def filter(values, options = {}) + def filter(values, **options, &block) filtered_values = Set.new @periods.values.each do |period| - filtered_values += period.filter(values, options) + filtered_values += period.filter(values, **options, &block) end return filtered_values, (Set.new(values) - filtered_values) diff --git a/lib/periodical/version.rb b/lib/periodical/version.rb index aae5721..8b44a3c 100644 --- a/lib/periodical/version.rb +++ b/lib/periodical/version.rb @@ -19,5 +19,5 @@ # THE SOFTWARE. module Periodical - VERSION = "1.0.1" + VERSION = "1.1.0" end diff --git a/spec/periodical/filter_spec.rb b/spec/periodical/filter_spec.rb index 3216f78..4a14034 100644 --- a/spec/periodical/filter_spec.rb +++ b/spec/periodical/filter_spec.rb @@ -36,11 +36,9 @@ module Periodical::PeriodSpec policy << Periodical::Filter::Daily.new(3) selected, rejected = policy.filter(dates) - expect(selected.count).to be 3 - expect(rejected.count).to be 3 - # Keep oldest is the default policy - expect(selected).to be_include(dates[0]) + expect(selected).to include(*dates.first(3)) + expect(rejected).to include(*dates.last(3)) end it "should keep youngest" do @@ -52,7 +50,7 @@ module Periodical::PeriodSpec policy = Periodical::Filter::Policy.new policy << Periodical::Filter::Monthly.new(1) - selected, rejected = policy.filter(dates, :keep => :youngest) + selected, rejected = policy.filter(dates, :keep => :new) expect(selected.count).to be 1 expect(rejected.count).to be 1