-
Notifications
You must be signed in to change notification settings - Fork 21.4k
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make String#remove
and String#remove!
accept multiple arguments
#17383
Conversation
@@ -264,6 +264,11 @@ def test_remove | |||
assert_equal "Summer", "Fast Summer".remove!(/Fast /) | |||
end | |||
|
|||
def test_multiple_remove | |||
assert_equal "This is a good day to die".remove(" to ", /die/), "This is a good day" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
perhaps add coverage to ensure the dup
is used (e.g. original string is not mutated on the remove
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've merely copied the style of existing test, but I guess you're right. Testing that does makes more sense. Will add it.
Behavior looks nice to me, unfortunately implementation is almost 3 times slower now for likely the most common case ( require 'benchmark'
class String
def gsub_remove(pattern)
gsub pattern, ''
end
def union_regex_remove(*patterns)
dup.gsub!(Regexp.union(patterns), '')
end
def non_dup_union_regex_remove(*patterns)
gsub(Regexp.union(patterns), '')
end
def hybrid_regex_remove(*patterns)
pattern = patterns.one? ? patterns.first : Regexp.union(patterns)
dup.gsub!(pattern, '')
end
def non_dup_hybrid_regex_remove(*patterns)
pattern = patterns.one? ? patterns.first : Regexp.union(patterns)
gsub(pattern, '')
end
end
str = 'hello world'
n = 100_000
Benchmark.bm do |x|
x.report("gsub_remove") do
n.times do
str.gsub_remove('world')
end
end
x.report("union_regex_remove") do
n.times do
str.union_regex_remove('world')
end
end
x.report("non_dup_union_regex_remove") do
n.times do
str.non_dup_union_regex_remove('world')
end
end
x.report("hybrid_regex_remove") do
n.times do
str.hybrid_regex_remove('world')
end
end
x.report("non_dup_hybrid_regex_remove") do
n.times do
str.hybrid_regex_remove('world')
end
end
end
Seems the slowness comes due to use of So the below optimization reduces penalty from 170% worse to 40% worse: def remove!(*patterns)
pattern = patterns.one? patterns.first : Regexp.union(patterns)
pattern.gsub!(pattern '')
end Still significantly slower, unfortunately, and also a bit more complex. |
I did some benchmarking and seems like naive # benchmark.rb
require "benchmark/ips"
class String
def remove_original(pattern)
gsub pattern, ""
end
def remove_union(*patterns)
gsub Regexp.union(patterns), ""
end
def remove_each(*patterns)
dup.tap do |copy|
patterns.each do |pattern|
copy.gsub! pattern, ""
end
end
end
def remove_hybrid(*patterns)
patterns.one?? gsub(patterns.first, "") : remove_each(*patterns)
end
end
Benchmark.ips do |x|
x.report "original" do
"This is a good day to die".remove_original("day to die")
end
x.report "union" do
"This is a good day to die".remove_union("day to die")
end
x.report "each" do
"This is a good day to die".remove_each("day to die")
end
x.report "hybrid" do
"This is a good day to die".remove_hybrid("day to die")
end
x.compare!
end Here're the results using Ruby 2.1.3 on my MBA:
|
In two-arguments case Benchmark.ips do |x|
x.report "union" do
"This is a good day to die".remove_union("day ", /to die/)
end
x.report "each" do
"This is a good day to die".remove_each("day ", /to die/)
end
x.report "hybrid" do
"This is a good day to die".remove_hybrid("day ", /to die/)
end
x.compare!
end
|
gsub! pattern, '' | ||
# Alters the string by removing all occurrences of the patterns. | ||
def remove!(*patterns) | ||
return gsub!(patterns.first, "") if patterns.one? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid early return.
if/else is prefered for this case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you use the .each method there should be no performance benefit for checking .one? . Can just use the each.
Eugene
On Oct 25, 2014, at 2:03 PM, Rafael Mendonça França notifications@github.com wrote:
In activesupport/lib/active_support/core_ext/string/filters.rb:
end
Alters the string by removing all occurrences of the pattern. Short-hand for String#gsub!(pattern, '').
- def remove!(pattern)
- gsub! pattern, ''
Alters the string by removing all occurrences of the patterns.
- def remove!(*patterns)
- return gsub!(patterns.first, "") if patterns.one?
Avoid early return.if/else is prefered for this case.
—
Reply to this email directly or view it on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rafaelfranca removed the condition altogether.
Checking for number of arguments was only making require "benchmark/ips"
class String
def remove_original!(pattern)
gsub! pattern, ""
end
def remove_each!(*patterns)
patterns.each do |pattern|
gsub! pattern, ""
end
self
end
def remove_hybrid!(*patterns)
if patterns.one?
gsub! patterns.first, ""
else
patterns.each do |pattern|
gsub! pattern, ""
end
end
self
end
end
Benchmark.ips do |x|
x.report "original" do
"This is a good day to die".remove_original!("day to die")
end
x.report "each" do
"This is a good day to die".remove_each!("day to die")
end
x.report "hybrid" do
"This is a good day to die".remove_hybrid!("day to die")
end
x.compare!
end
|
Hey, is there anything else to do to get this merged? Are there still any problems? /cc @rafaelfranca |
Make `String#remove` and `String#remove!` accept multiple arguments Conflicts: activesupport/CHANGELOG.md
Quite often when I use
String#remove
I need to remove multiple patterns from the string at once. This change makes method handle multiple arguments without breaking backwards compatibility.Before:
After: