Splitting string using whitespace while keeping \n as a separate element - ruby

I am using Ruby and looking for a way to read in a sample string with the following text:
"This is a test
file, dog cat bark
meow woof woof"
and split elements into an array of characters based on whitespace, but to keep the \n value in the array as a separate element.
I know I can use the string.split(/\n/) to get
["this is a test", "file, dog cat bark", "meow woof woof"]
Also string.split(/ /) yields
["this", "is", "a", "test\nfile,", "dog", "cat", "bark\nmeow", "woof", "woof"]
But I am looking for a way to get:
["this", "is", "a", "test", "\n", "file,", "dog", "cat", "bark", "\n", "meow", "woof", "woof"]
Is there any way to accomplish this using Ruby?

It's a strange thing to do but:
string.split /(?=\n)|(?<=\n)| /
#=> ["This", "is", "a", "test", "\n", "file,", "dog", "cat", "bark", "\n", "meow", "woof", "woof"]

You could turn your logic around a bit and look for what you want instead of looking for the delimiters between what you want. A simple scan like this should do the trick:
>> s.scan(/\S+|\n+/)
=> ["This", "is", "a", "test", "\n", "file,", "dog", "cat", "bark", "\n", "meow", "woof", "woof"]
That assumes that repeated \n should be a single token of course.

This isn't particularly elegant, but you could try replacing "\n" with " \n " (note the spaces surrounding \n), and then split the resulting string on / /.

This is an odd request, and perhaps, if you told us WHY you want to do that, we could help you do it in a more straightforward and conventional fashion.
It looks like you're trying to split the words and still know where your original line-ends were. Having the lines split into individual words is useful for many things, but keeping the line-ends... not so much in my experience.
When I'm dealing with text and need to break the lines up for processing, I do it this way:
text = "This is a test
file, dog cat bark
meow woof woof"
data = text.lines.map(&:split)
At this point, data looks like:
[["This", "is", "a", "test"],
["file,", "dog", "cat", "bark"],
["meow", "woof", "woof"]]
I know that each sub-array was a separate line, so if I need to process by lines I can do it using an iterator like each or map, or to reconstruct the original text I can join(" ") the sub-array elements, then join("\n") the resulting lines:
data.map{ |a| a.join(' ') }.join("\n")
=> "This is a test\nfile, dog cat bark\nmeow woof woof"

Related

How to split on a character and keep value

I have a string:
"N8383"
I want to split on the character and maintain it to get:
["N", "8383"]
I tried the following:
"N8383".split(/[A-Z]/)
which gives me:
["", "8383"]
I want to match some more example strings like:
N344 344N S555 555S
String#split is a bad fit for this problem for the reasons others have stated. I would approach it like this, using String#scan instead:
str_parts = "N8383".scan(/[[:alpha:]]+/)
num_parts = "N8383".scan(/[[:digit:]]+/)
This will give you something to work with if the strings contain multiple string parts and/or multiple numeric parts.
This expression:
%w[N344 344N S555 555S].map do |str|
next str.scan(/[[:alpha:]]+/), str.scan(/[[:digit:]]+/)
end
Will return:
[
[["N"], ["344"]],
[["N"], ["344"]],
[["S"], ["555"]],
[["S"], ["555"]]
]
Although you are scanning each string twice, I think it's a better solution than 1. trying to come up with a complex regex that backtracks to return the parts in the right order, or 2. reprocessing the results to put the parts in the right order. Especially if the strings are as short as they are in the examples you've provided. That being said, if scanning each string twice really rankles you, here's another way to do it:
str_parts, num_parts = str.scan(/([[:alpha:]]+)|([[:digit:]]+)/).transpose.each(&:compact!)
Okay given the examples you could use the following regex
/(?=[A-Z])|(?<=[A-Z])/
This will look look ahead (?=) for a single character [A-Z] or look behind (?<=) for a single character [A-Z]. Since these are zero length assertions the split is placed between the characters rather than being the character. e.g.
%w{N8383 N344 344N S555 555S}.map {|s| s.split(/(?=[A-Z])|(?<=[A-Z])/) }
#=> [["N", "8383"], ["N", "344"], ["344", "N"], ["S", "555"], ["555", "S"]]
However this regex is specific to the given cases and does not offer any real deviation from the given cases e.g I have no idea of desired output for "N344S" but right now it will be ["N", "344" ,"S"] and worse yet "NSS344S" will be ["N", "S", "S", "344", "S"]
def doit(str)
str.scan(/\d+|\p{L}+/)
end
doit "N123" #=> ["N", "123"]
doit "123N" #=> ["123", "N"]
doit "N123M" #=> ["N", "123", "M"]
doit "N12M3P" #=> ["N", "12", "M", "3", "P"]
doit "123" #=> ["123"]
doit "NMN" #=> ["NMN"]
doit "" #=> []

ruby regexp to find a word that does not contain digits

I want my regular expression to return an enumerator that would return blocks with words that are not digits, what is the best way I could get that?
I have tried following:
regexp= /(?=\w+)(?=^(?:(?!\d+).)*$)/
"this is a number 1234".split(regexp) # ["this is a number 1234"]
where I expected (?=\w+) should ensure if that is word or not and I expected (?=^(?:(?!\d+).)*$) to ensure it does not contain any digits.
I expected an output:
["this", "is", "a", "number"]
scan is easier than split for this:
regexp = /\b[[:alpha:]]+\b/
p "this is a number 1234".scan(regexp)
# => ["this", "is", "a", "number"]
Try Following.
p "this is a number 1234".scan(/\D+/).first.split(' ')

