how to count the words of a string in ruby - 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

Related

Ruby- How to remove all words which have a specific pattern in a string

For example, the string is "I am very happy today". I want to remove all words containing the letter "a". So the output should be "I very". how can I do that?
Similar to #Sam's answer, only smaller :) Uses the little known Enumerable#grep_v.
Inverted version of #grep. Returns an array of every element in enum for which not Pattern === element.
"I am very happy today".split.grep_v(/a/).join(' ') # => "I very"
You can try splitting each word and removing the ones that have the letter 'a' and join the words together like this:
"I am very happy today".split.reject{ |word| word.include?("a") }.join(" ")
Here's an example with a regex :
word boundary
alphanumeric characters
a
alphanumeric characters
word boundary
You need to remove the unneeded spaces then.
"I am very happy today".gsub(/\b\w*a\w*\b/i, '').strip.gsub(/\s+/, ' ')
The answers with split and join are cleaner, though.

opposite of sub in ruby

I want to replace the content (or delete it) that does not match with my filter.
I think the perfect description would be an opposite sub. I cannot find anything similar in the docs, and I'm not sure how to invert the regex, but I think a method would probably be the more convenient.
An example of how it would work (I've just changed the words to make it more clear)
"bird.cats.dogs".opposite_sub(/(dogs|cats)\.(dogs|cats)/, '')
#"cats.dogs"
I hope it's easy enough to understand.
Thanks in advance.
String#[] can take a regular expression as its parameter:
▶ "bird.cats.dogs"[/(dogs|cats)\.(dogs|cats)/]
#⇒ "cats.dogs"
For multiple matches one can use String#scan:
▶ "bird.cats.dogs.bird.cats.dogs".scan /(?:dogs|cats)\.(?:dogs|cats)/
#⇒ ["cats.dogs", "cats.dogs"]
So you want to extract the part that matches your regex?
You can use String#slice, for example:
"bird.cats.dogs".slice(/(dogs|cats)\.(dogs|cats)/)
#=> "cats.dogs"
And String#[] does the same.
"bird.cats.dogs"[/(dogs|cats)\.(dogs|cats)/]
#=> "cats.dogs"
You cannot have a single replacement string because the part of the string that matches the regex might not be at the beginning or end of the string, in which case it's not clear whether the replacement string should precede or follow the matching string. I've therefore written the following with two replacement strings, one for pre-match, the other for post_match. I've made this a method of the String class as that's what you've asked for (though I've given the method a less-perfect name :-) )
class String
def replace_non_matching(regex, replace_before, replace_after)
first, match, last = partition(regex)
replace_before + match + replace_after
end
end
r = /(dogs|cats)\.(dogs|cats)/
"birds.cats.dogs.pigs".replace_non_matching(r, "", "")
#=> "cats.dogs"
"birds.cats.dogs".replace_non_matching(r, "snakes.", ".hens")
#=> "snakes.cats.dogs.hens"
"birds.cats.dogs.mice.cats.dogs.bats".replace_non_matching(r, "snakes.", ".hens")
#=> "snakes.cats.dogs.hens"
Regarding the last example, the method could be modified to replace "birds.", ".mice." and ".bats", but in that case three replacement strings would be needed. In general, determining in advance the number of replacement strings needed could be problematic.

Use regular expression to fetch 3 groups from string

