Replace the last match in a string - ruby

I'm playing around with Ruby to do some file versioning for me. I have a string 2.0.0.65 . I split it up, increment the build number (65 --> 66) then I want to replace the 65 with the 66. In this replace though, I only want to replace the last match of the string. What's the best way in Ruby to do this?
version_text = IO.read('C:\\Properties')
puts version_text
version = version_text.match(/(\d+\.\d+\.\d+\.\d+)/)[1]
puts version
build_version = version.split('.')[3]
puts build_version
incremented_version = build_version.to_i + 1
puts incremented_version`
...

If you just want to increment the integer at the very end of a string then try this:
s = '2.0.0.65'
s.sub(/\d+\Z/) {|x| x.to_i + 1} # => '2.0.0.66'

You can do something like this:
parts = "2.0.0.65".split('.')
parts[3] = parts[3].to_i + 1
puts parts.join(".")
output:
2.0.0.66
This gives you more control over just using a string replacement method, as now you can increment other parts of the version string if needed more easily.

Once you have the string with the build number, you only need to use 'succ' method
'2.0.0.65'.succ()
Which gives you the string
'2.0.0.66'

sample = '2.0.0.65'
def incr_version(version)
parts = version.split('.')
parts[-1] = parts[-1].to_i + 1
parts.join('.')
end
incr_version(sample) # => '2.0.0.66'

For fun, if you want to increment the last integer in any string you could do this:
str = "I have 3 cats and 41 rabbits"
str.reverse.sub(/\d+/){ |s| (s.reverse.to_i+1).to_s.reverse }.reverse
#=> "I have 3 cats and 42 rabbits"
This is only valid when you modify your regex to match the reversed version of the text.
More generally, you can do this:
class String
# Replace the last occurrence of a regex in a string.
# As with `sub` you may specify matches in the replacement string,
# or pass a block instead of the replacement string.
# Unlike `sub` the captured sub-expressions will be passed as
# additional parameters to your block.
def rsub!(pattern,replacement=nil)
if n=rindex(pattern)
found=match(pattern,n)
self[n,found[0].length] = if replacement
replacement.gsub(/\\\d+/){ |s| found[s[1..-1].to_i] || s }
else
yield(*found).to_s
end
end
end
def rsub(pattern,replacement=nil,&block)
dup.tap{ |s| s.rsub!(pattern,replacement,&block) }
end
end
str = "I have 3 cats and 41 rabbits"
puts str.rsub(/(?<=\D)(\d+)/,'xx')
#=> I have 3 cats and xx rabbits
puts str.rsub(/(?<=\D)(\d+)/,'-\1-')
#=> I have 3 cats and -41- rabbits
puts str.rsub(/(?<=\D)(\d+)/){ |n| n.to_i+1 }
#=> I have 3 cats and 42 rabbits
Note that (as with rindex) because the regex search starts from the end of the string you may need to make a slightly more complex regex to force your match to be greedy.

Related

Reverse a string while keeping it in a single line

I'm trying to reverse a string using the code:
puts("Hi now it's going to be done!")
string = gets.chomp.to_s
i = string.length
while i >= 0
puts(string[i])
i = i - 1
end
It prints the string in backward order, but each word is on a single line. How can I keep all of them on a single line?
puts adds a newline to the end of the output if one isn't already present.
print does not. So do this:
while i >=0
print string[i]
i=i-1
end
puts
The final puts is because you want any further printing to be on a new line.
Try this:
"Hi now it's going to be done!".chars.inject([]) { |s, c| s.unshift(c) }.join
Or This is a little easier to follow:
string = 'Hi now it's going to be done!'
string.reverse!

How to output certain characters and add characters in certain positions in ruby?

I'm not sure where I went wrong, but I created a class called phone number that takes a parameter of ph. ph gets converted to a string. So, when 123456789 is inputted like a.PhoneNumber(123456789) it should input out (123) 456-7890 and when a.area_code is called it should give #123, etc.
Why does none of my scan methods work correctly?
Can I use the split method to pull out only #123 from 1234567890?
class PhoneNumber
def initialize (ph)
#ph = ph
#ph.insert(0, '#')
#ph.scan(/.{0,1}/).join('( ')
#ph.scan(/.{3,4}/).join(')')
#ph.scan(/.{3,5}/).join('- ')
end
def to_s
#ph
end
def area_code
#ph.split(0,2)
end
end
print "Please enter the number: "
puts a = PhoneNumber.new(gets.strip)
puts a.area_code
The scan methods don't work because the Regex you are using are not doing what I think you think it should be doing.
.{0,1} matches anything between 0 and 1 character. That's why they just return the match string iteratively
#ph.scan(/.{0,1}/) #=> "#1234567890"
#ph.scan(/.{3,4}/) #=> "#1234567890"
#ph.scan(/.{4,5}/) #=> "#1234567890"
One possible way to fix this is to use indexes to get split the #ph in three parts. Alternatively, you can also use something like this to split the number in groups
#ph.scan(/(\d{3})(\d{3})(\d{4})/) #=> [["123", "456", "7890"]]
First argument to split must be a String or Regexp in #ph.split(0,2)
You can define area_code something like this if the first char of #ph is #
def area_code
#split[1,3]
end

Ruby: gsub Replace String with File

So i'm working on a function that combines different configuration files
I'm looping trough a configuration file and when I see a specific word (In this example "Test" I want this to be replaced with a File (Multiple Lines of text)
I have this for now
def self.configIncludes(config)
config = #configpath #path to config
combinedconfig = #configpath #for test purposes
doc = File.open(config)
text = doc.read
combinedConfig = text.gsub("test" , combinedconfig)
puts combinedConfig
So now I just replace my string "test" with combinedconfig but the output of this is my directory of where the config is placed
How do I replace it with text ?
All help is appreciated!
If the files are not large you could do the following.
Code
def replace_text(file_in, file_out, word_to_filename)
File.write(file_out,
File.read(file_in).gsub(Regexp.union(word_to_filename.keys)) { |word|
File.read(word_to_filename[word]) })
end
word_to_filename is a hash such that the key word is a to be replaced by the contents of the file named word_to_filename[word].
If the files are large, do this line-by-line, perhaps using IO#foreach.
Example
file_in = "input_file"
file_out = "output_file"
File.write(file_in, "Days of wine\n and roses")
#=> 23
File.write("wine_replacement", "only darkness")
#=> 13
File.write("roses_replacement", "no light")
#=> 8
word_to_filename = { "wine"=>"wine_replacement", "roses"=>"roses_replacement" }
replace_text(file_in, file_out, word_to_filename)
#=> 35
puts File.read(file_out)
Days of only darkness
and no light
Explanation
For file_in, file_out and word_to_filename I used in the above example, the steps are as follows.
str0 = File.read(file_in)
#=> "Days of wine\n and roses"
r = Regexp.union(word_to_filename.keys)
#=> /wine|roses/
Let's first see which words match the regex:
str0.scan(r)
#=> ["wine", "roses"]
Continuing,
str1 = str0.gsub(r) { |word| File.read(word_to_filename[word]) }
#=> "Days of only darkness\n and no light"
File.write(file_out, str1)
#=> 35
In computing str1, the gsub first matches the the word "wine". That string is therefore passed to the block and assigned to the block variable:
word = "wine"
and the block calculation is performed:
str2 = word_to_filename[word]
#=> word_to_filename["wine"]
#=> "wine_replacement"
File.read("wine_replacement")
#=> "only darkness"
so "wine" is replaced with "only darkness". The match on "roses" is processed similarly.

Counting words in Ruby with some exceptions

Say that we want to count the number of words in a document. I know we can do the following:
text.each_line(){ |line| totalWords = totalWords + line.split.size }
Say, that I just want to add some exceptions, such that, I don't want to count the following as words:
(1) numbers
(2) standalone letters
(3) email addresses
How can we do that?
Thanks.
You can wrap this up pretty neatly:
text.each_line do |line|
total_words += line.split.reject do |word|
word.match(/\A(\d+|\w|\S*\#\S+\.\S+)\z/)
end.length
end
Roughly speaking that defines an approximate email address.
Remember Ruby strongly encourages the use of variables with names like total_words and not totalWords.
assuming you can represent all the exceptions in a single regular expression regex_variable, you could do:
text.each_line(){ |line| totalWords = totalWords + line.split.count {|wrd| wrd !~ regex_variable }
your regular expression could look something like:
regex_variable = /\d.|^[a-z]{1}$|\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i
I don't claim to be a regex expert, so you may want to double check that, particularly the email validation part
In addition to the other answers, a little gem hunting came up with this:
WordsCounted Gem
Get the following data from any string or readable file:
Word count
Unique word count
Word density
Character count
Average characters per word
A hash map of words and the number of times they occur
A hash map of words and their lengths
The longest word(s) and its length
The most occurring word(s) and its number of occurrences.
Count invividual strings for occurrences.
A flexible way to exclude words (or anything) from the count. You can pass a string, a regexp, an array, or a lambda.
Customisable criteria. Pass your own regexp rules to split strings if you prefer. The default regexp has two features:
Filters special characters but respects hyphens and apostrophes.
Plays nicely with diacritics (UTF and unicode characters): "São Paulo" is treated as ["São", "Paulo"] and not ["S", "", "o", "Paulo"].
Opens and reads files. Pass in a file path or a url instead of a string.
Have you ever started answering a question and found yourself wandering, exploring interesting, but tangential issues, or concepts you didn't fully understand? That's what happened to me here. Perhaps some of the ideas might prove useful in other settings, if not for the problem at hand.
For readability, we might define some helpers in the class String, but to avoid contamination, I'll use Refinements.
Code
module StringHelpers
refine String do
def count_words
remove_punctuation.split.count { |w|
!(w.is_number? || w.size == 1 || w.is_email_address?) }
end
def remove_punctuation
gsub(/[.!?,;:)](?:\s|$)|(?:^|\s)\(|\-|\n/,' ')
end
def is_number?
self =~ /\A-?\d+(?:\.\d+)?\z/
end
def is_email_address?
include?('#') # for testing only
end
end
end
module CountWords
using StringHelpers
def self.count_words_in_file(fname)
IO.foreach(fname).reduce(0) { |t,l| t+l.count_words }
end
end
Note that using must be in a module (possibly a class). It does not work in main, presumably because that would make the methods available in the class self.class #=> Object, which would defeat the purpose of Refinements. (Readers: please correct me if I'm wrong about the reason using must be in a module.)
Example
Let's first informally check that the helpers are working correctly:
module CheckHelpers
using StringHelpers
s = "You can reach my dog, a 10-year-old golden, at fido#dogs.org."
p s = s.remove_punctuation
#=> "You can reach my dog a 10 year old golden at fido#dogs.org."
p words = s.split
#=> ["You", "can", "reach", "my", "dog", "a", "10",
# "year", "old", "golden", "at", "fido#dogs.org."]
p '123'.is_number? #=> 0
p '-123'.is_number? #=> 0
p '1.23'.is_number? #=> 0
p '123.'.is_number? #=> nil
p "fido#dogs.org".is_email_address? #=> true
p "fido(at)dogs.org".is_email_address? #=> false
p s.count_words #=> 9 (`'a'`, `'10'` and "fido#dogs.org" excluded)
s = "My cat, who has 4 lives remaining, is at abbie(at)felines.org."
p s = s.remove_punctuation
p s.count_words
end
All looks OK. Next, put I'll put some text in a file:
FName = "pets"
text =<<_
My cat, who has 4 lives remaining, is at abbie(at)felines.org.
You can reach my dog, a 10-year-old golden, at fido#dogs.org.
_
File.write(FName, text)
#=> 125
and confirm the file contents:
File.read(FName)
#=> "My cat, who has 4 lives remaining, is at abbie(at)felines.org.\n
# You can reach my dog, a 10-year-old golden, at fido#dogs.org.\n"
Now, count the words:
CountWords.count_words_in_file(FName)
#=> 18 (9 in ech line)
Note that there is at least one problem with the removal of punctuation. It has to do with the hyphen. Any idea what that might be?
Something like...?
def is_countable(word)
return false if word.size < 2
return false if word ~= /^[0-9]+$/
return false if is_an_email_address(word) # you need a gem for this...
return true
end
wordCount = text.split().inject(0) {|count,word| count += 1 if is_countable(word) }
Or, since I am jumping to the conclusion that you can just split your entire text into an array with split(), you might need:
wordCount = 0
text.each_line do |line|
line.split.each{|word| wordCount += 1 if is_countable(word) }
end

Search through text word by word

I'd like to search through a txt file for a particular word. If I find that word, I'd like to retrieve the word that immediately follows it in the file. If my text file contained:
"My name is Jay and I want to go to the store"
I'd be searching for the word "want", and would want to add the word "to" to my array. I'll be looking through a very big text file, so any notes on performance would be great too.
The most literal way to read that might look like this:
a = []
str = "My name is Jack and I want to go to the store"
str.scan(/\w+/).each_cons(2) {|x, y| a << y if x == 'to'}
a
#=> ["go", "the"]
To read the file into a string use File.read.
This is one way:
Code
def find_next(fname, word)
enum = IO.foreach(fname)
loop do
e = (enum.next).scan(/\w+/)
ndx = e.index(word)
if ndx
return e[ndx+1] if ndx < e.size-1
loop do
e = enum.next
break if e =~ /\w+/
end
return e[/\w+/]
end
end
nil
end
Example
text =<<_
It was the best of times, it was the worst of times,
it was the age of wisdom, it was the age of foolishness,
. . . . .
it was the epoch of belief, it was the epoch of incredulity,
it was the season of light, it was the season of darkness,
it was the spring of hope, it was the winter of despair…
_
FName = "two_cities"
File.write(FName, text)
find_next(FName, "worst")
# of
find_next(FName, "wisdom")
# it
find_next(FName, "foolishness")
# it
find_next(FName, "dispair")
#=> nil
find_next(FName, "magpie")
#=> nil
Shorter, but less efficient, and problematic with large files:
File.read(FName)[/(?<=\b#{word}\b)\W+(\w+)/,1]
This is probably not the fastest way to do it, but something along these lines should work:
filename = "/path/to/filename"
target_word = "weasel"
next_word = ""
File.open(filename).each_line do |line|
line.split.each_with_index do |word, index|
if word == target_word
next_word = line.split[index + 1]
end
end
end
Given a File, String, or StringIO stored in file:
pattern, match = 'want', nil
catch :found do
file.each_line do |line|
line.split.each_cons(2) do |words|
if words[0] == pattern
match = words.pop
throw :found
end
end
end
end
match
#=> "to"
Note that this answer will find at most one match per file for speed, and linewise operation will save memory. If you want to find multiple matches per file, or find matches across line breaks, then this other answer is probably the way to go. YMMV.
This is the fastest I could come up with, assuming your file is loaded in a string:
word = 'want'
array = []
string.scan(/\b#{word}\b\s(\w+)/) do
array << $1
end
This will find ALL words that follow your particular word. So for example:
word = 'want'
string = 'My name is Jay and I want to go and I want a candy'
array = []
string.scan(/\b#{word}\b\s(\w+)/) do
array << $1
end
p array #=> ["to", "a"]
Testing this on my machine where I duplicated this string 500,000 times, I was able to reach 0.6 seconds execution time. I've also tried other approaches like splitting the string etc. but this was the fastest solution:
require 'benchmark'
Benchmark.bm do |bm|
bm.report do
word = 'want'
string = 'My name is Jay and I want to go and I want a candy' * 500_000
array = []
string.scan(/\b#{word}\b\s(\w+)/) do
array << $1
end
end
end

Resources