Can't get rid of some characters when pushing string to array - ruby-on-rails

I'm creating some kind of custom tags that I'll use later to filter some datas. However, when I add the tags inside an array, I get the following:
"[\"witcher 3\", \"badass\", \"epic\"]"
#tags = []
params[:tags].split(', ').map do |tag|
#tags.push(tag.strip)
end
# About 5 lines under
FileDetail.create!(path: path, creation_date: date, tags: #tags)
Why do these \ show up, and why don't .strip work?
Thank you in advance

You are setting an array of strings in #tag, and \" represents an escaped character, in this case " which is used by ruby to represent String objects.
Consider the following code (an try it on IRB):
foo = ["bar", "baz"]
#=> ["bar", "baz"]
foo.inspect
#=> "[\"bar\", \"baz\"]"
foo.each { |f| puts "tag: #{f}" }
# tag: bar
# tag: baz
As you can see, there is really no \ character to strip from the string, its just how ruby outputs a String representation. So your code doesn't need .strip method:
#tags = []
params[:tags].split(', ').map do |tag|
#tags.push(tag)
end
Not related to your question, but still relevant: split method will return an array, so there is no need to create one before and then push items to it; just assign the returned array to #tags.
For example:
params[:tags] = "witcher 3, badass, epic"
#=> "witcher 3, badass, epic"
#tags = params[:tags].split(', ')
#=> ["witcher 3", "badass", "epic"]
If you want, you can still use map and strip to remove leading and trailing spaces:
params[:tags] = "witcher 3, badass , epic "
#=> "witcher 3, badass , epic "
params[:tags].split(",").map(&:strip)
#=> ["witcher 3", "badass", "epic"]

Related

Parsing a string list with multiple values into JSON

I have about thirty thousand records with a string column that has been stored in the following format, with different keys:
"something: this, this and that, that, other stuff, another: name, another name, last: here"
In rails, I want to change it into a hash like
{
something: [ "this", "this and that", "that" ],
another: [ "name", "another name" ],
last: [ "here" ]
}
Is there a way to do this elegantly? I was thinking of splitting at the colon, then doing a reverse search of the first space.
There are about a hundred ways to solve this. A pretty straightforward one is this:
str = "something: this, this and that, that, other stuff, another: name, another name, last: here"
key = nil
str.scan(/\s*([^,:]+)(:)?\s*/).each_with_object({}) do |(val, colon), hsh|
if colon
key = val.to_sym
hsh[key] = []
else
hsh[key] << val
end
end
# => {
# something: ["this", "this and that", "that", "other stuff"],
# another: ["name", "another name"],
# last: ["here"]
# }
It works by scanning the string with the following regular expression:
/
\s* # any amount of optional whitespace
([^,:]+) # one or more characters that aren't , or : (capture 1)
(:)? # an optional trailing : (capture 2)
\s* # any amount of optional whitespace
/x
Then it iterates over the matches and puts them into a hash. When a match has a trailing colon (capture 2), a new hash key is created with an empty array for a value. Otherwise the value (capture 1) is added to the array for the most recent key.
Or…
A somewhat less straightforward but cleverer approach is to let the RegExp do more work:
MATCH_LIST_ENTRY = /([^:]+):\s*((?:[^,]+(?:,\s*|$))+?)(?=[^:,]+:|$)/
def parse_list2(str)
str.scan(MATCH_LIST_ENTRY).map do |k, vs|
[k.to_sym, vs.split(/,\s*/)]
end.to_h
end
I won't pick apart the RegExp for this one, but it's simpler than it looks. Regexper does a pretty good job of explaining it.
You can see both of these in action on repl.it here: https://repl.it/#jrunning/LongtermMidnightblueAssembler
If str is the string given in the example, the desired hash can be constructed as follows.
str.split(/, *(?=\p{L}+:)/).
each_with_object({}) do |s,h|
k, v = s.split(/: +/)
h[k.to_sym]= v.split(/, */)
end
#=> {:something=>["this", "this and that", "that", "other stuff"],
# :another=>["name", "another name"],
# :last=>["here"]}
Note:
str.split(/, *(?=\p{L}+:)/)
#=> ["something: this, this and that, that, other stuff",
# "another: name, another name",
# "last: here"]
This regular expression reads, "match a comma followed by zero or more spaces, the match to be immediately followed by one or more Unicode letters followed by a colon, (?=\p{L}+:) being a positive lookahead".
elegantly:
result_hash = {}
string.scan(/(?<key>[\w]+(?=:))|(?<value>[\s\w]+(?=(,|\z)))/) do |key,value|
if key.present?
result_hash[key] = []
current_key = key
elsif value.present?
result_hash[current_key] << value.strip
end
end
then jsonize:
json = result_hash.to.json

Rails join array inside map without escaping

I have an array of hashes that I map into a string
Example:
array_of_hashes = [{
:me => 'happy',
:you => 'notsohappy',
:email => [
{"Contact"=>"", "isVerified"=>"1"},
{"Contact"=>"me#example.com", "isVerified"=>"1"},
{"Contact"=>"you#example.com", "isVerified"=>"1"}
]
},{another instance here...}]
Now I want to convert this to a new array that will give me:
["happy", "notsodhappy", "me#example.com", "you#example.com"]
I need to map and reject empty email addresses in the "email" array of hashes.
So far I tried:
array_of_hashes.map{|record| [
record['me'],
record['you'],
record['email'].map { |email| email['Contact']}.reject { |c| c.empty? }.join('", "')
] }
But this returns ["happy", "notsohappy", "me#example.com\", \"you#example.com"]
The quotes are escapes even if I add .html_safe after the .join
In short, it's insisting to keep the joined array a single string. I need it split to separate strings... as many as are in the array.
I need to get rid of these quotes because I am trying to export the array as CSV and so far it's not splitting the email addresses to separate columns.
Suggestions?
array_of_hashes.map do |h|
[h[:me], h[:you]].push(
h[:email].map {|e|e["Contact"]}.reject(&:empty?)
).flatten
end
# => [["happy", "notsohappy", "me#example.com", "you#example.com"], ...]
results = []
array_of_hashes.each do |hash|
single_result = []
single_result << hash[:me]
single_result << hash[:you]
hash[:email].each do |email|
single_result << email["Contact"] if email["Contact"].present?
end
results << single_result
return results
end
This will results : -
2.3.1 :091 > results
=> [["happy", "notsohappy", "me#example.com", "you#example.com"], ["happy", "notsohappy", "me#example.com", "you#example.com"], ["happy", "notsohappy", "me#example.com", "you#example.com"]]

New DateTime instead of String in ruby

I've got some issue with DateTime in Ruby
I've got line which looks like this (it's in .txt file)
DateTime.new(1979,1,1) DateTime.new(2012,3,29)
And my function to get this looks like this
def split_line
array = line.split(' ')
#date_of_birth = array[0]
#date_of_death = array[1]
end
But #date_of_birth and #date_of_death class are String. How can I get them as DateTime?
Assuming your string is in the correct format, then you're probably looking for:
#date_of_birth = array[0].to_datetime
#date_of_death = array[1].to_datetime
See here for more info:
https://apidock.com/rails/String/to_datetime
This:
DateTime.new(1979,1,1) DateTime.new(2012,3,29)
Is not code. What do you expect that to do?
If you want two DateTimes as a space-separated string, do something like:
"#{DateTime.new(1979,1,1)} #{DateTime.new(2012,3,29)}"
When you have something like #{...} inside a set of double quotation marks (they must be double, not single quotation marks), it's called string interpolation. Learn it. Love it. Live it.
But, for the life of me, I don't know why you wouldn't do:
[DateTime.new(1979,1,1), DateTime.new(2012,3,29)]
Which gives you an array, so no split needed. Just:
def split_line
#date_of_birth = array[0]
#date_of_death = array[1]
end
If you want DateTime values, grab the numbers and create them:
require 'date'
'DateTime.new(1979,1,1) DateTime.new(2012,3,29)'.split.map { |s|
DateTime.new(*s.scan(/\d+/).map(&:to_i) )
}
# => [#<DateTime: 1979-01-01T00:00:00+00:00 ((2443875j,0s,0n),+0s,2299161j)>,
# #<DateTime: 2012-03-29T00:00:00+00:00 ((2456016j,0s,0n),+0s,2299161j)>]
The values aren't DateTime though, they're Dates:
'DateTime.new(1979,1,1) DateTime.new(2012,3,29)'.split.map { |s|
Date.new(*s.scan(/\d+/).map(&:to_i) )
}
# => [#<Date: 1979-01-01 ((2443875j,0s,0n),+0s,2299161j)>,
# #<Date: 2012-03-29 ((2456016j,0s,0n),+0s,2299161j)>]
Breaking it down:
'DateTime.new(1979,1,1) DateTime.new(2012,3,29)'.split # => ["DateTime.new(1979,1,1)", "DateTime.new(2012,3,29)"]
.map { |s|
Date.new(
*s.scan(/\d+/) # => ["1979", "1", "1"], ["2012", "3", "29"]
.map(&:to_i) # => [1979, 1, 1], [2012, 3, 29]
)
}
# => [#<Date: 1979-01-01 ((2443875j,0s,0n),+0s,2299161j)>,
# #<Date: 2012-03-29 ((2456016j,0s,0n),+0s,2299161j)>]
* (AKA "splat"), used like this, explodes an array into its elements, which is useful when you have an array but the method only takes separate parameters.
The bigger question is why you're getting values like that in a text file.

