- Notifications
You must be signed in to change notification settings - Fork22.1k
AddArray#extract!#33137
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
AddArray#extract!#33137
Uh oh!
There was an error while loading.Please reload this page.
Conversation
rails-bot commentedJun 14, 2018
(@rails-bot has picked a reviewer for you, use r? to override) |
rafaelfranca commentedJun 14, 2018
I'm ok with this one.@dhh what do you think of this name? |
dhh commentedJun 14, 2018 via email
sikachu commentedJun 15, 2018
I honestly thought that this is what |
matthewd commentedJun 15, 2018
FWIW, it would be named
💯. This was very helpful, thank you. On the implementation: I'd like to avoid allocating the second array. What do you think of something like Let's also avoid |
bogdanvlviv commentedJun 15, 2018 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Thanks for the review guys! @matthewd I applied your suggestions inRefactor
I prepared benchmarks in order to ensure that the changes speed up the method: (Not sure that these benchmarks are completely right) beginrequire"bundler/inline"rescueLoadError=>e $stderr.puts"Bundler version 1.10 or later is required. Please updateyour Bundler"raiseeendclassArraydefextract_v1!(&block)unlessblock_given?to_enum(:extract!){size}elseextracted_elements,other_elements=partition(&block)replace(other_elements)extracted_elementsendenddefextract_v2!returnto_enum(:extract!){size}unlessblock_given?extracted_elements=[]reject!do |element|extracted_elements <<elementifyield(element)endextracted_elementsendendgemfile(true)dosource"https://rubygems.org"gem"benchmark-ips"endarrays_for_partition=Array.new(1000){(0..10000).to_a}arrays_for_extract_v1=Array.new(1000){(0..10000).to_a}arrays_for_extract_v2=Array.new(1000){(0..10000).to_a}Benchmark.ipsdo |x|x.report("Array#partition")doarrays_for_partition.eachdo |numbers|odd_numbers,numbers=numbers.partition{ |number|number.odd?}numbersendendx.report("Array#extract_v1!")doarrays_for_extract_v1.eachdo |numbers|odd_numbers=numbers.extract_v1!{ |number|number.odd?}numbersendendx.report("Array#extract_v2!")doarrays_for_extract_v2.eachdo |numbers|odd_numbers=numbers.extract_v2!{ |number|number.odd?}numbersendendx.compare!end The result of the benchmarks: ruby -vruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux] Fetching gem metadata from https://rubygems.org/.Resolving dependencies...Using benchmark-ips 2.7.2Using bundler 1.16.1Warming up -------------------------------------- Array#partition 1.000 i/100ms Array#extract_v1! 1.000 i/100ms Array#extract_v2! 1.000 i/100msCalculating ------------------------------------- Array#partition 1.390 (± 0.0%) i/s - 7.000in 5.044843s Array#extract_v1! 2.781 (± 0.0%) i/s - 14.000in 5.050589s Array#extract_v2! 3.151 (± 0.0%) i/s - 16.000in 5.080608sComparison: Array#extract_v2!: 3.2 i/s Array#extract_v1!: 2.8 i/s - 1.13x slower Array#partition: 1.4 i/s - 2.27x slower
Yeah, It will be more readable and also using of early return would improve |
sikachu commentedJun 22, 2018
I rerun the test case (twice, in the span of 6 days ..ha) and it is now passing.@matthewd@rafaelfranca would either of you mind merging this in if this is good to go? |
0a8e9ef to58abeedCompareThe method removes and returns the elements for which the block returns a true value.If no block is given, an Enumerator is returned instead.```numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]odd_numbers = numbers.extract! { |number| number.odd? } # => [1, 3, 5, 7, 9]numbers # => [0, 2, 4, 6, 8]```Avoid allocating the second array by using `Array#reject!` instead of`Enumerable#partition` in `Array#extract!`.There are benchmarks in order to ensure that the changes speed up the method:```begin require "bundler/inline"rescue LoadError => e $stderr.puts "Bundler version 1.10 or later is required. Please updateyour Bundler" raise eendclass Array def extract_v1!(&block) unless block_given? to_enum(:extract!) { size } else extracted_elements, other_elements = partition(&block) replace(other_elements) extracted_elements end end def extract_v2! return to_enum(:extract!) { size } unless block_given? extracted_elements = [] reject! do |element| extracted_elements << element if yield(element) end extracted_elements endendgemfile(true) do source "https://rubygems.org" gem "benchmark-ips"endarrays_for_partition = Array.new(1000) { (0..10000).to_a }arrays_for_extract_v1 = Array.new(1000) { (0..10000).to_a }arrays_for_extract_v2 = Array.new(1000) { (0..10000).to_a }Benchmark.ips do |x| x.report("Array#partition") do arrays_for_partition.each do |numbers| odd_numbers, numbers = numbers.partition { |number| number.odd? } numbers end end x.report("Array#extract_v1!") do arrays_for_extract_v1.each do |numbers| odd_numbers = numbers.extract_v1! { |number| number.odd? } numbers end end x.report("Array#extract_v2!") do arrays_for_extract_v2.each do |numbers| odd_numbers = numbers.extract_v2! { |number| number.odd? } numbers end end x.compare!end```The result of the benchmarks:```ruby -vruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux]``````Fetching gem metadata fromhttps://rubygems.org/.Resolving dependencies...Using benchmark-ips 2.7.2Using bundler 1.16.1Warming up -------------------------------------- Array#partition 1.000 i/100ms Array#extract_v1! 1.000 i/100ms Array#extract_v2! 1.000 i/100msCalculating ------------------------------------- Array#partition 1.390 (± 0.0%) i/s - 7.000 in 5.044843s Array#extract_v1! 2.781 (± 0.0%) i/s - 14.000 in 5.050589s Array#extract_v2! 3.151 (± 0.0%) i/s - 16.000 in 5.080608sComparison: Array#extract_v2!: 3.2 i/s Array#extract_v1!: 2.8 i/s - 1.13x slower Array#partition: 1.4 i/s - 2.27x slower```Avoid `unless`/`else` in favour of an early return.The double-negative of that `else` can be confusing,even though the code layout is nearly the same.Also using of early return would improve `git diff`if we needed to change this method.58abeed tob71abb3Compare
Uh oh!
There was an error while loading.Please reload this page.
The method removes and returns the elements for which the block returns a true value.
If no block is given, an Enumerator is returned instead.
Also, I added commitUse
Array#extract!where possible in order to show that we can improve Rails code base by this method.Looks more readable if use
Array#extract!, doesn't it?I know we try to omit adding new methods to ActiveSupport, but I think this one has a good chance to be added since we can apply it in Rails code base.
By the way, we already added
Hash#extract!https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/hash/slice.rb#L41-L47
Post:https://gitlab.com/bogdanvlviv/posts/-/issues/14,https://bogdanvlviv.com/posts/ruby/rails/array-extract-to-activesupport-6-0.html
I have tried to add Active Support's
Array#extract!asArray#extractto Ruby, seeFeature #15831: "AddArray#extract,Hash#extract, andENV.extract".Thanks!