module ConditionalSample::MixMe
This module is suitable as a mixin, using the results of self#to_a
It is automatically included in Array, so each of these methods are added to Array when you require 'conditional_sample'
For both methods, the 'conditions' array must contain boolean procs using args |arr, elem|
- arr
-
a reference to the current array that has been built up through the recursion chain.
- elem
-
a reference to the current element being considered.
Public Instance Methods
Return a permutation of 'array' where each element validates to the same index in a 'conditions' array of procs that return Boolean.
The output is an array that is a complete permutation of the input array. i.e. output.length == array.length
Any elements in the array that are extra to the number of conditions will be assumed valid.
array = [1,2,3,4,5].shuffle conditions = [ proc { |arr, elem| elem < 2}, proc { |arr, elem| elem > 2}, proc { |arr, elem| elem > 1} ] array.conditional_permutation(conditions) possible output => [1, 3, 4, 5, 2]
# File lib/conditional_sample/public.rb, line 40 def conditional_permutation conditions, seconds = nil ConditionalSample::method_assert(self, 'to_a') # Convert the conditions to an array. conditions = ConditionalSample::to_conditions_array(conditions, proc { true }) # Run the recursion, and rescue after a number of seconds. timeout_rescue(seconds, []) do conditional_permutation_recurse(self.to_a, conditions).tap{ |i| i.pop } end end
Return values from 'array' where each element validates to the same index in a 'conditions' array of procs that return Boolean.
The output is an array of conditions.length that is a partial permutation of the input array, where satisfies only the conditions.
Any elements in the array that are extra to the number of conditions will not be output.
array = [1,2,3,4,5].shuffle conditions = [ proc { |arr, elem| elem < 2}, proc { |arr, elem| elem > 2}, proc { |arr, elem| elem > 1} ] array.conditional_sample(conditions) possible output => [1, 5, 3]
# File lib/conditional_sample/public.rb, line 73 def conditional_sample conditions, seconds = nil ConditionalSample::method_assert(self, 'to_a') # Would always return [] anyway if there are more conditions than # inputs, this just avoids running the complex recursion code. return [] if conditions.length > self.to_a.length # Convert the conditions to an array. conditions = ConditionalSample::to_conditions_array(conditions, proc { true }) # Run the recursion, and rescue after a number of seconds. timeout_rescue(seconds, []) do conditional_sample_recurse(self.to_a, conditions).tap{ |i| i.pop } end end
Private Instance Methods
Private recursive method. conditional_permutation
is the public interface.
# File lib/conditional_sample/private.rb, line 36 def conditional_permutation_recurse ( array, conditions, current_iter = 0, current_array = []) output = [] # Get the current conditional. cond = conditions[current_iter] # Loop through and return the first element that validates. valid = false array.each do |elem| # Test the condition. If we've run out of elements # in the condition array, then allow any value. valid = cond ? cond.call(current_array, elem) : true if valid # Remove this element from the array, and recurse. remain = array.dup delete_first!(remain, elem) # If the remaining array is empty, no need to recurse. new_val = nil if !remain.empty? new_val = conditional_permutation_recurse( remain, conditions, current_iter + 1, current_array + [elem]) end # If we cannot use this value, because it breaks future conditions. if !remain.empty? && new_val.empty? valid = false else output << elem << new_val end end break if valid end output.flatten end
Private recursive method. conditional_sample
is the public interface.
# File lib/conditional_sample/private.rb, line 88 def conditional_sample_recurse ( array, conditions, current_iter = 0, current_array = []) output = [] # Get the current conditional. cond = conditions[current_iter] # Return nil if we have reached the end of the conditionals. return nil if cond.nil? # Loop through and return the first element that validates. valid = false array.each do |elem| # Test the condition. If we've run out of elements # in the condition array, then allow any value. valid = cond.call(current_array, elem) if valid # Remove this element from the array, and recurse. remain = array.dup delete_first!(remain, elem) # If the remaining array is empty, no need to recurse. new_val = conditional_sample_recurse( remain, conditions, current_iter + 1, current_array + [elem]) # If we cannot use this value, because it breaks future conditions. if new_val and new_val.empty? valid = false else output << elem << new_val end end break if valid end output.flatten end
Delete the first matching value in an array. Destructive to the first argument.
# File lib/conditional_sample/private.rb, line 28 def delete_first! array, value array.delete_at(array.index(value) || array.length) end
Run a bloc in a given number of 'seconds'. If the bloc completes in time, then return the result of the bloc, else return 'rescue_value'.
# File lib/conditional_sample/private.rb, line 14 def timeout_rescue seconds, rescue_value = nil begin Timeout::timeout(seconds.to_f) do yield end rescue Timeout::Error rescue_value end end