Ruby split on numbers vs letters - ruby

I'd like to split the following string on letters:
1234B
There are always only ever 4 digits and one letter. I just want to split those out.
Here is my attempt, I think I have the method right and the regex matches the number but I dont think my syntax or my regex is pertinent to the problem Im attempting to solve.
"1234A".split(/^\d{4}/)

What you want is not clear, but a general solution to this kind of situation is:
"1234A".scan(/\d+|\D+/)
# => ["1234", "A"]

If there are always 4 digits and 1 letter, there's no need to use regular expressions to split the string. Just do this:
str = "1234A"
digits,letter = str[0..3],str[4]

Looking at it purely from the perspective of splitting any string into groups of 4:
"1234A".scan(/.{1,4}/)
# => ["1234", "A"]

Another no-regex version:
str = "1234A"
str.chars.to_a.last # => "A"
str.chop # => "1234"

Related

Splitting the string with last underscore

I have a string like "a_b_c" or "a_b_c_d" or "a_b_c_d_e". I want to split the string at the last underscore.
**input**
'a_b_c'
**output**
a_b
c
**input**
'a_b_c_d'
**output**
a_b_c
d
I have done the following:
a='a_b_c'
a=a.split('_')
last=a.pop
a.delete(last)
p a.join("_")
p last
and achieved the result, but I don't think this should be done this way. I hope there is some regular expression to achieve this. Is there anyone who can help me with this?
You can use String#rpartition that searches for a given pattern form the right end of the string and splits when it finds it.
'a_b_c_d_e'.rpartition(/_/)
=> ["a_b_c_d", "_", "e"]
s = 'a_b_c_d_e'
parts = s.rpartition(/_/)
[parts.first, parts.last]
=> ["a_b_c_d", "e"]
EDIT: applying advices from the comments:
'a_b_c_d_e'.rpartition('_').values_at(0,2)
=> ["a_b_c_d", "e"]
Do you really need to split? How about just replacing the _ with a space? e.g. using rindex and []=
a[a.rindex('_')] = ' '
I didn't do a benchmark, but split creates a new array, which typically requires more resources, at least in other languages.
EDIT: as the question was edited, its now clear the OP is asking for a list instead of a string output
You can also get values as below,
> a = a.split('_')
> a[0..-2].join('_')
# => "a_b_c_d"
> a[-1]
# => "e"
'a_b_c_d_e'.split /_(?!.*_)/
#=> ["a_b_c_d", "e"]
The negative lookahead (?!.*_) requires that following the match of the underscore there is no other underscore in the string.
Split it with regex:
a.split(/_(?=[^_]+$)/)
Explanation:
matches the character _ with positive Lookahead (?=[^_]+$)
Match a single character not present in the list below [^_]+ and
$ asserts position at the end of the string, or before the line terminator right at the end of the string (if any)
Assuming you know this string follows this format:
str = 'a_b_c_d_e'
# Remainder
str[0...-2] # -> 'a_b_c_d'
# Last symbol
str[-1] # -> 'e'

How do you capture part of a regex to a variable in Ruby?

