Skip to content

Commit

Permalink
Support flat_map
Browse files Browse the repository at this point in the history
flat_map is over 40% faster for small arrays (see benchmarks), but is
not available in Ruby-1.8.7. This commit adds an implementation for
1.8.7 with a pass-through for >= 1.9.

Hat tip to Myron Marston for the implementation that does not monkey
patch nor extend runtime objects, and supports blocks without converting
them to Procs.
  • Loading branch information
dchelimsky committed Aug 22, 2013
1 parent e329a9e commit c8a3804
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 11 deletions.
94 changes: 94 additions & 0 deletions benchmarks/map_then_flatten_vs_flat_map_benchmarks.rb
@@ -0,0 +1,94 @@
require 'benchmark'

$n = 10000
size = 100

puts "size: #{size}"
puts

def report
reals = []
Benchmark.benchmark do |bm|
3.times do
reals << bm.report { $n.times { yield } }.real
end
end

reals.reduce(&:+) / reals.count
end

avgs = []

puts "map then flatten"
avgs << report {
(1..size).
map {|n| [n]}.
flatten
}

puts

puts "flat_map"
avgs << report {
(1..size).
flat_map {|n| [n]}
}

puts avgs
if avgs[0] < avgs[1]
puts "map then flatten faster by #{((1.0 - avgs[0]/avgs[1]) * 100).round(2)} %"
else
puts "flat_map faster by #{((1.0 - avgs[1]/avgs[0]) * 100).round(2)} %"
end

__END__

for each size (10, 100, 1000) showing smallest diff
between map-then-flatten and flat_map in at least
5 runs

size: 10

map then flatten
0.550000 0.000000 0.550000 ( 0.547897)
0.570000 0.000000 0.570000 ( 0.565139)
0.550000 0.000000 0.550000 ( 0.557421)

flat_map
0.320000 0.000000 0.320000 ( 0.316801)
0.320000 0.010000 0.330000 ( 0.325373)
0.330000 0.000000 0.330000 ( 0.325169)

flat_map faster by 42.09 %

**********************************************

size: 100

map then flatten
0.390000 0.000000 0.390000 ( 0.387307)
0.390000 0.000000 0.390000 ( 0.387630)
0.380000 0.000000 0.380000 ( 0.389421)

flat_map
0.250000 0.000000 0.250000 ( 0.259444)
0.270000 0.000000 0.270000 ( 0.261972)
0.250000 0.000000 0.250000 ( 0.252584)

flat_map faster by 33.53 %

**********************************************

size: 1000

map then flatten
0.380000 0.000000 0.380000 ( 0.382788)
0.380000 0.000000 0.380000 ( 0.372447)
0.370000 0.000000 0.370000 ( 0.370065)

flat_map
0.240000 0.000000 0.240000 ( 0.240357)
0.240000 0.000000 0.240000 ( 0.242325)
0.240000 0.000000 0.240000 ( 0.240985)

flat_map faster by 35.69 %
2 changes: 1 addition & 1 deletion lib/rspec/core.rb
Expand Up @@ -11,6 +11,7 @@
require 'set'
require 'time'
require 'rbconfig'
require_rspec['core/flat_map']
require_rspec['core/filter_manager']
require_rspec['core/dsl']
require_rspec['core/extensions/ordered']
Expand Down Expand Up @@ -171,7 +172,6 @@ class << self
def self.path_to_executable
@path_to_executable ||= File.expand_path('../../../exe/rspec', __FILE__)
end

end

MODULES_TO_AUTOLOAD = {
Expand Down
4 changes: 2 additions & 2 deletions lib/rspec/core/configuration.rb
Expand Up @@ -975,10 +975,10 @@ def warnings
private

def get_files_to_run(paths)
paths.map do |path|
FlatMap.flat_map(paths) do |path|
path = path.gsub(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR
File.directory?(path) ? gather_directories(path) : extract_location(path)
end.flatten.sort
end.sort
end

def gather_directories(path)
Expand Down
2 changes: 1 addition & 1 deletion lib/rspec/core/configuration_options.rb
Expand Up @@ -124,7 +124,7 @@ def options_from(path)
def args_from_options_file(path)
return [] unless path && File.exist?(path)
config_string = options_file_as_erb_string(path)
config_string.split(/\n+/).map(&:shellsplit).flatten
FlatMap.flat_map(config_string.split(/\n+/), &:shellsplit)
end

def options_file_as_erb_string(path)
Expand Down
17 changes: 17 additions & 0 deletions lib/rspec/core/flat_map.rb
@@ -0,0 +1,17 @@
module RSpec
module Core
module FlatMap
if [].respond_to?(:flat_map)
def flat_map(array)
array.flat_map { |item| yield item }
end
else
def flat_map(array)
array.map { |item| yield item }.flatten
end
end

module_function :flat_map
end
end
end
6 changes: 3 additions & 3 deletions lib/rspec/core/hooks.rb
Expand Up @@ -448,7 +448,7 @@ def run_hook(hook, scope, example_or_group=ExampleGroup.new, initial_procsy=nil)

# @private
def around_each_hooks_for(example, initial_procsy=nil)
AroundHookCollection.new(parent_groups.map {|a| a.hooks[:around][:each]}.flatten).for(example, initial_procsy)
AroundHookCollection.new(FlatMap.flat_map(parent_groups) {|a| a.hooks[:around][:each]}).for(example, initial_procsy)
end

private
Expand All @@ -472,11 +472,11 @@ def after_all_hooks_for(group)
end

def before_each_hooks_for(example)
HookCollection.new(parent_groups.reverse.map {|a| a.hooks[:before][:each]}.flatten).for(example)
HookCollection.new(FlatMap.flat_map(parent_groups.reverse) {|a| a.hooks[:before][:each]}).for(example)
end

def after_each_hooks_for(example)
HookCollection.new(parent_groups.map {|a| a.hooks[:after][:each]}.flatten).for(example)
HookCollection.new(FlatMap.flat_map(parent_groups) {|a| a.hooks[:after][:each]}).for(example)
end

def register_hook prepend_or_append, hook, *args, &block
Expand Down
2 changes: 1 addition & 1 deletion lib/rspec/core/metadata.rb
Expand Up @@ -159,7 +159,7 @@ def described_class
end

def full_description
build_description_from(*container_stack.reverse.map {|a| a[:description_args]}.flatten)
build_description_from(*FlatMap.flat_map(container_stack.reverse) {|a| a[:description_args]})
end

def container_stack
Expand Down
5 changes: 2 additions & 3 deletions lib/rspec/core/world.rb
Expand Up @@ -47,9 +47,8 @@ def configure_group(group)
end

def example_count
example_groups.collect {|g| g.descendants}.flatten.inject(0) do |sum, g|
sum + g.filtered_examples.size
end
FlatMap.flat_map(example_groups) {|g| g.descendants}.
inject(0) {|sum, g| sum + g.filtered_examples.size}
end

def preceding_declaration_line(filter_line)
Expand Down

0 comments on commit c8a3804

Please sign in to comment.