It appears that using gsub inside a (double quoted) heredoc does not evaluate the result of gsub, as follows:
class Test
def self.define_phone
class_eval <<-EOS
def _phone=(val)
puts val
puts val.gsub(/\D/,'')
end
EOS
end
end
Test.define_phone
test = Test.new
test._phone = '123-456-7890'
# >> 123-456-7890
# >> 123-456-7890
The second puts should have printed 1234567890, just as it would in this case:
'123-456-7890'.gsub(/\D/,'')
# => "1234567890"
What is going on inside the heredoc?
The problem is with the \D in the regex. It will be evaluated when the heredoc is evaluated as a string, which results in D:
"\D" # => "D"
eval("/\D/") #=> /D/
On the other hand, \D inside a single quote will not be evaluated as D:
'\D' # => "\\D"
eval('/\D/') # => /\D/
So wrap the heredoc terminator EOS in a single quote to achieve what you want:
class Test
def self.define_phone
class_eval <<-'EOS'
def _phone=(val)
puts val
puts val.gsub(/\D/,'')
end
EOS
end
end
Test.define_phone
test = Test.new
test._phone = '123-456-7890'
# >> 123-456-7890
# >> 1234567890
Reference
If you run the above code without the wrapped EOS, gsub will try to replace "D" (literally) in the val. See this:
test._phone = '123-D456-D7890DD'
# >> 123-D456-D7890DD
# >> 123-456-7890
Related
Using Ruby 2.4. How do I apply an editing of a stirng to the string itself? I have this method
# Removes the word from teh end of the string
def remove_word_from_end_of_str(str, word)
str[0...(-1 * word.length)]
end
I want the parameter to be operated upon, but it isn't working ...
2.4.0 :001 > str = "abc def"
=> "abc def"
2.4.0 :002 > StringHelper.remove_word_from_end_of_str(str, "def")
=> "abc "
2.4.0 :003 > str
=> "abc def"
I want the string that was passed in to be equal to "abc " but that isn't happening. I don't want to set the variable to the result of the function (e.g. "str = StringHelper.remove(...)"
Ruby already has the String#delete! method that does exactly this:
>> str = 'abc def'
=> "abc def"
>> word = 'def'
=> "def"
>> str.delete!(word)
=> "abc "
>> str
=> "abc "
Note that this will remove all instances of word:
>> str = 'def abc def'
=> "def abc def"
>> str.delete!(word)
=> " abc "
To limit the effect to only the last word, you can do:
>> str = 'def abc def'
=> "def abc def"
>> str.slice!(-word.length..-1)
=> "def"
>> str
=> "def abc "
str[range] is just a shorthand for str.slice(range). You just have to use the destructive method, like that :
# Removes the word from the end of the string
def remove_word_from_end_of_str(str, word)
str.slice!((str.length - word.length)...(str.length))
end
For more information, see the documentation.
If you want your function to return the new string as well, you should use :
# Removes the word from the end of the string
def remove_word_from_end_of_str(str, word)
str.slice!((str.length - word.length)...(str.length))
str
end
Try:
def remove_word_from_end_of_str(str, word)
str.slice!((str.length - word.length)..str.length)
end
Also, your explanation is a little confusing. You are calling the remove_word method as a class method but it is an instance method.
chomp! returns a the String with the given record separator removed from the end of string (if present), and nil if nothing was removed.
def remove_word_from_end_of_str(str, word)
str.chomp!( "CO")
end
str = "Aurora CO"
remove_word_from_end_of_str(str, "CO")
p str #=> "Aurora "
Lets say i want to remove leading space from a string
a = " Hello world"
puts a.gsub!(/^ /,"") # => "Hello World"
But if there is no leading space in the string
b = "Hello World"
puts a.gsub!(/^ /,"") # => nil
Now if I use just gsub instead of gsub it returns the string:
puts b.gsub(/^ /,"") # => "Hello World"
puts a.gsub(/^ /,"") # => "Hello World" (works for both a and b)
So is it possible to get gsub! to perform like gsub as shown above?
the reason is because gsub would create a new object everytime which I would like to avoid because I will be using at least 4 or 5 gsubs on the string I need to manipulate.
thanks
Here are two ways of doing that.
Add || a to the gsub expression
a.gsub!(/\A\s/,"") || a
would give you the desired result, namely removing the space, if present, and returning the string after any change is made. If no change is made, this reduces to nil || a #=> a. If a change is made the expression reduces to a.gsub!(/\A\s/,""). For example,
a = " Hello"
a.gsub!(/\A\s/,"") || a #=> "Hello"
a #=> "Hello"
a ="Hello"
a.gsub!(/\A\s/,"") || a #=> "Hello"
a #=> "Hello"
Although it makes no difference here, I prefer to use the beginning of string anchor, \A, rather than the beginning of line anchor, ^, I also prefer using a whitespace character \s to a space (assuming it couldn't be a tab) to a space, as an inadvertent extra space might not be noticed.
Use String#replace and the non-destructive method String#gsub
a = " Hello"
a.replace(a.gsub(/\A\s/,"")) #=> "Hello"
a #=> "Hello"
a ="Hello"
a.replace(a.gsub(/\A\s/,"")) #=> "Hello"
a #=> "Hello"
Answer:
b.gsub!(/(^ )?/,"") => "Hello World"
Explanation:
(...) Capture everything enclosed
a? Zero or one of a
— via http://rubular.com
Example:
irb(main):008:0> a = " Hello World"
=> " Hello World"
irb(main):009:0> b = "Hello World"
=> "Hello World"
irb(main):010:0> a.gsub!(/(^ )?/,"")
=> "Hello World"
irb(main):011:0> b.gsub!(/(^ )?/,"")
=> "Hello World"
Alternative:
b.gsub!(/^ |/,"") # => "Hello World" using "OR" pipe char
The code below adds a method to the String class that simply calls gsub! and then returns the String object that it was called on. So if the gsub! was "successful" and something has been subbed it returns the subbed string. Otherwise it will return the original string.
class String
def filled_gsub!(oldsubstr, newsubstr)
gsub!(oldsubstr, newsubstr)
self
end
end
Ruby noob here
I understand ruby does pass by reference for function parameters
However, I am getting the feeling this is slightly different from conventional c/c++ style pass by reference
Sample code:
def test1(str)
str += ' World!'
end
def test2(str)
str << ' World!'
end
str = 'Hello'
test1(str)
p str # Hello
test2(str)
p str # Hello World!
I would expect test1 to also return Hello World! if I were using references in c/c++.
This is simply out of curiosity -- any explanations would be appreciated
I understand ruby does pass by reference for function parameters
Ruby is strictly pass-by-value, always. There is no pass-by-reference in Ruby, ever.
This is simply out of curiosity -- any explanations would be appreciated
The simple explanation for why your code snippet doesn't show the result you would expect for pass-by-reference is that Ruby isn't pass-by-reference. It is pass-by-value, and your code snippet proves that.
Here is a small snippet that demonstrates that Ruby is, in fact, pass-by-value and not pass-by-reference:
#!/usr/bin/env ruby
def is_ruby_pass_by_value?(foo)
foo << <<~HERE
More precisely, it is call-by-object-sharing!
Call-by-object-sharing is a special case of pass-by-value,
where the value is always an immutable pointer to a (potentially mutable) value.
HERE
foo = 'No, Ruby is pass-by-reference.'
return
end
bar = ['Yes, of course, Ruby *is* pass-by-value!']
is_ruby_pass_by_value?(bar)
puts bar
# Yes, of course, Ruby *is* pass-by-value!,
# More precisely, it is call-by-object-sharing!
# Call-by-object-sharing is a special case of pass-by-value,
# where the value is always an immutable pointer to a (potentially mutable) value.
Ruby does however allow mutation of objects, it is not a purely functional language like Haskell or Clean.
In the first case a new object was created when you did str += ' World!'
str = "Hello"
=> "Hello"
str.object_id
=> 69867706917360
str += " World"
=> "Hello World"
str.object_id
=> 69867706885680
str = "Hello"
=> "Hello"
str.object_id
=> 69867706856200
str << " World"
=> "Hello World"
str.object_id
=> 69867706856200
str = "Hello"
=> "Hello"
str.object_id
=> 69867706786780
str.freeze
=> "Hello"
str << " World"
RuntimeError: can't modify frozen String
str += " World"
=> "Hello World"
"<<" is a Binary Left Shift Operator. The left operands value is moved left by the number of bits specified by the right operand.
So "<<" doesn't create a new string, str.contact("World") doesn't create a new string as well.
The method test1 doesn't have to do anything with the returned result , you can try this method :
def test1(str)
str.concat(' World!')
end
Look at the following adaption of your test, by showing the object_id of your object you can easily see if it is the same or not. Test1 returns another String object because of the += concatenation but it is not used afterward.
This looks like passed by reference but in reality it is the value of the pointer to the object that is passed. The best explenation I could find for this is here , the author calls it pass-reference-by-value
def test1(str)
p ["in test1 before", str.object_id]
str += ' World!'
p ["in test1 after", str.object_id]
str
end
def test2(str)
p ["in test2", str.object_id]
str << ' World!'
end
str = 'Hello'
p ["in main", str.object_id]
test1(str)
p str # Hello
p ["after test1", str.object_id]
test2(str)
p str
p ["after test2", str.object_id]
Gives
["in main", 12363600]
["in test1 before", 12363600] # the same object, so pointer to object passed by value
["in test1 after", 12362976] # returns a new object, the old is unchanged
"Hello"
["after test1", 12363600] # idem
["in test2", 12363600]
"Hello World!"
["after test2", 12363600]
# still the same object
STRING IS REFERENCED, in ruby except value like numbers, true, false, nil, others are referenced.
a = "hello"
b = a
a.replace("Hola")
p a # Hola
p b # Hola
You would wanna add the magic comment at the beginning.:
# frozen_string_literal: true
a = "hello"
b = a
a.replace("Hola") # can't modify frozen String: "hello" (FrozenError)
def test1(str)
str += ' World!'
end
operator += is a syntactic sugar in ruby. Expression a += b translates to a = a + b. Operator + applied on String instance creates new String, which is a concatenation of the two arguments. That is why str is not modified in the first case.
Also I'd like to correct your statement:
I understand ruby does pass by reference for function parameters
Actually ruby passes by reference every parameter except the "value types" - ie. values nil, true, false and instances of class Fixnum
How do I call a variable from an array? Trying to make this:
hello_world = "Hey"
array = [ '#{hello_world} ho' ]
array.each do |a|
puts a
end
say ["Hey ho"] instead of ["\#{hello_world} ho"].
Do as below -
hello_world = "Hey"
array = [ "#{hello_world} ho" ]
array # => ["Hey ho"]
array.each do |a|
p a
end
# >> "Hey ho"
Single-quoted strings disabling interpolation, but double-quote strings allow interpolation.
Remember - Interpolation may be disabled by escaping the “#” character or using single-quote strings:
'#{1 + 1}' #=> "\#{1 + 1}"
I'm having difficulty reading a file with escaped characters in Ruby...
My text file has the string "First Line\r\nSecond Line" and when I use File.read, I get a string back that escapes my escaped characters: "First Line\r\nSecond Line"
These two strings are not the same things...
1.9.2-p318 :006 > f = File.read("file.txt")
=> "First Line\\r\\nSecond Line"
1.9.2-p318 :007 > f.count('\\')
=> 2
1.9.2-p318 :008 > f = "First Line\r\nSecond Line"
=> "First Line\r\nSecond Line"
1.9.2-p318 :009 > f.count('\\')
=> 0
How can I get the File.read to not escape my escaped characters?
Create a method to remove all the additional escape characters that the File.Read method added, like this:
# Define a method to handle unescaping the escape characters
def unescape_escapes(s)
s = s.gsub("\\\\", "\\") #Backslash
s = s.gsub('\\"', '"') #Double quotes
s = s.gsub("\\'", "\'") #Single quotes
s = s.gsub("\\a", "\a") #Bell/alert
s = s.gsub("\\b", "\b") #Backspace
s = s.gsub("\\r", "\r") #Carriage Return
s = s.gsub("\\n", "\n") #New Line
s = s.gsub("\\s", "\s") #Space
s = s.gsub("\\t", "\t") #Tab
s
end
Then see it in action:
# Create your sample file
f = File.new("file.txt", "w")
f.write("First Line\\r\\nSecond Line")
f.close
# Use the method to solve your problem
f = File.read("file.txt")
puts "BEFORE:", f
puts f.count('\\')
f = unescape_escapes(f)
puts "AFTER:", f
puts f.count('\\')
# Here's a more elaborate use of it
f = File.new("file2.txt", "w")
f.write("He used \\\"Double Quotes\\\".")
f.write("\\nThen a Backslash: \\\\")
f.write('\\nFollowed by \\\'Single Quotes\\\'.')
f.write("\\nHere's a bell/alert: \\a")
f.write("\\nThis is a backspaces\\b.")
f.write("\\nNow we see a\\rcarriage return.")
f.write("\\nWe've seen many\\nnew lines already.")
f.write("\\nHow\\sabout\\ssome\\sspaces?")
f.write("\\nWe'll also see some more:\\n\\ttab\\n\\tcharacters")
f.close
# Read the file without the method
puts "", "BEFORE:"
puts File.read("file2.txt")
# Read the file with the method
puts "", "AFTER:"
puts unescape_escapes(File.read("file2.txt"))
You could just hack them back in.
foo = f.gsub("\r\n", "\\r\\n")
#=> "First Line\\r\\nSecond Line"
foo.count("\\")
#=> 2