Partition/split a string by character set in Ruby

How can I separate different character sets in my string? For example, if I had these charsets:
[a-z]
[A-Z]
[0-9]
[\s]
{everything else}
And this input:
thisISaTEST***1234pie
Then I want to separate the different character sets, for example, if I used a newline as the separating character:
this
IS
a
TEST
***
1234
pie
I've tried this regex, with a positive lookahead:
'thisISaTEST***1234pie'.gsub(/(?=[a-z]+|[A-Z]+|[0-9]+|[\s]+)/, "\n")
But apparently the +s aren't being greedy, because I'm getting:
t
h
# (snip)...
S
T***
1
# (snip)...
e
I snipped out the irrelevant parts, but as you can see each character is counting as its own charset, except the {everything else} charset.
How can I do this? It does not necessarily have to be by regex. Splitting them into an array would work too.
The difficult part is to match whatever that does not match the rest of the regex. Forget about that, and think of a way that you can mix the non-matching parts together with the matching parts.
"thisISaTEST***1234pie"
.split(/([a-z]+|[A-Z]+|\d+|\s+)/).reject(&:empty?)
# => ["this", "IS", "a", "TEST", "***", "1234", "pie"]
In the ASCII character set, apart from alphanumerics and space, there are thirty-two "punctuation" characters, which are matched with the property construct \p{punct}.
To split your string into sequences of a single category, you can write
str = 'thisISaTEST***1234pie'
p str.scan(/\G(?:[a-z]+|[A-Z]+|\d+|\s+|[\p{punct}]+)/)
output
["this", "IS", "a", "TEST", "***", "1234", "pie"]
Alternatively, if your string contains characters outside the ASCII set, you could write the whole thing in terms of properties, like this
p str.scan(/\G(?:\p{lower}+|\p{upper}+|\p{digit}+|\p{space}|[^\p{alnum}\p{space}]+)/)
Here a two solutions.
String#scan with a regular expression
str = "thisISa\n TEST*$*1234pie"
r = /[a-z]+|[A-Z]+|\d+|\s+|[^a-zA-Z\d\s]+/
str.scan r
#=> ["this", "IS", "a", "\n ", "TEST", "*$*", "1234", "pie"]
Because of ^ at the beginning of [^a-zA-Z\d\s] that character class matches any character other than letters (lower and upper case), digits and whitespace.
Use Enumerable#slice_when1
First, a helper method:
def type(c)
case c
when /[a-z]/ then 0
when /[A-Z]/ then 1
when /\d/ then 2
when /\s/ then 3
else 4
end
end
For example,
type "f" #=> 0
type "P" #=> 1
type "3" #=> 2
type "\n" #=> 3
type "*" #=> 4
Then
str.each_char.slice_when { |c1,c2| type(c1) != type(c2) }.map(&:join)
#=> ["this", "IS", "a", "TEST", "***", "1234", "pie"]
1. slich_when made its debut in Ruby v2.4.
Non-word, non-space chars can be covered with [^\w\s], so:
"thisISaTEST***1234pie".scan /[a-z]+|[A-Z]+|\d+|\s+|[^\w\s]+/
#=> ["this", "IS", "a", "TEST", "***", "1234", "pie"]

Extract the last word in sentence/string?

I have an array of strings, of different lengths and contents.
Now i'm looking for an easy way to extract the last word from each string, without knowing how long that word is or how long the string is.
something like;
array.each{|string| puts string.fetch(" ", last)
This should work just fine
"my random sentence".split.last # => "sentence"
to exclude punctuation, delete it
"my rando­m sente­nce..,.!?".­split.last­.delete('.­!?,') #=> "sentence"
To get the "last words" as an array from an array you collect
["random sentence...",­ "lorem ipsum!!!"­].collect { |s| s.spl­it.last.delete('.­!?,') } # => ["sentence", "ipsum"]
array_of_strings = ["test 1", "test 2", "test 3"]
array_of_strings.map{|str| str.split.last} #=> ["1","2","3"]
["one two",­ "thre­e four five"­].collect { |s| s.spl­it.last }
=> ["two", "five"]
"a string of words!".match(/(.*\s)*(.+)\Z/)[2] #=> 'words!' catches from the last whitespace on. That would include the punctuation.
To extract that from an array of strings, use it with collect:
["a string of words", "Something to say?", "Try me!"].collect {|s| s.match(/(.*\s)*(.+)\Z/)[2] } #=> ["words", "say?", "me!"]
The problem with all of these solutions is that you only considering spaces for word separation. Using regex you can capture any non-word character as a word separator. Here is what I use:
str = 'Non-space characters, like foo=bar.'
str.split(/\W/).last
# "bar"
This is the simplest way I can think of.
hostname> irb
irb(main):001:0> str = 'This is a string.'
=> "This is a string."
irb(main):002:0> words = str.split(/\s+/).last
=> "string."
irb(main):003:0>

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