convert params array of numbers (a string) into an actual integer Array - ruby-on-rails

I am sending an array ("[1,3,44,2,0]") via an Ajax PATCH call, and it arrives as:
Parameters: {"ids"=>"[1,3,44,2,0]"}
To taint check, I am using the following line - in which the match anchors against the start and end of the string, and makes sure that there is at least one digit, or that the numbers are comma separated:
raise "unexpected ids #{params[:ids]}" unless params[:ids].match(/\A\[(\d+,)*\d+\]\z/)
And to make an actual integer array out of it, I am using the following approach (strip the brackets, split on comma, convert each string element to an integer):
irb> "[1,3,44,2,0]"[1...-1].split(',').map {|e| e.to_i}
=> [1, 3, 44, 2, 0]
Is there a better (simpler, cheaper, faster) way of doing this?

Try
JSON.parse(params[:ids])
But I think you should check your Ajax call. It must be possible to pass the array not as a string.

Related

Why flatten gets rid of mapped values?

I am trying to parse a sentence and, at the same time, convert numbers to their digit representation.
As a simple example I want the sentence
three apples
parsed and converted to
3 apples
With this code simple code I can actually parse the sentence correctly and convert three in 3, but when I try to flatten the result, 3 is reverted back to three.
Parser three() => string('three').trim().map((value) => '3');
Parser apples() => string('apples').trim();
Parser sentence = three() & apples();
// this produces Success[1:13]: [3, apples]
print(sentence.parse('three apples'));
// this produces Success[1:13]: three apples
print(sentence.flatten().parse('three apples'));
Am I missing something? Is flatten behavior correct?
Thanks in advance
L
Yes, this is the documented behavior of flatten: It discards the result of the parser, and returns a sub-string of the consumed range in the input being read.
From the question it is not clear what you expect instead?
You might want to use the token parser: sentence.token().parse('three apples'). This will yield a Token object that contains both, the parsed list [3, 'apples'] through Token.value and the consumed input string 'three apples' through Token.input.
Alternatively, you might want to transform the parsed list using a custom mapping function: sentence.map((list) => '${list[0]} ${list[1]}') yields '3 apples'.

Can someone explain why the .split method in Ruby works this way? [duplicate]

This question already has answers here:
Zero-length string being returned from String#split
(2 answers)
Closed 6 years ago.
Why does .split create an empty character when its argument is the first letter of the string, and it doesn't do the same when the argument is the last letter of the string? In the second example, doesn't it "say", since nothing is on my right I'll output "" ? (Is there a 'nil' at the end of the string?)
I know this is not a very relevant question, however, I'd like to understand why the method behaves this way. Thank you!
string = "aware"
string.split("a") --> # outputs: ["", "w", "re"]
string.split("e") --> # outputs: ["awar"]
Below is a simple example of behavioral oddity that String#split may seem to have:
"1234".split(/1/) # => ["", "234"]
It seems like the expected result of the above example would be [“234”] since it is splitting on the 1, but instead we’re getting an unexpected empty string.
**
How String#split works
**
Internally String#split only uses regular expression delimiters. If you pass in a string delimiter it will be escaped for a regular expression and then turned into a regular expression:
1 2 3 4
"1234".split("1") # is really the same as "1234".split( Regexp.new( Regexp.escape("1") ) )
For the remainder of this article when I refer to delimiter I am referring to a regular expression delimiter since internally that is what String#split uses.
String#split keeps track the track of five important pieces of information:
the string itself
a results array which is returned
the position marking where to start matching the string against the
delimiter. This is the start position and is initialized to 0.
the position marking where the string matched the delimiter. This is
the matched position and is initialized to 0.
the position marking the offset immediately following where the
string matched the delimiter
String#split operates in a loop. It continues to match the string against the delimiter until there are no more matches that can be found. It performs the following steps on each iteration:
from the start position match the delimiter against the string
set the matchedposition to where the delimiter matched the string
if the delimiter didn’t match the string then break the loop
create a substring using the start and matched positions of the
string being matched. Push this substring onto the results array
set the start position for the next iteration
With this knowledge let’s discuss how String#split handles the previous example of:
"1234".split(/1/) # => ["", "234"]
the first loop
the start position is initialized to 0
the delimiter is matched against the string “1234”
the first match occurs with the first character, “1” which is at
position 0. This sets the matched position to 0.
a substring is created using the start and matched positions and
pushed onto our result array. This gives us string[start,end] which
translates to “1234”[0,0] which returns an empty string.
the start position is reset to position 1
The second loop
start is now 1
The delimiter is matched against the remainder of our string, “234”
No match is found so the loop is finished.
A substring is created using the start position and remainder of the
string and pushed onto the results array
the results array is returned
Given how String#split works it is easy to see why we have that unexpected empty string in our results array. You should note that this only occurred because the regular expression matched our string at the first character. Below is an example where the delimiter doesn’t match the first character and there is no empty string:
"1234".split(/2/) # => ["1", "34"]
The pickaxe book says of string#split
If the limit parameter is omitted, trailing empty fields are suppressed. ... If negative, there is no limit to the number of fields returned and trailing null [empty] fields are not suppressed. So:
irb(main):001:0> "aware".split('e')
=> ["awar"]
irb(main):002:0> "aware".split('e',-1)
=> ["awar", ""]

