Ruby: how to dasherize un String without using ActiveSupport - ruby

As the title says, I'd like to dasherize a String by chunks of 3 characters, e.g.:
str = '123456789'
str.dasherize => # '123-456-789'
It should be done outside Rails ActiveSupport or any other gems.

You could do:
'123456789'.scan(/.{1,3}/).join('-')
#=> "123-456-789"
'1234567890'.scan(/.{1,3}/).join('-')
#=> "123-456-789-0"
'12345678901'.scan(/.{1,3}/).join('-')
#=> "123-456-789-01"
This is splitting the string into an Array of 1-3 character chunks, then re-joining them with a hyphen.
You didn't specify how such a method should behave if the string's length is not a multiple of 3, but you could tweak the above approach to get some other result if desired.

One liner:
"123456789".chars.each_slice(3).map(&:join).join('-')
Explanation:
"123456789"
.chars # returns an Enumerator that yields each character
.each_slice(3) # groups the previous iterator into chunks of 3: ['1', '2', '3']
.map(&:join) # join the sub groups into strings: "123", "456"
.join("-") # finally, join the resulting groups with dashes.

Without regular expressions, with step
string = '123456789'
(string.size - 3).step(1, -3) do |i|
string.insert(i, '-')
end
puts string

Related

How to make a repeated string to the left be deleted without using While?

For example, I have this string of only numbers:
0009102
If I convert it to integer Ruby automatically gives me this value:
9102
That's correct. But my program gives me different types of numbers:
2229102 desired output => 9102
9999102 desired output => 102
If you look at them I have treated 2 and 9 as zeros since they are automatically deleted, well, it is easy to delete that with an while but I must avoid it.
In other words, how do you make 'n' on the left be considered a zero for Ruby?
"2229102".sub(/\A(\d)\1*/, "") #=> "9102"`.
The regular expression reads, "match the first digit in the string (\A is the beginning-of-string anchor) in capture group 1 ((\d)), followed by zero or more characters (*) that equal the contents of capture group 1 (\1). String#gsub converts that match to an empty string.
Try with Enumerable#chunk_while:
s = '222910222'
s.each_char.chunk_while(&:==).drop(1).join
#=> "910222"
Where s.each_char.chunk_while(&:==).to_a #=> [["2", "2", "2"], ["9"], ["1"], ["0"], ["2", "2", "2"]]
Similar to the solution of iGian you could also use drop_while.
s = '222910222'
s.each_char.each_cons(2).drop_while { |a, b| a == b }.map(&:last).join
#=> "910222"
# or
s.each_char.drop_while.with_index(-1) { |c, i| i < 0 || c == s[i] }.join
#=> "910222"
You can also try this way:
s = '9999102938'
s.chars.then{ |chars| chars[chars.index(chars.uniq[1])..-1] }.join
=> "102938"

Convert array string objects into hashes?

I have an array:
a = ["us.production => 1", "us.stats => 1", "us.stats.total_active => 1", "us.stats.inactive => 0"]
How can I modify it into a hash object? e.g.:
h = {"us.production" => 1, "us.stats" => 1, "us.stats.total_active" => 1, "us.stats.inactive" => 0}
Thank you,
If pattern you are having is proper and constant, you can try following,
h = a.map { |x| x.split(' => ') }.to_h
# => {"us.production"=>"1", "us.stats"=>"1", "us.stats.total_active"=>"1", "us.stats.inactive"=>"0"}
Instead it is better to use split(/\s*=>\s*/) instead of split(' => ')
You can split every string with String#split and then convert an array of pairs into a hash with Array#to_h:
a = ["us.production => 1", "us.stats => 1", "us.stats.total_active => 1", "us.stats.inactive => 0"]
pairs = a.map{|s| s.split(/\s*=>\s*/)}
# => [["us.production", "1"], ["us.stats", "1"], ["us.stats.total_active", "1"], ["us.stats.inactive", "0"]]
pairs.to_h
# => {"us.production"=>"1", "us.stats"=>"1", "us.stats.total_active"=>"1", "us.stats.inactive"=>"0"}
/\s*=>\s*/ is a regular expression that matches first any number of whitespaces with \s*, then => then again any nuber of whitespaces. As it's a String#split delimiter, this part of string won't be present in a string pair.
The other answers to date are incorrect because they leave the values as strings, whereas the spec is that they be integers. That can be easily corrected. One way is to change s.split(/\s*=>\s*/) in #mrzasa's answer to k,v = s.split(/\s*=>\s*/); [k,v.to_i]. Another way is to tack .transform_values(&:to_i) to the ends of the expressions given in those answers. I expect the authors of those answers either didn't notice that integers were required or intended to leave it as an exercise for the OP to do the (rather uninteresting) conversion.
To make a single pass through the array and avoid the creation of a temporary array and local variables (other than block variables), I suggest using Enumerable#each_with_object (rather than map and to_h), and use regular expressions to extract both keys and values (rather than using String#split):
a = ["us.production => 1", "us.stats=>1", "us.stats.total_active => 1"]
a.each_with_object({}) { |s,h| h[s[/.*[^ ](?= *=>)/]] = s[/\d+\z/].to_i }
#=> {"us.production"=>1, "us.stats"=>1, "us.stats.total_active"=>1}
The first regular expression reads, "match zero or more characters (.*) followed by a character that is not a space ([^ ]), provided that is followed by zero or more spaces (*) followed by the string "=>". (?= *=>) is a positive lookahead.
The second regular expression reads, "match one or more digits (\d+) at the end of the string (the anchor \z). If that string could represent a negative integer, change that regex to /-?\d+\z/ (? makes the minus sign optional).

