Skip to content

Commit de15409

Browse files
committed
Replace usage of Array#? with Array#intersect? for efficiency
`Array#intersect?` was introduced in Ruby 3.1.0 and it's more efficient and useful when the result of the intersection is not needed as the following benchmarks show: ``` require "bundler/inline" gemfile(true) do source "https://rubygems.org" git_source(:github) { |repo| "https://github.com/#{repo}.git" } gem "rails", path: "./" # If you want to test against edge Rails replace the previous line with this: # gem "rails", github: "rails/rails", branch: "main" gem "benchmark-ips" end require "active_support" SCENARIOS = [ [(1..100).to_a, (90..200).to_a], # Case 1 [("a".."m").to_a, ("j".."z").to_a], # Case 2 [(1..100).to_a, (101..200).to_a], # Case 3 ] SCENARIOS.each_with_index do |values, n| puts puts " Case #{n + 1} ".center(80, "=") puts Benchmark.ips do |x| x.report("Array#?") { !(values[0] & values[1]).empty? } x.report("Array#intersect?") { values[0].intersect?(values[1]) } x.compare! end end ``` Results: ``` ==================================== Case 1 ==================================== ruby 3.2.2 (2023-03-30 revision e51014f9c0) [arm64-darwin21] Warming up -------------------------------------- Array#? 34.221k i/100ms Array#intersect? 62.035k i/100ms Calculating ------------------------------------- Array#? 343.119k (± 1.1%) i/s - 1.745M in 5.087078s Array#intersect? 615.394k (± 1.1%) i/s - 3.102M in 5.040838s Comparison: Array#intersect?: 615393.7 i/s Array#?: 343119.4 i/s - 1.79x slower ==================================== Case 2 ==================================== ruby 3.2.2 (2023-03-30 revision e51014f9c0) [arm64-darwin21] Warming up -------------------------------------- Array#? 103.256k i/100ms Array#intersect? 185.104k i/100ms Calculating ------------------------------------- Array#? 1.039M (± 1.3%) i/s - 5.266M in 5.066847s Array#intersect? 1.873M (± 1.6%) i/s - 9.440M in 5.041740s Comparison: Array#intersect?: 1872932.7 i/s Array#?: 1039482.4 i/s - 1.80x slower ==================================== Case 3 ==================================== ruby 3.2.2 (2023-03-30 revision e51014f9c0) [arm64-darwin21] Warming up -------------------------------------- Array#? 37.070k i/100ms Array#intersect? 41.438k i/100ms Calculating ------------------------------------- Array#? 370.902k (± 0.8%) i/s - 1.891M in 5.097584s Array#intersect? 409.902k (± 1.0%) i/s - 2.072M in 5.055185s Comparison: Array#intersect?: 409901.8 i/s Array#?: 370902.3 i/s - 1.11x slower ```
1 parent 1c3ae87 commit de15409

File tree

10 files changed

+11
-11
lines changed

10 files changed

+11
-11
lines changed

actionview/lib/action_view/layouts.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -428,7 +428,7 @@ def _default_layout(lookup_context, formats, require_layout = false)
428428
end
429429

430430
def _include_layout?(options)
431-
(options.keys & [:body, :plain, :html, :inline, :partial]).empty? || options.key?(:layout)
431+
!options.keys.intersect?([:body, :plain, :html, :inline, :partial]) || options.key?(:layout)
432432
end
433433
end
434434
end

actionview/lib/action_view/render_parser/prism_render_parser.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ def render_call_options(node)
8484

8585
# Here we validate that the options have the keys we expect.
8686
keys = options.keys
87-
return if (keys & RENDER_TYPE_KEYS).empty?
87+
return if !keys.intersect?(RENDER_TYPE_KEYS)
8888
return if (keys - ALL_KNOWN_KEYS).any?
8989

9090
# Finally, we can return a valid set of options.

actionview/lib/action_view/rendering.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ def _normalize_options(options)
179179
options[:partial] = action_name
180180
end
181181

182-
if (options.keys & [:partial, :file, :template]).empty?
182+
if !options.keys.intersect?([:partial, :file, :template])
183183
options[:prefixes] ||= _prefixes
184184
end
185185

activemodel/lib/active_model/validations/callbacks.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def set_options_for_callback(options)
101101
options[:on] = Array(options[:on])
102102
options[:if] = [
103103
->(o) {
104-
!(options[:on] & Array(o.validation_context)).empty?
104+
options[:on].intersect?(Array(o.validation_context))
105105
},
106106
*options[:if]
107107
]

activemodel/lib/active_model/validations/comparison.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class ComparisonValidator < EachValidator # :nodoc:
1010
include ResolveValue
1111

1212
def check_validity!
13-
unless (options.keys & COMPARE_CHECKS.keys).any?
13+
unless options.keys.intersect?(COMPARE_CHECKS.keys)
1414
raise ArgumentError, "Expected one of :greater_than, :greater_than_or_equal_to, "\
1515
":equal_to, :less_than, :less_than_or_equal_to, or :other_than option to be supplied."
1616
end

activesupport/lib/active_support/core_ext/module/delegation.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ def delegate(*methods, to: nil, prefix: nil, allow_nil: nil, private: nil, as: n
227227
if method_object
228228
parameters = method_object.parameters
229229

230-
if (parameters.map(&:first) & [:opt, :rest, :keyreq, :key, :keyrest]).any?
230+
if parameters.map(&:first).intersect?([:opt, :rest, :keyreq, :key, :keyrest])
231231
"..."
232232
else
233233
defn = parameters.filter_map { |type, arg| arg if type == :req }

activesupport/lib/active_support/core_ext/string/conversions.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class String
2222
def to_time(form = :local)
2323
parts = Date._parse(self, false)
2424
used_keys = %i(year mon mday hour min sec sec_fraction offset)
25-
return if (parts.keys & used_keys).empty?
25+
return if !parts.keys.intersect?(used_keys)
2626

2727
now = Time.now
2828
time = Time.new(

activesupport/lib/active_support/duration/iso8601_parser.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,12 +102,12 @@ def validate!
102102
raise_parsing_error("is empty duration") if parts.empty?
103103

104104
# Mixing any of Y, M, D with W is invalid.
105-
if parts.key?(:weeks) && (parts.keys & DATE_COMPONENTS).any?
105+
if parts.key?(:weeks) && parts.keys.intersect?(DATE_COMPONENTS)
106106
raise_parsing_error("mixing weeks with other date parts not allowed")
107107
end
108108

109109
# Specifying an empty T part is invalid.
110-
if mode == :time && (parts.keys & TIME_COMPONENTS).empty?
110+
if mode == :time && !parts.keys.intersect?(TIME_COMPONENTS)
111111
raise_parsing_error("time part marker is present but time part is empty")
112112
end
113113

activesupport/lib/active_support/duration/iso8601_serializer.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def normalize
5050
end
5151

5252
def week_mixed_with_date?(parts)
53-
parts.key?(:weeks) && (parts.keys & DATE_COMPONENTS).any?
53+
parts.key?(:weeks) && parts.keys.intersect?(DATE_COMPONENTS)
5454
end
5555

5656
def format_seconds(seconds)

activesupport/lib/active_support/logger.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def self.logger_outputs_to?(logger, *sources)
2323
logdevs = loggers.map { |logger| logger.instance_variable_get(:@logdev) }
2424
logger_sources = logdevs.filter_map { |logdev| logdev.dev if logdev.respond_to?(:dev) }
2525

26-
(sources & logger_sources).any?
26+
sources.intersect?(logger_sources)
2727
end
2828

2929
def initialize(*args, **kwargs)

0 commit comments

Comments
 (0)