Flatten an array of strings in Ruby - ruby

What's the best idiomatic (cleanest) way to convert an array of strings into a string, while keeping the enclosing quotes for each elements.
In other words, from this:
a = ["file 1.txt", "file 2.txt", "file 3.txt"]
I'd need to get this
"'file 1.txt' 'file 2.txt' 'file 3.txt'"
Single and double quotes could be interchanged here.
The best ways I know of is by using map and inject/reduce.
eg: a.map{|dir| "'" + dir + "'"}.join(' ')
eg2: a.reduce("'"){|acc, dir| acc += dir+"' "}
Performance could be improved by avoiding temp string creation (+ operator). That's not my main question though. Is there a cleaner more concise way to achieve the same result?

Shorter doesn't always mean simpler. Your first example was succinct, readable, and easily changeable, without being unnecessarily complex.
a.map { |s| "'#{s}'" }.join(' ')

Try out
"'#{a.join("' '")}'"
Or if golfing
?'+a*"' '"+?'

Try this:
"'" + a.join("' '") + "'"

"'"+a*"' '"+"'"
or
"'#{a*"' '"}'"
or
a.to_s[1...-1].gsub /",?/,"'"

Related

ruby: workarounds for nested string interpolation

In the string
"#{x ? (x.to_s + ' is ') : ''}ok", Rubocop's Style/StringConcatenation suggests avoiding the +.
But that requires a nested string interpolation
"#{x ? '#{x.to_s} is ' : ''}ok)",
which at least in Ruby 2.7 is not expanded: #{x.to_s} is treated like any other literal.
Is the + version alright because it's on the fringes of what a style guide could cover, or must one introduce a temporary variable?
tmp = x ? '#{x.to_s} is ' : ''
"#{tmp}ok"
Context: the string is sent to a logfile. ok is actually a long list of details. x is worth logging, but only when it exists.
Yes, a variable will make this more readable (imo):
prefix = "#{x} is " if x
"#{prefix}ok"
(this relies on the fact that nil#to_s == '')
Given that "ok" is actually:(according to the comments)
"...a long string that has even more interpolations. Duplicating that string isn't DRY".
I would go with
ok = generate_some_long_string()
ok.prepend("#{x} is ") if x
ok
This does mutate ok but based on my understanding of the question this may actually be desirable.
Nesting Interpolation
As an aside and I would not recommend it (because it is difficult to read) but nesting interpolation is completely valid ruby e.g.
x="val"
"#{x ? "#{x} is " : ""}ok"
#=> "val is ok"
This works because what is inside the interpolation closure is treated like any other ruby code. The inner double quotes open and close a new String rather than closing the first and opening another because the interpolation closure is waiting for a closing curly brace. You could technically do this at any depth.
"#{x ? "#{"the #{y = x}ue of"} #{x} is " : ""}#{y.inspect}"
#=> "the value of val is \"val\""

Change string to hash with ruby

I have ugly string that looks like this:
"\"New\"=>\"0\""
Which will be the best way to converting it into hash object?
Problem with "\"New\"=>\"0\"" is it does not look like a Hash. So first step should be to manipulate it to look like a Hash:
"{" + a + "}"
# => "{\"New\"=>\"0\"}"
Now once you have a hash looking string you can convert it into Hash like this:
eval "{" + a + "}"
# => {"New"=>"0"}
However there is still one issue, eval is not safe and inadvisable to use. So lets manipulate the string further to make it look json-like and use JSON.parse:
require `json`
JSON.parse ("{" + a + "}").gsub("=>",":")
# => {"New"=>"0"}
How about JSON.parse(string.gsub("=>", ":"))
You can use regex to pull out the key and value. Then create Hash directly
Hash[*"\"New\"=>\"0\"".scan(/".*?"/)]
Hard to nail down the best way if you can't tell us exactly the general format of those strings. You may not even need the regex. eg
Hash[*"\"New\"=>\"0\"".split('"').values_at(1,3)]
Also works for "\"Rocket\"=>\"=>\""

What is the best way to get 51, out of the following string

What is the best way to get '51', out of the following string in ruby :
"<https://api.example.com/users/lgs/api?page=2>; rel=\"next\", <https://api.example.com/users/lgs/api?page=51>; rel=\"last\""
Thanks in advance
Luca
If you know there're only two numbers in that string then this is enough:
str = '"<https://api.example.com/users/lgs/api?page=2>; rel=\"next\", <https://api.example.com/users/lgs/api?page=51>; rel=\"last\""'
p str.scan(/\d+/).last #=> "51"
If not then provide more detail to make the regex more precise. Also you can add to_i if you need the answer as a number.
You can use regexp in this way:
res = str.match /.page=(\d+)./
in this way you are "capturing all the digits between "(" and ")" (in the last token), and you result will be store in
res.captures.first
(or simply in $1 variable)

Regular expression help

I am currently doing a bunch of processing on a string using regular expressions with gsub() but I'm chaining them quite heavily which is starting to get messy. Can you help me construct a single regex for the following:
string.gsub(/\.com/,'').gsub(/\./,'').gsub(/&/,'and').gsub(' ','-').gsub("'",'').gsub(",",'').gsub(":",'').gsub("#39;",'').gsub("*",'').gsub("amp;",'')
Basically the above removes the following:
.com
.
,
:
*
switches '&' for 'and'
switches ' ' for '-'
switches ' for ''
Is there an easier way to do this?
You can combine the ones that remove characters:
string.gsub(/\.com|[.,:*]/,'')
The pipe | means "or". The right side of the or is a character class; it means "one of these characters".
A translation table is more scalable as you add more options:
translations = Hash.new
translations['.com'] = ''
translations['&'] = 'and'
...
translations.each{ |from, to| string.gsub from, to }
Building on Tim's answer:
You can pass a block to String.gsub, so you could combine them all, if you wanted:
string.gsub(/\.com|[.,:*& ']/) do |sub|
case(sub)
when '&'
'and'
when ' '
'-'
else
''
end
end
Or, building off echoback's answer, you could use a translation hash in the block (you may need to call translations.default = '' to get this working):
string.gsub(/\.com|[.,:*& ']/) {|sub| translations[sub]}
The biggest perk of using a block is only having one call to gsub (not the fastest function ever).
Hope this helps!

How do I parse a quoted string inside another string?

I want to extract the quoted substrings from inside a string. This is an example:
string = 'aaaa' + string_var_x + 'bbbb' + string_var_y
The output after parsing should be:
["'aaaa'", "'bbbb'"]
The initial solution was to string.scan /'\w'/ which is almost ok.
Still I can't get it working on more complex string, as it's implied that inside '...' there can be any kind of characters (including numbers, and !##$%^&*() whatever).
Any ideas?
I wonder if there's some way to make /'.*'/ working, but make it less greedy?
Lazy should fix this:
/'.*?'/
Another possibility is to use this:
/'[^']*'/
An alternate way to do it is:
>> %{string = 'aaaa' + string_var_x + 'bbbb' + string_var_y}.scan(/'[^'].+?'/)
#=> ["'aaaa'", "'bbbb'"]
String.scan gets overlooked a lot.

Resources