How to split string into an array into two separate strings

I have the following Ruby on Rails params:
<ActionController::Parameters {"type"=>["abc, def"], "format"=>:json, "controller"=>"order", "action"=>"index"} permitted: false>
I want to check if there's a , in the string, then separate it into two strings like below and update type in params.
<ActionController::Parameters {"type"=>["abc", "def"], "format"=>:json, "controller"=>"order", "action"=>"index"} permitted: false>
I tried to do like below:
params[:type][0].split(",") #=> ["abc", " def"]
but I am not sure why there's a space before the second string.
How can I achieve that?
Because there's a whitespace in your string, that's why the result of using split will also include it in the splitted element for the array.
You could remove first the whitespaces and then use split. Or add ', ' as the split value in order it takes the comma and the space after it. Or depending on the result you're trying to get, to map the resulting elements in the array and remove the whitespaces there, like:
string = 'abc, def'
p string.split ',' # ["abc", " def"]
p string.split ', ' # ["abc", "def"]
p string.delete(' ').split ',' # ["abc", "def"]
p string.split(',').map &:strip # ["abc", "def"]

Scanning through a hash and return a value if true

Based on my hash, I want to match it if it's in the string:
def conv
str = "I only have one, two or maybe sixty"
hash = {:one => 1, :two => 2, :six => 6, :sixty => 60 }
str.match( Regexp.union( hash.keys.to_s ) )
end
puts conv # => <blank>
The above does not work but this only matches "one":
str.match( Regexp.union( hash[0].to_s ) )
Edited:
Any idea how to match "one", "two" and sixty in the string exactly?
If my string has "sixt" it return "6" and that should not happen based on #Cary's answer.
You need to convert each element of hash.keys to a string, rather than converting the array hash.keys to a string, and you should use String#scan rather than String#match. You may also need to play around with the regex until it returns everyhing you want and nothing you don't want.
Let's first look at your example:
str = "I only have one, two or maybe sixty"
hash = {:one => 1, :two => 2, :six => 6, :sixty => 60}
We might consider constructing the regex with word breaks (\b) before and after each word we wish to match:
r0 = Regexp.union(hash.keys.map { |k| /\b#{k.to_s}\b/ })
#=> /(?-mix:\bone\b)|(?-mix:\btwo\b)|(?-mix:\bsix\b)|(?-mix:\bsixty\b)/
str.scan(r0)
#=> ["one", "two", "sixty"]
Without the word breaks, scan would return ["one", "two", "six"], as "sixty" in str would match "six". (Word breaks are zero-width. One before a string requires that the string be preceded by a non-word character or be at the beginning of the string. One after a string requires that the string be followed by a non-word character or be at the end of the string.)
Depending on your requirements, word breaks may not be sufficient or suitable. Suppose, for example (with hash above):
str = "I only have one, two, twenty-one or maybe sixty"
and we do not wish to match "twenty-one". However,
str.scan(r0)
#=> ["one", "two", "one", "sixty"]
One option would be to use a regex that demands that matches be preceded by whitespace or be at the beginning of the string, and be followed by whitespace or be at the end of the string:
r1 = Regexp.union(hash.keys.map { |k| /(?<=^|\s)#{k.to_s}(?=\s|$)/ })
str.scan(r1)
#=> ["sixty"]
(?<=^|\s) is a positive lookbehind; (?=\s|$) is a positive lookahead.
Well, that avoided the match of "twenty-one" (good), but we no longer matched "one" or "two" (bad) because of the comma following each of those words in the string.
Perhaps the solution here is to first remove punctuation, which allows us to then apply either of the above regexes:
str.tr('.,?!:;-','')
#=> "I only have one two twentyone or maybe sixty"
str.tr('.,?!:;-','').scan(r0)
#=> ["one", "two", "sixty"]
str.tr('.,?!:;-','').scan(r1)
#=> ["one", "two", "sixty"]
You may also want to change / at the end of the regex to /i to make the match insensitive to case.1
1 Historical note for readers who want to know why 'a' is called lower case and 'A' is called upper case.

putting enumeration with spaces in rails collection

irb(main):001:0> t = %w{this is a test}
=> ["this", "is", "a", "test"]
irb(main):002:0> t.size
=> 4
irb(main):003:0> t = %w{"this is" a test}
=> ["\"this", "is\"", "a", "test"]
irb(main):004:0> t.size
=> 4
In the end I expected t.size to be 3.
As suggested, each space has to be escaped ...which turns out to be a lot of work. What other options are there? I have a list of about 30 words that I need to put in a collection because I am showing them as checkboxes using simple_form
Why not just use a normal array so no one has to visually parse all the escaping to figure out what's going on? This is pretty clear:
t = [
'this is',
'a',
'test'
]
and the people maintaining your code won't hate you for using %w{} when it isn't appropriate or when they mess things up because they didn't see your escaped whitespace.
You need to escape the space with a '\', like t = %w{this\ is a test} if you dont want that space to be a splitter.
Escape the space using \:
%w{this\ is a test}
You can escape the space %w{this\ is a test} to get ['this is', 'a', 'test'], but in general I wouldn't use %w unless then intention is to split on whitespace.
As others have pointed out use the %w{} construct when spaces are the separator for the words. If you have items that must be quoted and still want to use the construct you can do:
> %w{a test here}.unshift("This is")
=> ["This is", "a", "test", "here"]
require 'csv'
str = '"this is" a test'
p CSV.parse_line(str,{:col_sep=>' '})
#=> ["this is", "a", "test"]

Resources