This is my expected result.
Input a string and get three returned string.
I have no idea how to finish it with Regex in Ruby.
this is my roughly idea.
match(/(.*?)(_)(.*?)(\d+)/)
Input and expected output
# "R224_OO2003" => R224, OO, 2003
# "R2241_OOP2003" => R2244, OOP, 2003
If the example description I gave in my comment on the question is correct, you need a very straightforward regex:
r = /(.+)_(.+)(\d{4})/
Then:
"R224_OO2003".scan(r).flatten #=> ["R224", "OO", "2003"]
"R2241_OOP2003".scan(r).flatten #=> ["R2241", "OOP", "2003"]
Assuming that your three parts consist of (R and one or more digits), then an underbar, then (one or more non-whitespace characters), before finally (a 4-digit numeric date), then your regex could be something like this:
^(R\d+)_(\S+)(\d{4})$
The ^ indicates start of string, and the $ indicates end of string. \d+ indicates one or more digits, while \S+ says one or more non-whitespace characters. The \d{4} says exactly four digits.
To recover data from the matches, you could either use the pre-defined globals that line up with your groups, or you could could use named captures.
To use the match globals just use $1, $2, and $3. In general, you can figure out the number to use by counting the left parentheses of the specific group.
To use the named captures, include ? right after the left paren of a particular group. For example:
x = "R2241_OOP2003"
match_data = /^(?<first>R\d+)_(?<second>\S+)(?<third>\d{4})$/.match(x)
puts match_data['first'], match_data['second'], match_data['third']
yields
R2241
OOP
2003
as expected.
As long as your pattern covers all possibilities, then you just need to use the match object to return the 3 strings:
my_match = "R224_OO2003".match(/(.*?)(_)(.*?)(\d+)/)
#=> #<MatchData "R224_OO2003" 1:"R224" 2:"_" 3:"OO" 4:"2003">
puts my_match[0] #=> "R224_OO2003"
puts my_match[1] #=> "R224"
puts my_match[2] #=> "_"
puts my_match[3] #=> "00"
puts my_match[4] #=> "2003"
A MatchData object contains an array of each match group starting at index [1]. As you can see, index [0] returns the entire string. If you don't want the capture the "_" you can leave it's parentheses out.
Also, I'm not sure you are getting what you want with the part:
(.*?)
this basically says one or more of any single character followed by zero or one of any single character.

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.

Eloquent way to format string in Ruby?

str = 'foo_bar baz __goo'
Should print as
Foo Bar Baz Goo
Tried to use split /(\s|_)/, but it returns '_' and ' ' and multiple spaces...?
Ruby 1.9.3
try this:
str.split(/[\s_]+/).map(&:classify).join(" ")
if you have access to active support, or
str.split(/[\s_]+/).map(&:capitalize).join(" ")
if you want plain ruby.
Assuming you mean /(\s|_)/ (direction of the slashes matters!), your regular expression is pretty close. The reason you're getting the delimiters (spaces and underscores) in your result is the parentheses: they instruct the splitter to include the delimiters in the returned array.
The reason you are getting extra empty strings is that you are splitting on \s, which matches exactly one space (or tab), or '_', which matches exactly one underscore. If you want to treat any number of spaces or underscores as a single delimiter, you need to add + to your regex - it means "one or more of the previous thing".
But \s|_+ means "a space, or one or more underscores". You want to apply the + to the whole expression, not just the _. That brings us back to the parentheses. In this case, you want to group the two alternatives together without capturing (and returning) them; the syntax for that is (?:...). So this is the result:
str.split(/(?:\s|_)+/)
Now, if you want to normalize case, you want to run capitalize on each string, which you can do with map, like this:
str.split(/(?:\s|_)+/).map { |s| s.capitalize }
or use the shortcut:
str.split(/(?:\s|_)+/).map(&:capitalize)
So far, all these solutions return an array of strings, which you can do a variety of things with. But if you just want to put them back together into a single string, you can use join. For instance, to put them together with a single space between them:
str.split(/(?:\s|_)+/).map(&:capitalize).join ' '
Try splitting the string on:
[\s_]+
Use /[\ _]+/, it looks for one or more occurrence or space or underscore. In that way it is able to eat out multiple underscores, spaces or a combination of both. After than you get an array, so you use map to transform each of them. Later you can get them together using join. See examples -
Get them in a list -
str.split(/[\ _]+/).map {|s| s.capitalize }
=> ["Foo", "Bar", "Baz", "Goo"]
Get them as a whole string -
str.split(/[\ _]+/).map {|s| s.capitalize }.join(" ")
=> "Foo Bar Baz Goo"
Here the pure Ruby version:
str = 'foo_bar baz __goo'
str.split(/[ _]+/).map{|s| s[0].upcase+s[1..-1]}.join(" ")

Resources