How to expand a string in Ruby based on some condition?

I have a string a5bc2cdf3. I want to expand it to aaaaabcbccdfcdfcdf.
In the string is a5, so the resulting string should contain 5 consecutive "a"s, "bc2" results in "bc" appearing 2 times consecutively, and cdf should repeat 3 times.
If input is a5bc2cdf3, and output is aaaaabcbccdfcdfcdf how can I do this in a Ruby method?
def get_character("compressed_string",index)
expanded_string = calculate_expanded_string(compressed_string)
required_char = expanded_string(char_at, index_number(for eg 3))
end
def calculate_expanded_string(compressed_string)
return expanded
end
You may use a regex like
.gsub(/([a-zA-Z]+)(\d+)/){$1*$2.to_i}
See the Ruby online demo
The /([a-zA-Z]+)(\d+)/ will match stubstrings with 1+ letters (([a-zA-Z]+)) and 1+ digits ((\d+)) and will capture them into 2 groups that are later used inside a block to return the string you need.
Note that instead of [a-zA-Z] you might consider using \p{L} that can match any letters.
You want to break out of gsub once the specified index is reached in the original "compressed" string. It is still possible, see this Ruby demo:
s = 'a5bc2cdf3' # input string
index = 5 # break index
result = "" # expanded string
s.gsub!(/([a-zA-Z]+)(\d+)/){ # regex replacement
result << $1*$2.to_i # add to the resulting string
break if Regexp.last_match.end(0) >= index # Break if the current match end index is bigger or equal to index
}
puts result[index] # Show the result
# => b
For brevity, you may replace Regexp.last_match with $~.
I would propose to use scan to move over the compressed string, using a simple RegEx which detects groups of non-decimal characters followed by their count as decimal /([^\d]+)(\d+)/.
def get_character(compressed_string, index)
result = nil
compressed_string.scan(/([^\d]+)(\d+)/).inject(0) do |total_length, (chars, count)|
decoded_string = chars * count.to_i
total_length += decoded_string.length
if index < total_length
result = decoded_string[-(total_length - index)]
break
else
total_length
end
end
result
end
Knowing the current (total) length, one can break out of the loop if the current expanded string includes the requested index. The string is never decoded entirely.
This code gives the following results
get_character("a5bc2cdf3", 5) # => "b"
get_character("a5bc2cdf3", 10) # => "d"
get_character("a5bc2cdf3", 20) # => nil
Just another way. I prefer Wiktor's method by a long way.
def stringy str, index
lets, nums = str.split(/\d+/), str.split(/[a-z]+/)[1..-1].map(&:to_i)
ostr = lets.zip(nums).map { |l,n| l*n }.join
ostr[index]
end
str = 'a5bc2cdf3'
p stringy str, 5 #=> "b"
I'd use:
str = "a5bc2cdf3"
str.split(/(\d+)/).each_slice(2).map { |s, c| s * c.to_i }.join # => "aaaaabcbccdfcdfcdf"
Here's how it breaks down:
str.split(/(\d+)/) # => ["a", "5", "bc", "2", "cdf", "3"]
This works because split will return the value being split on if it's in a regex group: /(\d+)/.
str.split(/(\d+)/).each_slice(2).to_a # => [["a", "5"], ["bc", "2"], ["cdf", "3"]]
The resulting array can be broken into the string to be repeated and its associated count using each_slice(2).
str.split(/(\d+)/).each_slice(2).map { |s, c| s * c.to_i } # => ["aaaaa", "bcbc", "cdfcdfcdf"]
That array of arrays can then be processed in a map that uses String's * to repeat the characters.
And finally join concatenates all the resulting expanded strings back into a single string.