Discarding everything in a string variable after the second decimal

I have a Ruby string variable with the value 1.14.2.ab3-4.dl0.rhel
However, I want to discard everything after the second decimal so that I get the value as 1.14
I am using the following command:
str.split(".")[0] but it doesn't seem to work
When you split by . on your string you get:
['1', '14', '2', 'ab3-4', 'dl0', 'rhel']
From this you can get the first two items joined by period:
str.split(".")[0..1].join(".")
# or
str.split(".").first(2).join(".")
With a regexp, you could just look for the first number with 2 decimals :
"1.14.2.ab3-4.dl0.rhel"[/\d+\.\d{2}/]
#=> "1.14"
#maxple's answer only works when the substring of interest is at the beginning of the string. As that was not part of the specification (only in the example), I don't think that's a reasonable assumption. (#Eric did not make that assumption.)
There is also ambiguity about your statement, "discard everything after the second decimal". #maxple interpreted that as after the second decimal point (but also discarded the second decimal point), whereas #Eric assumed it meant after the second decimal digit. This is what happens when questions are imprecise.
If the substring is at the beginning of the string, and you mean to discard the second decimal point and everything after, here are two ways to do that.
str = "1.14.2.ab3-4.dl0.rhel"
1. Modify #Eric's regex:
str[/\A\d+\.\d+/]
#=> "1.14"
2. Convert the string to a float and then back to a string:
str.to_f.to_s
#=> "1.14"
#1 returns nil if the desired substring does not exist, whereas #2 returns "0.0". As long as "0.0" is not a valid substring, either can be used to determine if the substring exists, and if it does, return the substring.
You could also use the partition method in String: https://ruby-doc.org/core-2.2.0/String.html#method-i-partition
"1.14.2.ab3-4.dl0.rhel".partition(/\d+\.\d{2}/)[1]
=> "1.14"

Getting string characters in an array

I have a string in ruby whose initial characters would be numbers and the last character would always be a letter. Some of the examples are: 2C, 1P, 45H, 135D.
I want to get an array which would have 2 objects, first would be the number and second would be the character.
Eg: for 2C, array would be [2, C]
for 45H, array would be [45, H]
for 135D, array would be [135, D]
I tried my_string[/(\d+)([A-Z])$/].split(//, 1), but it gives me an entire string in an array. Like ["2C"], ["45H"]
Am I missing something here?
I had to do some quick Googling to see how to use Ruby's split, but here is how you want to do it:
print '2C'.split(/(?<=\d)(?=[A-Z])/);
// ["2", "C"]
The expression works by doing a lookbehind ((?<=...)) and a lookahead ((?=...)). This means we will match the spot that has a digit to the left and a letter to the right.
You can use scan:
'150D'.scan(/\d+|\w/)
# => ["150", "D"]

How to make Rails 3 JSON parse doubly quoted strings and single number

Background
On the json.org website, a string is defined as "char+" where char+ is one or more char. A char is any unicode character except " or \. A subset of control characters are allowed, just escape them:
"foo" "2" "\\"
In Javascript, if you want to parse a string, it needs to be enclosed:
"\"foo\"" or '"foo"', but not "'foo'"
In Rails 3, the JSON gem that runs C or pure Ruby code is default.
As per the accepted answer, the gem parses JSON documents rather than elements. A document is either a collection in the form of key, value (object/hash) or values (array).
The problem
Strings
Let's say we want to parse the string foo, We would need to enclose it as "\"foo\"" or '"foo"'
JSON.parse("\"foo\"")
JSON.parse('"foo"')
yield
JSON::ParserError unexpected token at '"foo"'
meaning it can't parse "foo"
Numbers
The same goes for numbers: '3' or "3" will yield Needs at least two octets.
Larger numbers ( an octet is a byte, so two utf8 characters are two bytes ): '42' or "42" simply yield the same JSON::ParserError unexpected token at '42'
The workaround
The gem correctly parses these things if they are in an array: '["foo"]' or '[3]'
jsonstring = '"foo"'
JSON.parse( "[#{jsonstring}]" )[0]
yields
"foo"
This is ridiculous. Am I not understanding something correctly? Or is this gem bugged?
json.org states:
JSON is built on two structures:
A collection of name/value pairs. In various languages, this is realized as an object, record, struct, dictionary, hash table, keyed list, or associative array.
An ordered list of values. In most languages, this is realized as an array, vector, list, or sequence.
Since "foo" is neither of the above you are getting the error message. To further concrete it have a look at Ruby JSON Parser, the documenation states:
To create a valid JSON document you have to make sure, that the output is embedded in either a JSON array [] or a JSON object {}. The easiest way to do this, is by putting your values in a Ruby Array or Hash instance.
Hence what you are mentioning as "workaround" is actually the correct way to parse a string using the JSON parser.
Additional Info:
Parsing "\"foo\"" on jsonlint.com and json.parser.online.fr raises an error,
parsing ["foo"] passes the validation test on both the sites.

Resources