I have loaded a string with #{variable} references in it. How would I resolve those variables in the string like puts does?
name="jim"
str="Hi #{name}"
puts str
Instead of puts, I would like to have the result available to pass as a parameter or save into a variable.
you could eval it
name = "Patrick"
s = 'hello, #{name}'
s # => "hello, \#{name}"
# wrap the string in double quotes, making it a valid interpolatable ruby string
eval "\"#{s}\"" # => "hello, Patrick"
puts doesn't resolve the variables. The Ruby parser does when it creates the string. if you passed str to any other method, it would be the same as passing 'Hi jim', since the interpolation is already done.
String has a format option that appears as %. It can be used to pass arguments into a predefined string much like interpolation does.
message = "Hello, %s"
for_patrick = message % "Patrick" #=> "Hello, Patrick"
for_jessie = message % "Jessie" #=> "Hello, Jessie"
messages = "Hello, %s and %s"
for_p_and_j = messages % ["Patrick", "Jessie"] #=> "Hello, Patrick and Jessie"
It may not look "Rubyish" but I believe it is the functionality you are looking for.
So, if you have a string coming in from somewhere that contains these placeholders, you can then pass in values as arguments as so:
method_that_gets_hello_message % "Patrick"
This will also allow you to only accept values you are expecting.
message = "I can count to %d"
message % "eleven" #=> ArgumentError: invalid value for Integer()
There's a list on Wikipedia for possible placeholders for printf() that should also work in Ruby.
The eval seems to be the only solution for this particular task. But we can avoid this dirty-unsafe-dishonourable eval if we modify the task a bit: we can resolve not local, but instance variable without eval using instance_variable_get:
#name = "Patrick"
#id = 2 # Test that number is ok
#a_b = "oooo" # Test that our regex can eat underscores
s = 'hello, #{name} !!#{id} ??#{a_b}'
s.gsub(/#\{(\w+)\}/) { instance_variable_get '#'+$1 }
=> "hello, Patrick !!2 ??oooo"
In this case you even can use any other characters instead of #{} (for example, %name% etc), by only modifying the regex a bit.
But of course, all this smells.
It sounds like you want the basis for a template system, which Ruby does easily if you use String's gsub or sub methods.
replacements = { '%greeting%' => 'Hello', '%name%' => 'Jim' }
pattern = Regexp.union(replacements.keys)
'%greeting% %name%!'.gsub(pattern, replacements)
=> "Hello Jim!"
You could just as easily define the key as:
replacements = { '#{name}' => 'Jim' }
and use Ruby's normal string interpolation #{...} but I'd recommend not reusing that. Instead use something unique.
The advantage to this is the target => replacement map can easily be put into a YAML file, or a database table, and then you can swap them out with other languages, or different user information. The sky is the limit.
The benefit to this also, is there is no evaluation involved, it's only string substitution. With a bit of creative use you can actually implement macros:
macros = { '%salutation%' => '%greeting% %name%' }
replacements = { '%greeting%' => 'Hello', '%name%' => 'Jim' }
macro_pattern, replacement_pattern = [macros, replacements].map{ |h| Regexp.union(h.keys) }
'%salutation%!'.gsub(macro_pattern, macros).gsub(replacement_pattern, replacements)
=> "Hello Jim!"
Related
I was recently asked this in an interview and was figuring out a way to do this without using regex in Ruby as I was told it would be a bonus if you can solve it without using regex.
Question: Assume that the hash has 1 million key, value pairs and we have to be able to sub the variables in the string that are between % % this pattern. How would I be able to do this without regex.
We have a string str = "%greet%! Hi there, %var_1% that can be any other %var_2% injected to the %var_3%. Nice!, goodbye)"
we have a hash called dict = { greet: 'Hi there', var_1: 'FIRST VARIABLE', var_2: 'values', var_3: 'string', }
This was my solution:
def template(str, dict)
vars = value.scan(/%(.*?)%/).flatten
vars.each do |var|
value = value.gsub("%#{var}%", dict[var.to_sym])
end
value
end
There are many ways to solve this, but you will probably need some kind of parsing and / or lexical analysis if you don't want to use built-in pattern matching.
Let's keep it very simple and say that your string's content falls into two categories: text and variable which are separated by %, e.g. (you could also think of the variables being enclosed by %, but that's harder to implement)
str = "Hello %name%, hope to see you %when%!"
# TTTTTT VVVV TTTTTTTTTTTTTTTTTT VVVV T
As you can see, the categories are alternating. We can utilize this and write a little helper method that turns a string into a list of [type, value] pairs, something like this:
def each_part(str)
return enum_for(__method__, str) unless block_given?
type = [:text, :var].cycle
buf = ''
str.each_char do |char|
if char != '%'
buf << char
else
yield type.next, buf
buf = ''
end
end
yield type.next, buf
end
It starts by defining an enumerator that will cycle between the two types and an empty buffer. It will then read each_char from the string. If the char is not %, it will just append it to the buffer and keep reading. Once it encounters a %, it will yield the current buffer along with the type and start a new buffer (next will also switch the type). After the loop ends, it will yield once more to output the remaining characters.
It outputs this kind of data:
each_part(str).to_a
#=> [[:text, "Hello "],
# [:var, "name"],
# [:text, ", hope to see you "],
# [:var, "when"],
# [:text, "!"]]
We can use this to convert the string:
dict = { name: 'Tom', when: 'soon' }
output = ''
each_part(str) do |type, value|
case type
when :text
output << value
when :var
output << dict[value.to_sym]
end
end
p output
#=> "Hello Tom, hope to see you soon!"
You could of course combine parsing and evaluation, but I like the separation. An full-fledged parser might involve even more steps.
A very simple approach:
First, split the string on '%':
str = "%greet%! Hi there, %var_1% that can be any other %var_2% injected to the %var_3%. Nice!, goodbye)"
chunks = str.split('%')
Now we can assume given the way the problem has been specified, that every other "chunk" will be a key to replace. Iterating with the index will make that easier to figure out.
chunks.each_with_index { |c, i| chunks[i] = (i.even? ? c : dict[c.to_sym]) }.join
Result:
"Hi there! Hi there, FIRST VARIABLE that can be any other values injected to the string. Nice!, goodbye)"
Note: this does not handle malformed input well at all.
With this code:
input = gets.chomp.downcase!
puts input
if there is at least one uppercase letter in the input, the input will be put on screen, freed of its uppercases. But if the input has no uppercase letter, it will put nil, like if nothing was written.
I want my input to be fully downcased; if it is a string with no uppercase letter, it should return the same string.
I thought about something like this:
input = gets.chomp
if input.include(uppercase) then input.downcase! end
But this doesn't work. I hope someone has an idea on how I should do this.
According to the docs for String:
(emphasis is mine added)
downcase
Returns a copy of str with all uppercase letters replaced with their lowercase counterparts. The operation is locale
insensitive—only characters “A” to “Z” are affected. Note: case
replacement is effective only in ASCII region.
downcase!
Downcases the contents of str, returning nil if no changes were made. Note: case replacement is effective only in ASCII
region.
Basically it says that downcase! (with exclamation mark) will return nil if there is no uppercase letters.
To fix your program:
input = gets.chomp.downcase
puts input
Hope that helped!
This will work:
input = gets.chomp.downcase
puts input
String#downcase
Returns a modified string and leaves the original unmodified.
str = "Hello world!"
str.downcase # => "hello world!"
str # => "Hello world!"
String#downcase!
Modifies the original string, returns nil if no changes were made or returns the new string if a change was made.
str = "Hello world!"
str.downcase! # => "hello world!"
str # => "hello world!"
str.downcase! # => nil
! (bang) methods
It's common for Ruby methods with ! / non-! variants to behave in a similar manner. See this post for an in-depth explanation why.
The reason that downcase! returns nil is so you know whether or not the object was changed. If you're assigning the modified string to another variable, like you are here, you should use downcase instead (without the bang !).
If you're not familiar, the standard library bang methods typically act on the receiver directly. That means this:
foo = "Hello"
foo.downcase!
foo #=> "hello"
Versus this:
foo = "Hello"
bar = foo.downcase
foo #=> "Hello"
bar #=> "hello"
What's the easiest way in Ruby to interchange a part of a string with another value. Let's say that I have an email, and I want to check it on two domains, but I don't know which one I'll get as an input. The app I'm building should work with #gmail.com and #googlemail.com domains.
Example:
swap_string 'user#gmail.com' # >>user#googlemail.com
swap_string 'user#googlemail.com' # >>user#gmail.com
If you're looking to substitute a part of a string with something else, gsub works quite well.
Link to Gsub docs
It lets you match a part of a string with regex, and then substitute just that part with another string. Naturally, in place of regex, you can just use a specific string.
Example:
"user#gmail.com".gsub(/#gmail/, '#googlemail')
is equal to
user#googlemail.com
In my example I used #gmail and #googlemail instead of just gmail and googlemail. The reason for this is to make sure it's not an account with gmail in the name. It's unlikely, but could happen.
Don't match the .com either, as that can change depending on where the user's email is.
Assuming googlemail.com and gmail.com are the only two possibilities, you can use sub to replace a pattern with given replacement:
def swap_string(str)
if str =~ /gmail.com$/
str.sub("gmail.com","googlemail.com")
else
str.sub("googlemail.com","gmail.com")
end
end
swap_string 'user#gmail.com'
# => "user#googlemail.com"
swap_string 'user#googlemail.com'
# => "user#gmail.com"
You can try with Ruby gsub :
eg:
"user#gmail.com".gsub("gmail.com","googlemail.com");
As per your need of passing a string parameter in a function this should do:
def swap_mails(str)
if str =~ /gmail.com$/
str.sub('gmail.com','googlemail.com');
else
str.sub('googlemail.com','gmail.com');
end
end
swap_mails "vgmail#gmail.com" //vgmail#googlemail.com
swap_mails "vgmail#googlemail.com" ////vgmail#gmail.com
My addition :
def swap_domain str
str[/.+#/] + [ 'gmail.com', 'googlemail.com' ].detect do |d|
d != str.split('#')[1]
end
end
swap_domain 'user#gmail.com'
#=> user#googlemail.com
swap_domain 'user#googlemail.com'
#=> user#gmail.com
And this is bad code, imo.
String has a neat trick up it's sleeve in the form of String#[]:
def swap_string(string, lookups = {})
string.tap do |s|
lookups.each { |find, replace| s[find] = replace and break if s[find] }
end
end
# Example Usage
lookups = {"googlemail.com"=>"gmail.com", "gmail.com"=>"googlemail.com"}
swap_string("user#gmail.com", lookups) # => user#googlemail.com
swap_string("user#googlemail.com", lookups) # => user#gmail.com
Allowing lookups to be passed to your method makes it more reusable but you could just as easily have that hash inside of the method itself.
I have many strings following a certain pattern:
string = "Hello, #name. You did #thing." # example
Basically, my strings are a description where #word is dynamically. I need to replace each with a value at runtime.
string = "Hello, #{#name}. You did #{#thing}." # Is not an option!
The #word is basically a variable, but I just cannot use the method above.
How should I do that?
Instead doing search/replace, you can use Kernel#sprintf method, or its % shorthand. Combined with Hashes, it can come pretty handy:
'Hello, %{who}. You did %{what}' % {:who => 'Sal', :what => 'wrong'}
# => "Hello, Sal. You did wrong"
The advantage of using Hash instead of Array is that you don't have to worry about the ordering, and you can have the same value inserted on multiple places in the string.
You can format your string with placeholders that can be switched out dynamically using String's % operator.
string = "Hello, %s. You did %s"
puts string % ["Tony", "something awesome"]
puts string % ["Ronald", "nothing"]
#=> 'Hello, Tony. You did something awesome'
#=> 'Hello, Ronald. You did nothing'
Possible use case: Let's say you were writing a script that would be taking the name and action in as parameters.
puts "Hello, %s. You did %s" % ARGV
Assuming 'tony' and 'nothing' were the first two parameters, you would get 'Hello, Tony. You did nothing'.
I have a Ruby script that used string interpolation to build error messages.
p "#{vName} is not a defined variable" => 'xxx is not a defined variable'
Another programmer came through and attempted to externalize the string literals to a separate configuration file. Of course, he doesn't get the substitution.
p err_string_from_config => '#{vName} is not a defined variable'
I've looked around, but couldn't come up with anything better than converting to sprintf strings and using printf.
Does anybody know how to get the #{} substitution to work on strings that are not double quote literals within the Ruby script?
Actually Ruby has functionality very similar to John's Python example:
$ irb
>> greeting = 'hello %s, my name is %s!'
>> interpolated = greeting % ['Mike', 'John']
=> "hello Mike, my name is John!"
>>
This is also useful if your argument is an array constant. If you must use #{} style interpolation you could use eval:
>> greeting = 'hi #{name}' # notice name is not defined yet
>> name = "mike"
>> eval '"' + greeting + '"'
The eval approach is going to be much slower than using % style interpolation, so it's a trade-off.
I suggest that you look at Liquid templating language which provides more powerful features (e.g. you can reference parameters by name).
Previous example would look like:
greeting = Liquid::Template.parse("hello {{your_name}}, my name is {{my_name}}!")
interpolated = greeting.render('your_name' => 'Mike', 'my_name' => 'John')
# => "hello Mike, my name is John!"
Here's how I do it, just for the record. A bit clearer imo.
gief = '#{later} please'
later = "later"
puts eval(%Q["#{gief}"])
# => later please
But honestly, that is such a hack. Unless you have a really good reason to use a string, use a proc instead. I always try to use plain ruby instead of evaling strings.
gief = Proc.new {|l| "#{l} please" }
later = "later"
puts gief.call(later)
# => later please