Ruby Array conversion best way

What is the best way to achieve the following, I have following array of actions under ABC
ABC:-
ABC:Actions,
ABC:Actions:ADD-DATA,
ABC:Actions:TRANSFER-DATA,
ABC:Actions:EXPORT,
ABC:Actions:PRINT,
ABC:Detail,
ABC:Detail:OVERVIEW,
ABC:Detail:PRODUCT-DETAIL,
ABC:Detail:EVENT-LOG,
ABC:Detail:ORDERS
I want to format this as:
ABC =>{Actions=> [ADD-DATA,TRANSFER-DATA,EXPORT,PRINT], Detail => [Overview, Product-detail, event-log,orders]}
There's probably a ton of ways to do it but here's one:
a = ["ABC:Actions",
"ABC:Actions:ADD-DATA",
"ABC:Actions:TRANSFER-DATA",
"ABC:Actions:EXPORT",
"ABC:Actions:PRINT",
"ABC:Detail",
"ABC:Detail:OVERVIEW",
"ABC:Detail:PRODUCT-DETAIL",
"ABC:Detail:EVENT-LOG",
"ABC:Detail:ORDERS"]
a.map { |action| action.split(":") }.inject({}) do |m, s|
m[s.at(0)] ||= {}
m[s.at(0)][s.at(1)] ||= [] if s.at(1)
m[s.at(0)][s.at(1)] << s.at(2) if s.at(2)
m
end
The map call returns an array where each of the strings in the original array have been split into an array of elements that were separated by :. For example [["ABC","Actions","ADD-DATA"] ... ]
The inject call then builds up a hash by going through each of these "split" arrays. It creates a mapping for the first element, if one doesn't already exist, to an empty hash, e.g. "ABC" => {}. Then it creates a mapping in that hash for the second element, if one doesn't already exist, to an empty array, e.g. "ABC" => { "Detail" => [] }. Then it adds the third element to that array to give something like "ABC" => { "Detail" => ["OVERVIEW"] }. Then it goes onto the next "split" array and adds that to the hash too in the same way.
I will do this as below :
a = ["ABC:Actions",
"ABC:Actions:ADD-DATA",
"ABC:Actions:TRANSFER-DATA",
"ABC:Actions:EXPORT",
"ABC:Actions:PRINT",
"ABC:Detail",
"ABC:Detail:OVERVIEW",
"ABC:Detail:PRODUCT-DETAIL",
"ABC:Detail:EVENT-LOG",
"ABC:Detail:ORDERS"]
m = a.map{|i| i.split(":")[1..-1]}
# => [["Actions"],
# ["Actions", "ADD-DATA"],
# ["Actions", "TRANSFER-DATA"],
# ["Actions", "EXPORT"],
# ["Actions", "PRINT"],
# ["Detail"],
# ["Detail", "OVERVIEW"],
# ["Detail", "PRODUCT-DETAIL"],
# ["Detail", "EVENT-LOG"],
# ["Detail", "ORDERS"]]
m.each_with_object(Hash.new([])){|(i,j),ob| ob[i] = ob[i] + [j] unless j.nil? }
# => {"Actions"=>["ADD-DATA", "TRANSFER-DATA", "EXPORT", "PRINT"],
# "Detail"=>["OVERVIEW", "PRODUCT-DETAIL", "EVENT-LOG", "ORDERS"]}
It was just interesting to do it with group_by :)
a = ['ABC:Actions',
'ABC:Actions:ADD-DATA',
'ABC:Actions:TRANSFER-DATA',
'ABC:Actions:EXPORT',
'ABC:Actions:PRINT',
'ABC:Detail',
'ABC:Detail:OVERVIEW',
'ABC:Detail:PRODUCT-DETAIL',
'ABC:Detail:EVENT-LOG',
'ABC:Detail:ORDERS']
result = a.map { |action| action.split(":") }.group_by(&:shift)
result.each do |k1,v1|
result[k1] = v1.group_by(&:shift)
result[k1].each { |k2,v2| result[k1][k2] = v2.flatten }
end
p result
{"ABC"=>{"Actions"=>["ADD-DATA", "TRANSFER-DATA", "EXPORT", "PRINT"], "Detail"=>["OVERVIEW", "PRODUCT-DETAIL", "EVENT-LOG", "ORDERS"]}}

Resources