I know about "string"[/regex/], which returns the part of the string that matches. But what if I want to return only the captured part(s) of a string?
I have the string "1952-FEB-21_70_The_Case_of_the_Gold_Ring.mp3". I want to store in the variable title the text The_Case_of_the_Gold_Ring.
I can capture this part with the regex /\d_(?!.*\d_)(.*).mp3$/i. But writing the Ruby "1952-FEB-21_70_The_Case_of_the_Gold_Ring.mp3"[/\d_(?!.*\d_)(.*).mp3$/i] returns 0_The_Case_of_the_Gold_Ring.mp3 which isn't what I want.
I can get what I want by writing
"1952-FEB-21_70_The_Case_of_the_Gold_Ring.mp3" =~ /\d_(?!.*\d_)(.*).mp3$/i
title = $~.captures[0]
But this seems sloppy. Surely there's a proper way to do this?
(I'm aware that someone can probably write a simpler regex to target the text I want that lets the "string"[/regex/] method work, but this is just an example to illustrate the problem, the specific regex isn't the issue.)
You can pass number of part to [/regexp/, index] method:
=> string = "1952-FEB-21_70_The_Case_of_the_Gold_Ring.mp3"
=> string[/\d_(?!.*\d_)(.*).mp3$/i, 1]
=> "The_Case_of_the_Gold_Ring"
=> string[/\d_(?!.*\d_)(.*).mp3$/i, 0]
=> "0_The_Case_of_the_Gold_Ring.mp3"
Have a look at the match method:
string = "1952-FEB-21_70_The_Case_of_the_Gold_Ring.mp3"
regexp = /\d_(?!.*\d_)(.*).mp3$/i
matches = regexp.match(string)
matches[1]
#=> "The_Case_of_the_Gold_Ring"
Where matches[0] would return the whole match and matches[1] (and following) returns all subcaptures:
matches.to_a
#=> ["0_The_Case_of_the_Gold_Ring.mp3", "The_Case_of_the_Gold_Ring"]
Read more examples: http://ruby-doc.org/core-2.1.4/MatchData.html#method-i-5B-5D
You can use named captures
"1952-FEB-21_70_The_Case_of_the_Gold_Ring.mp3" =~ /\d_(?!.*\d_)(?<title>.*).mp3$/i
and $~[:title] will give you want you want
Meditate on this:
Here's the source string to be parsed:
str = "1952-FEB-21_70_The_Case_of_the_Gold_Ring.mp3"
Patterns can be defined as strings:
DATE_REGEX = '\d{4}-[A-Z]{3}-\d{2}'
SERIAL_REGEX = '\d{2}'
TITLE_REGEX = '.+'
Then interpolated into a regexp:
regex = /^(#{ DATE_REGEX })_(#{ SERIAL_REGEX })_(#{ TITLE_REGEX })/
# => /^(\d{4}-[A-Z]{3}-\d{2})_(\d{2})_(.+)/
The advantage to that is it's easier to maintain because the pattern is really several smaller ones.
str.match(regex) # => #<MatchData "1952-FEB-21_70_The_Case_of_the_Gold_Ring.mp3" 1:"1952-FEB-21" 2:"70" 3:"The_Case_of_the_Gold_Ring.mp3">
regex.match(str) # => #<MatchData "1952-FEB-21_70_The_Case_of_the_Gold_Ring.mp3" 1:"1952-FEB-21" 2:"70" 3:"The_Case_of_the_Gold_Ring.mp3">
are equivalent because both Regexp and String implement match.
We can retrieve what was captured as an array:
regex.match(str).captures # => ["1952-FEB-21", "70", "The_Case_of_the_Gold_Ring.mp3"]
regex.match(str).captures.last # => "The_Case_of_the_Gold_Ring.mp3"
We can also name the captures and access them like we would a hash:
regex = /^(?<date>#{ DATE_REGEX })_(?<serial>#{ SERIAL_REGEX })_(?<title>#{ TITLE_REGEX })/
matches = regex.match(str)
matches[:date] # => "1952-FEB-21"
matches[:serial] # => "70"
matches[:title] # => "The_Case_of_the_Gold_Ring.mp3"
Of course, it's not necessary to mess with that rigamarole at all. We can split the string on underscores ('_'):
str = "1952-FEB-21_70_The_Case_of_the_Gold_Ring.mp3"
str.split('_') # => ["1952-FEB-21", "70", "The", "Case", "of", "the", "Gold", "Ring.mp3"]
split can take a limit parameter saying how many times it should split the string. Passing in 3 gives us:
str.split('_', 3) # => ["1952-FEB-21", "70", "The_Case_of_the_Gold_Ring.mp3"]
Grabbing the last element returns:
str.split('_', 3).last # => "The_Case_of_the_Gold_Ring.mp3"
I believe it would be easiest to use a capture group here, but I'd like to present some possibilities that do not, for illustrative purposes. All employ the same positive lookahead ((?=\.mp3$)). all but one use a positive lookbehind and one uses \K to "forget" the match up to the last character before beginning of the desired match. Some permit the matched string to contain digits (.+); others do not ([^\d]).
str = "1952-FEB-21_70_The_Case_of_the_Gold_Ring.mp3"
1 # match follows last digit followed by underscore, cannot contain digits
str[/(?<=\d_)[^\d]+(?=\.mp3$)/]
#=> "The_Case_of_the_Gold_Ring"
2 # same as 1, as `\K` disregards match to that point
str[/\d_\K[^\d]+(?=\.mp3$)/]
#=> "The_Case_of_the_Gold_Ring"
3 # match follows underscore, two digits, underscore, may contain digits
str[/(?<=_\d\d_).+(?=\.mp3$)/]
#=> "The_Case_of_the_Gold_Ring"
4 # match follows string having specfic pattern, may contain digits
str[/(?<=\d{4}-[A-Z]{3}-\d{2}_\d{2}_).+(?=\.mp3$)/]
#=> "The_Case_of_the_Gold_Ring"
5 # match follows digit, any 12 characters, another digit and underscore,
# may contain digits
str[/(?<=\d.{12}\d_).+(?=\.mp3$)/]
#=> "The_Case_of_the_Gold_Ring"

Ruby on Rails - How do I split string and Number?

I have a string "FooFoo2014".
I want the result to be => "Foo Foo 2014"
Any idea?
This works fine:
puts "FooFoo2014".scan(/(\d+|[A-Z][a-z]+)/).join(' ')
# => Foo Foo 2014
Of course in condition that you separate numbers and words from capital letter.
"FooFoo2014"
.gsub(/(?<=\d)(?=\D)|(?<=\D)(?=\d)|(?<=[a-z])(?=[A-Z])/, " ")
# => "Foo Foo 2014"
Your example is a little generic. So this might be guessing in the wrong direction. That being said, it seems like you want to reformat the string a little:
"FooFoo2014".scan(/^([A-Z].*)([A-Z].*\D*)(\d+)$/).flatten.join(" ")
As "FooFoo2014" is a string with some internal structure important to you, you need to come up with the right regular expression yourself.
From your question, I extract two tasks:
split the FooFoo at the capital letter.
/([A-Z].*)([A-Z].*)/ would do that, given you only have standard latin letters
split the letter from the digits
/(.*\D)(\d+)/ achieves that.
The result of scan is an array in my version of ruby. Please verify that in your setup.
If you think that regular expressions are too complicated for this, I suggest that you take a good look into ActiveSupport. http://api.rubyonrails.org/v3.2.1/ might help you.
If its only letters then only digits:
target = "FooFoo2014"
match_data = target.match(/([A-Za-z]+)(\d+)/)
p match_data[1] # => "FooFoo"
p match_data[2] # => "2014
If it is two words each made of one capitalized letter then lowercase letters, then digits:
target = "FooBar2014"
match_data = target.match(/([A-Z][a-z]+)([A-Z][a-z]+)(\d+)/)
p match_data[1] # => "Foo"
p match_data[2] # => "Bar"
p match_data[3] # => "2014
Better regex are probably possible.

How do I write a regular expression that will match characters in any order?

I'm trying to write a regular expressions that will match a set of characters without regard to order. For example:
str = "act"
str.scan(/Insert expression here/)
would match:
cat
act
tca
atc
tac
cta
but would not match ca, ac or cata.
I read through a lot of similar questions and answers here on StackOverflow, but have not found one that matches my objectives exactly.
To clarify a bit, I'm using ruby and do not want to allow repeat characters.
Here is your solution
^(?:([act])(?!.*\1)){3}$
See it here on Regexr
^ # matches the start of the string
(?: # open a non capturing group
([act]) # The characters that are allowed and a capturing group
(?!.*\1) # That character is matched only if it does not occur once more, Lookahead assertion
){3} # Defines the amount of characters
$
The only special think is the lookahead assertion, to ensure the character is not repeated.
^ and $ are anchors to match the start and the end of the string.
[act]{3} or ^[act]{3}$ will do it in most regular expression dialects. If you can narrow down the system you're using, that will help you get a more specific answer.
Edit: as mentioned by #georgydyer in the comments below, it's unclear from your question whether or not repeated characters are allowed. If not, you can adapt the answer from this question and get:
^(?=[act]{3}$)(?!.*(.).*\1).*$
That is, a positive lookahead to check a match, and then a negative lookahead with a backreference to exclude repeated characters.
Here's how I'd go about it:
regex = /\b(?:#{ Regexp.union(str.split('').permutation.map{ |a| a.join }).source })\b/
# => /(?:act|atc|cat|cta|tac|tca)/
%w[
cat act tca atc tac cta
ca ac cata
].each do |w|
puts '"%s" %s' % [w, w[regex] ? 'matches' : "doesn't match"]
end
That outputs:
"cat" matches
"act" matches
"tca" matches
"atc" matches
"tac" matches
"cta" matches
"ca" doesn't match
"ac" doesn't match
"cata" doesn't match
I use the technique of passing an array into Regexp.union for a lot of things; I works especially well with the keys of a hash, and passing the hash into gsub for rapid search/replace on text templates. This is the example from the gsub documentation:
'hello'.gsub(/[eo]/, 'e' => 3, 'o' => '*') #=> "h3ll*"
Regexp.union creates a regex, and it's important to use source instead of to_s when extracting the actual pattern being generated:
puts regex.to_s
=> (?-mix:\b(?:act|atc|cat|cta|tac|tca)\b)
puts regex.source
=> \b(?:act|atc|cat|cta|tac|tca)\b
Notice how to_s embeds the pattern's flags inside the string. If you don't expect them you can accidentally embed that pattern into another, which won't behave as you expect. Been there, done that and have the dented helmet as proof.
If you really want to have fun, look into the Perl Regexp::Assemble module available on CPAN. Using that, plus List::Permutor, lets us generate more complex patterns. On a simple string like this it won't save much space, but on long strings or large arrays of desired hits it can make a huge difference. Unfortunately, Ruby has nothing like this, but it is possible to write a simple Perl script with the word or array of words, and have it generate the regex and pass it back:
use List::Permutor;
use Regexp::Assemble;
my $regex_assembler = Regexp::Assemble->new;
my $perm = new List::Permutor split('', 'act');
while (my #set = $perm->next) {
$regex_assembler->add(join('', #set));
}
print $regex_assembler->re, "\n";
(?-xism:(?:a(?:ct|tc)|c(?:at|ta)|t(?:ac|ca)))
See "Is there an efficient way to perform hundreds of text substitutions in Ruby?" for more information about using Regexp::Assemble with Ruby.
I will assume several things here:
- You are looking for permutations of given characters
- You are using ruby
str = "act"
permutations = str.split(//).permutation.map{|p| p.join("")}
# and for the actual test
permutations.include?("cat")
It is no regex though.
No doubt - the regex that uses positive/negative lookaheads and backreferences is slick, but if you're only dealing with three characters, I'd err on the side of verbosity by explicitly enumerating the character permutations like #scones suggested.
"act".split('').permutation.map(&:join)
=> ["act", "atc", "cat", "cta", "tac", "tca"]
And if you really need a regex out of it for scanning a larger string, you can always:
Regexp.union "act".split('').permutation.map(&:join)
=> /\b(act|atc|cat|cta|tac|tca)\b/
Obviously, this strategy doesn't scale if your search string grows, but it's much easier to observe the intent of code like this in my opinion.
EDIT: Added word boundaries for false positive on cata based on #theTinMan's feedback.

how to count the words of a string in ruby

I want to do something like this
def get_count(string)
sentence.split(' ').count
end
I think there's might be a better way, string may have built-in method to do this.
I believe count is a function so you probably want to use length.
def get_count(string)
sentence.split(' ').length
end
Edit: If your string is really long creating an array from it with any splitting will need more memory so here's a faster way:
def get_count(string)
(0..(string.length-1)).inject(1){|m,e| m += string[e].chr == ' ' ? 1 : 0 }
end
If the only word boundary is a single space, just count them.
puts "this sentence has five words".count(' ')+1 # => 5
If there are spaces, line endings, tabs , comma's followed by a space etc. between the words, then scanning for word boundaries is a possibility:
puts "this, is./tfour words".scan(/\b/).size/2
I know this is an old question, but this might help someone stumbling here. Countring words is a complicated problem. What is a "word"? Do numbers and special characters count as words? Etc...
I wrote the words_counted gem for this purpose. It's a highly flexible, customizable string analyser. You can ask it to analyse any string for word count, word occurrences, and exclude words/characters using regexp, strings, and arrays.
counter = WordsCounted::Counter.new("Hello World!", exclude: "World")
counter.word_count #=> 1
counted.words #=> ["Hello"]
Etc...
The documentation and full source are on Github.
using regular expression will also cover multi spaces:
sentence.split(/\S+/).size
String doesn't have anything pre-built to do what you wanted. You can define a method in your class or extend the String class itself for what you want to do:
def word_count( string )
return 0 if string.empty?
string.split.size
end
Regex split on any non-word character:
string.split(/\W+/).size
...although it makes apostrophe use count as two words, so depending on how small the margin of error needs to be, you might want to build your own regex expression.
I recently found that String#count is faster than splitting up the string by over an order of magnitude.
Unfortunately, String#count only accepts a string, not a regular expression. Also, it would count two adjacent spaces as two things, rather than a single thing, and you'd have to handle other white space characters seperately.
p " some word\nother\tword.word|word".strip.split(/\s+/).size #=> 4
I'd rather check for word boundaries directly:
"Lorem Lorem Lorem".scan(/\w+/).size
=> 3
If you need to match rock-and-roll as one word, you could do like:
"Lorem Lorem Lorem rock-and-roll".scan(/[\w-]+/).size
=> 4

Resources