How would you convert a string to an array in Ruby?
What I want to do is convert a string like "[value1, value2, value3]" to an array [value1, value2, value3]. Keep in mind some of these values may be strings themselves.
I am trying to write it in a method called str_to_ary.
def str_to_ary
#to_convert = self
#however everything I try beyond this point fails
end
Well, that looks like a JSON.
require 'json'
def str_to_ary
JSON.parse(#to_convert)
end
Note that this is true and works only if those string values in there are between double quotes, not single quotes.
well if you know that [ is always on the first place and ] is always on the last place then you can start with
string = "[X, 1, Test, 22, 3]"
trimmed = string[1,string.length-2]
array = trimmed.split(", ")
array => ["X", " 1", " Test", " 22", " 3"]
if you want to then cast 1, 22 or 3 into Integers then that's a different problem that requires more thought. What values are you expecting to have in the array?
Related
So this may seem odd, and I have done quite a bit of googling, however, I am not really a programmer, (sysops) and trying to figure out how to pass data to the AWS API in the required format, which does seem a little odd.
So, working with resources in AWS, I need to pass tags which are keys and values. The key is a string. The value is a comma separated string, in the first element of an array. So in Ruby terms, looks like this.
{env => ["stage,qa,dev"]}
and not
{env => ["stage","qa","dev"]}
I'm created an admittedly. not a very pretty little app that will allow me to run ssm documents on targeted instances in aws.
I can get the string into an array element using this class I created
class Tags
attr_accessor :tags
def initialize
#tags = {"env" => nil ,"os" => nil ,"group" => nil }
end
def set_values()
puts "please enter value/s for the following keys, using spaces or commas for multiple values"
#tags.each { |key,value|
print "enter #{key} value/s: "
#tags[key] = [gets.strip.chomp]
#tags[key] = Validate.multi_value(tags[key])
}
end
end
I then call this Validate.multi_value passing in the created Array, but it spits an array of my string value back.
class Validate
def self.multi_value(value)
if value.any?{ |sub_string| sub_string.include?(",") || sub_string.include?(" ") }
value = value[0].split(/[,\s]+/)
return value
else
return value
end
end
end
Using pry, I've seen it gets for example ["stage dev qa"] then the if statement does work, then it spits out ["stage","dev","qa"].
and I need it to output ["stage,dev,qa"] but for the life of me, I can't make it work.
I hope that's clear.
If you have any suggestions, I'd be most grateful.
I'm not hugely experienced at ruby and the may be class methods that I've missed.
If your arrays are always coming through in the format ["stage dev qa"] then first we need to split the one string into the parts we want:
arr = ["stage dev qa"]
arr.split(' ')
=> ["stage", "dev", "qa"]
Then we need to join them with the comma:
arr.split(' ').join(',')
=> "stage,dev,qa"
And finally we need to wrap it in an array:
[arr.first.split(' ').join(',')]
=> ["stage,dev,qa"]
All together:
def transform_array(arr)
[arr.first.split(' ').join(',')]
end
transform_array(['stage dev qa'])
=> ['stage,dev,qa']
More info: How do I convert an array of strings into a comma-separated string?
I see no point in creating a class here when a simple method would do.
def set_values
["env", "os", "group"].map do |tag|
puts "Please enter values for #{tag}, using spaces or commas"
print "to separate multiple values: "
gets.strip.gsub(/[ ,]+/, ',')
end
end
Suppose, when asked, the user enters, "stage dev,qa" (for"env"), "OS X" (for"OS") and "Hell's Angels" for "group". Then:
set_values
#=> ["stage,dev,qa", "OS,X", "Hell's,Angels"]
If, as I suspect, you only wish to convert spaces to commas for "env" and not for "os" or "group", write:
def set_values
puts "Please enter values for env, using spaces or commas"
print "to separate multiple values: "
[gets.strip.gsub(/[ ,]+/, ',')] +
["os", "group"].map do |tag|
print "Please enter value for #{tag}: "
gets.strip
end
end
set_values
#=> ["stage,dev,ga", "OS X", "Hell's Angels"]
See Array#map, String#gsub and Array#+.
gets.strip.gsub(/[ ,]+/, ',') merely chains the two operations s = gets.strip and s.gsub(/[ ,]+/, ','). Chaining is commonplace in Ruby.
The regular expression used by gsub reads, "match one or more spaces or commas", [ ,] being a character class, requiring one of the characters in the class be matched, + meaning that one or more of those spaces or commas are to be matched. If the string were "a , b,, c" there would be two matches, " , " and ",, "; gsub would convert both to a single comma.
Using print rather than puts displays the user's entry on the same line as the prompt, immediately after ": ", rather than on the next line. That is of course purely stylistic.
Often one would write gets.chomp rather than gets.strip. Both remove newlines and other whitespace at the end of the string, strip also removes any whitespace at the beginning of the string. strip is probably best in this case.
What do you think about this?, everything gets treated in the Validates method. I don't know if you wanted to remove repeated values, but, just in case I did, so a
"this string,, has too many,,, , spaces"
will become
"this,string,has,too,many,spaces"
and not
"this,,,,string,,,has,too,,many,,,,,,spaces"
Here's the code
class Tags
attr_accessor :tags
# initializes the class (no change)
#
def initialize
#tags = {"env" => nil ,"os" => nil ,"group" => nil }
end
# request and assign the values <- SOME CHANGES
#
def set_values
puts "please enter value/s for the following keys, using spaces or commas for multiple values"
#tags.each do |key,value|
print "enter #{key} value/s: "
#tags[key] = Validate.multi_value( gets )
end
end
end
class Validate
# Sets the array
#
def self.multi_value(value)
# Remove leading spaces, then remove special chars,
# replace all spaces with commas, then remove repetitions
#
[ value.strip.delete("\n","\r","\t","\rn").gsub(" ", ",").squeeze(",") ]
end
end
EDITED, thanks lacostenycoder
I have code that works but am soliciting suggestions for improvement.
I have a file containing ruby hashes:
{"dat"=>"2013-09-01T20:40:00-07:00", "sca"=>"5", "del"=>"755", "dir"=>"S"}
{"dat"=>"2013-09-01T21:00:00-07:00", "sca"=>"5", "del"=>"459", "dir"=>"S"}
that I want to convert to JSON that is both valid and human-readable. This code is compact and produces valid JSON...
#!/usr/bin/env ruby
# expected input: file of hashes, one/line
# output: properly formatted json array
require 'json'
json_array = []
while input = ARGF.gets
input.each_line do |line|
json_array.push( eval(line) )
end
end
print json_array
puts
..but without any newlines is not easily human-readable:
[{"dat"=>"2013-09-01T20:40:00-07:00", "sca"=>"5", "del"=>"755", "dir"=>"S"}, {"dat"=>"2013-09-01T21:00:00-07:00", "sca"=>"5", "del"=>"459", "dir"=>"S"}]
Substituting
puts JSON.pretty_generate(json_array)
for the two output lines above produces valid JSON that is human-readable, but verbose:
[
{
"dat": "2013-09-01T20:40:00-07:00",
"sca": "5",
"del": "755",
"dir": "S"
},
(more lines...)
Better from a human-readbiility standpoint would be to have a "record" on each line:
[
{"dat":"2013-09-01T20:40:00-07:00","sca":"5","del":"755","dir":"S"},
{"dat":"2013-09-01T21:00:00-07:00","sca":"5","del":"459","dir":"S"}
]
But in order to avoid the trailing comma issue [apparently a common problem - see http://trailingcomma.com/ ] I have resorted to an ugly loop with special casing. While it accomplishes the goal, I'm not happy about it and I feel like there must be a simpler way:
#!/usr/bin/env ruby
# expected input: file of hashes, one/line
# output: properly formatted json array
require 'json'
prevHash = ""
currHash = ""
puts "["
while input = ARGF.gets
# in order to to prevent a dangling comma on last element in output json array
# this counter-intuitive loop always outputs the prev, not the current, array elem
# with a trailing comma
input.each_line do |currLine|
currHash = eval(currLine) # convert string to hash
if (prevHash != "") # if not first time thru
puts " " + prevHash.to_json + ","
end
prevHash = currHash
end
end
# then, finally add the last array element *without* the troublesome trailing comma
puts " " + currHash.to_json
puts "]"
Suggestions welcome, particularly those that show me the artful one-liner that I missed.
JSON.pretty_generate accepts an optional hash parameter where you can configure the generator.
A state hash can have the following keys:
indent: a string used to indent levels (default: ”),
space: a string that is put after, a : or , delimiter (default: ”),
space_before: a string that is put before a : pair delimiter (default: ”),
object_nl: a string that is put at the end of a JSON object (default: ”),
array_nl: a string that is put at the end of a JSON array (default: ”),
allow_nan: true if NaN, Infinity, and -Infinity should be generated, otherwise an exception is thrown if these values are encountered. This options defaults to false.
max_nesting: The maximum depth of nesting allowed in the data structures from which JSON is to be generated. Disable depth checking with :max_nesting => false, it defaults to 19.
Playing around with that the closest I could get to your requirement is
JSON.pretty_generate(hash, {object_nl: '', indent: ' '})
which renders to
[
{ "dat": "2013-09-01T20:40:00-07:00", "sca": "5", "del": "755", "dir": "S"},
{ "dat": "2013-09-01T21:00:00-07:00", "sca": "5", "del": "459", "dir": "S"}
]
For an input like 12,34,56;78,91;50,60;
I want to split the string by semi-colon delimit and then those strings split by comma delimit
ex:
puts "Input: "
input = gets.chomp
s_array = input.split(";")
for i in 0..s_array.size
puts s_array[i].split(",")
end
It will successfully print with puts but after I get an error
undefined method 'split' for nil:NilClass <NoMethodError>
Whats the reason for this error?
Change .. for ...
for i in 0...s_array.size
Creating a range with .. is inclusive, while ... is not, e.g.
1..5 # => 1,2,3,4,5
1...5 # => 1,2,3,4
So the variable i overflows the array, in your case if the array size is 5, array_s[5] will be nill.
A more rubyish approach is:
input.split(";").each { |x| puts x.split (",") }
You should use Array#each, it is not rubyish to use for and there are very few cases where for loop is required in place of each in ruby and the for keyword delegates to each even when used.
i'm writing a client to a third-party API, and they provide data in a weird format. At first, it might look like JSON but it's not, and i'm a bit confused about how i should handle that.
It's a key-value based format (much like JSON).
Keys are separated by '=' from their values.
Keys and values are wrapped within double-quotes.
Dictionaries start with '{' and end with '}'.
Arrays start with '('
and end with ')'
Lines end with ';' (Excepted for arrays content) and end-of-line character (\r i think).
Sometimes, there seem to be unicode (Stuff like \U2623 for the BioHazard sign) in strings.
What could possibly be this format? Shall i use a premade gem to parse it, or should i build my own parser?
{ "anArray" = (
"100",
"200",
"300"
);
"aDictionary" = {
"aString" = "Something";
};
}
EDIT This format seems to be Apple's property list, but it's not XML neither Binary... This make sense as the API is from a WebObjects webservice. i will try to use CFPropertyList gem to parse it, if there is a better solution, please let me know.
EDIT 2 This is a NextSTEP Property List.
Here's a robust answer using a custom StringScanner-based parser. It allows whitespace to be optional, allows trailing commas after the last item in a list and allows omitting the semicolon after the last dictionary key/value pair. It allows the outermost item to be an dictionary, array, or string. And it allows really any sort of legal string content, including parens and curly braces and escaped text like \n.
Seen in action:
p parse('{ "array" = ( "1", "2", ( "3", "4" ) ); "hash"={ "key"={ "more"="oh}]yes;!"; }; }; }')
#=> {"array"=>["1", "2", ["3", "4"]], "hash"=>{"key"=>{"more"=>"oh}]yes;!"}}}
puts parse('("Escaped \"Quotes\" Allowed", "And Unicode \u2623 OK")')
#=> Escaped "Quotes" Allowed
#=> And Unicode ☣ OK
The code:
require 'strscan'
def parse(str)
ss, getstr, getary, getdct = StringScanner.new(str)
getvalue = ->{
if ss.scan /\s*\{\s*/ then getdct[]
elsif ss.scan /\s*\(\s*/ then getary[]
elsif str = getstr[] then str
elsif ss.scan /\s*[)}]\s*/ then nil end
}
getstr = ->{
if str=ss.scan(/\s*"(?:[^"\\]|\\u\d+|\\.)*"\s*/i)
eval str.gsub(/([^\\](?:\\\\)*)#(?=[{#$])/,'\1\#')
end
}
getary = ->{
[].tap do |a|
while v=getvalue[]
a << v
ss.scan /\s*,\s*/
end
end
}
getdct = ->{
{}.tap do |h|
while key = getstr[]
ss.scan /\s*=\s*/
if value=getvalue[] then h[key]=value; ss.scan(/\s*;\s*/) end
end
end
end
}
getvalue[]
end
As an alternative to rolling your own parser from scratch in the future, you might also want to look into the Treetop Ruby library.
Edit: I've replaced the implementation of getstr above with one that should prevent running arbitrary Ruby code inside the eval. For more details, see "Eval a string without interpolation". Seen in action:
#secret = "OH NO!"
$secret = "OH NO!"
##secret = "OH NO!"
puts parse('"\"#{:NOT&&:very}\" bad. \u262E\n##secret \\#$secret \\\\###secret"')
Here's a very quick-and-dirty hack that transforms the syntax into valid Ruby and then evals it. Note that this could be dangerous. More importantly, this will convert all parentheses inside keys and values into square brackets.
def parse(str)
eval(
str
.gsub( /" = (?=[({"])/, '" => ' ) # Dictionary separators become =>
.gsub( /(?<=[)}"]); (?=[)}"])/, ', ' ) # Dictionary semicolons become ,
.tr( '()', '[]' ) # ALL parens become square brackets
)
end
p parse('{ "anArray" = ( "100", "200", "300" ); "aDictionary" = { "aString" = "Something"; }; }')
#=> {"anArray"=>["100", "200", "300"], "aDictionary"=>{"aString"=>"Something"}}
Is it possible to write a function that takes a block of strings, does something with the strings, and then returns an array with the strings?
def collect_string(&block)
# just toss them into an array and return it
return ...
end
a = collect_string {
"string 1"
"string 2"
"string 3"
}
When I print out what a is, I should get
["string 1", "string2", "string3"]
Now suppose I decided to change my mind and wanted to do something more with the strings first. Maybe I want to remove all of the vowels first, or just grab the first 3 characters.
This is really not what the blocks are for. You're going to make an array of strings anyway, so why not use an array to begin with?
def collect_string &block
v = block.call
# do something with v
end
# block returning array
a = collect_string {[
"string 1",
"string 2",
"string 3"
]}
If you use a block as in your example then it will return only "string 3", the last expression evaluated. Previous strings are lost.