Inserting a space in between characters using gsub - Ruby - ruby

Let's say I had a string "I have 36 dogs in 54 of my houses in 24 countries".
Is it possible by using only gsub to add a " " between each digit so that the string becomes "I have 3 6 dogs in 5 4 of my houses in 2 4 countries"?
gsub(/(\d)(\d)/, "#{$1} #{$2}") does not work as it replaces each digit with a space and neither does gsub(/\d\d/, "\d \d"), which replaces the each digit with d.

s = "I have 3651 dogs in 24 countries"
Four ways to use String#gsub:
Use a positive lookahead and capture group
r = /
(\d) # match a digit in capture group 1
(?=\d) # match a digit in a positive lookahead
/x # extended mode
s.gsub(r, '\1 ')
#=> "I have 3 6 5 1 dogs in 2 4 countries"
A positive lookbehind could be used as well:
s.gsub(/(?<=\d)(\d)/, ' \1')
Use a block
s.gsub(/\d+/) { |s| s.chars.join(' ') }
#=> "I have 3 6 5 1 dogs in 2 4 countries"
Use a positive lookahead and a block
s.gsub(/\d(?=\d)/) { |s| s + ' ' }
#=> "I have 3 6 5 1 dogs in 2 4 countries"
Use a hash
h = '0'.upto('9').each_with_object({}) { |s,h| h[s] = s + ' ' }
#=> {"0"=>"0 ", "1"=>"1 ", "2"=>"2 ", "3"=>"3 ", "4"=>"4 ",
# "5"=>"5 ", "6"=>"6 ", "7"=>"7 ", "8"=>"8 ", "9"=>"9 "}
s.gsub(/\d(?=\d)/, h)
#=> "I have 3 6 5 1 dogs in 2 4 countries"

An alternative way is to look for the place between the numbers using lookahead and lookbehind and then just replace that with a space.
[1] pry(main)> s = "I have 36 dogs in 54 of my houses in 24 countries"
=> "I have 36 dogs in 54 of my houses in 24 countries"
[2] pry(main)> s.gsub(/(?<=\d)(?=\d)/, ' ')
=> "I have 3 6 dogs in 5 4 of my houses in 2 4 countries"

In order to reference a match you should use \n where n is the match, not $1.
s = "I have 36 dogs in 54 of my houses in 24 countries"
s.gsub(/(\d)(\d)/, '\1 \2')
# => "I have 3 6 dogs in 5 4 of my houses in 2 4 countries"

Related

How to add and write to a file in Ruby

I am new to programming and would like some advice on how to proceed with this question.
I have a file with 10 math questions, but can't seem to figure out how to replace the underscores with the answer of each question.
1. 1 + 3 = __
2. 2 + 2 = __
3. 0 + 9 = __
4. 3 + 4 = __
5. 5 + 2 = __
6. 2 + 5 = __
7. 6 + 4 = __
8. 7 + 1 = __
9. 9 + 9 = __
10. 10 + -1 = __
I am able to open the text file and reading from it but can't seem to figure the way to add them and delete the underscores with the correct answer.
This is the code to read from the file.
File.open("math1.txt", 'r+') do |file|
file.each_line do |line|
puts line
end
end
Extracting operands and operator from a string
You have to somehow extract the operands and operators from the line. I'd use a regular expression:
re = /([-]?\d+) ([-+*\/]) ([-]?\d+)/
([-]?\d+) is the 1st capturing group
[-]? match the literal character - zero or one time
\d+ match a digit (0-9) one ore more times
([-+*\/]) is the 2nd capturing group
[-+*\/] match the characters -, +, * or / (must be escaped) once
([-]?\d+) is the 3rd, capturing group, same as the 1st
Examples using match:
re.match("1. 1 + 3 = __") #=> #<MatchData "1 + 3" 1:"1" 2:"+" 3:"3">
re.match("7. 6 + 4 = __") #=> #<MatchData "6 + 4" 1:"6" 2:"+" 3:"4">
re.match("10. 10 + -1") #=> #<MatchData "10 + -1" 1:"10" 2:"+" 3:"-1">
The capturing groups are can be retrieved from the MatchData:
m = re.match("7. 6 + 4 = __") #=> #<MatchData "6 + 4" 1:"6" 2:"+" 3:"4">
m[1] #=> "6"
m[2] #=> "+"
m[3] #=> "4"
And they are also stored (from left to right) in special global variables $1, $2 and $3:
re.match("7. 6 + 4 = __") #=> #<MatchData "6 + 4" 1:"6" 2:"+" 3:"4">
$1 #=> "6"
$2 #=> "+"
$3 #=> "4"
Performing calculations with dynamic operands and operator
Note that these are still strings. We have to convert them to integers via to_i in order to perform operations on them:
$1.to_i #=> 6
$3.to_i #=> 4
$1.to_i + $3.to_i #=> 10
But I've hard-coded the +. To make the method call dynamic, we have to use send:
6.send("+", 4) #=> 10
$1.to_i.send($2, $3.to_i) #=> 10
Replacing a placeholder with a value
Finally, we have to replace __ with our result. This can be done with sub:
"7. 6 + 4 = __".sub("__", "10")
#=> "7. 6 + 4 = 10"
That's it. Now you have to incorporate these steps into your loop.
This would be a simple solution
out = File.open("out.txt",'w+')
File.open("math1.txt", 'r+') do |file|
file.each_line do |line|
calc = eval(line.split(".")[1].split("=")[0])
out.write(line.sub('__',calc.to_s))
end
end

Splitting at Space Between Letter and Digit

I'm having the worst time with this simple regex.
Example input:
Cleveland Indians 5, Boston Redsox 4
I'm trying to split at the , and the space between the letter and number
Example output:
Cleveland Indians
5
Boston Redsox
4
Here is what I have so far, but it's including the number still.
/,|\s[0-9]/
string = "Cleveland Indians 5, Boston Redsox 4"
string.split /,\s*|\s(?=\d)/
# => ["Cleveland Indians", "5", "Boston Redsox", "4"]
\s(?=\d): a space followed by a digit using lookahead.
If you divide it into two splits -- one at the comma + space, then one to separate the team name from the score -- it might be a bit clearer, especially if you have to add more options like a space before the comma too (real-world data gets messy!):
scores = "Cleveland Indians 5, Boston Redsox 4"
scores.split(/,\s*/).map{|score| score.split(/\s+(?=\d)/)}
=> [["Cleveland Indians", "5"], ["Boston Redsox", "4"]]
The resulting list of lists is a more meaningful grouping, too.
"Cleveland Indians 5, Boston Redsox 4".split(/\s*(\d+)(?:,\s+|\z)/)
# => ["Cleveland Indians", "5", "Boston Redsox", "4"]
1)
str = "Cleveland Indians 15, Boston Red Sox 4"
phrases = str.split(", ")
phrases.each do |phrase|
*team_names, score = phrase.split(" ")
puts team_names.join " "
puts score
end
--output:--
Cleveland Indians
15
Boston Red Sox
4
.
2)
str = "Cleveland Indians 15, Boston Red Sox 4"
pieces = str.split(/
\s* #A space 0 or more times
(\d+) #A digit 1 or more times, include match with results
[,\s]* #A comma or space, 0 or more times
/x)
puts pieces
--output:--
Cleveland Indians
15
Boston Red Sox
4
The first split is on " 15, " and the second split is on " 4" -- with the score included in the results.
.
3)
str = "Cleveland Indians 15, Boston Red Sox 4"
str.scan(/
(
\w #Begin with a word character
\D+ #followed by not a digit, 1 or more times
)
[ ] #followed by a space
(\d+) #followed by a digit, one or more times
/x) {|capture_groups| puts capture_groups}
--output:--
Cleveland Indians
15
Boston Red Sox
4

Print numbers in a range

I am trying to print all numbers between 1 and 50, using the following code:
[1..50].each{|n| puts n}
but the console print
[1..50]
I want to print something like this
1
2
3
4
...
50
Try the following code:
(1..50).each { |n| puts n }
The problem is that you're using [] delimiter instead of () one.
You can use [1..10] with a minor tweak:
[*1..10].each{ |i| p i }
outputs:
1
2
3
4
5
6
7
8
9
10
The * (AKA "splat") "explodes" the range into its components, which are then used to populate the array. It's similar to writing (1..10).to_a.
You can also do:
puts [*1..10]
to print the same thing.
So, try:
[*1..10].join(' ') # => "1 2 3 4 5 6 7 8 9 10"
or:
[*1..10] * ' ' # => "1 2 3 4 5 6 7 8 9 10"
To get the output you want.
The error here is that you are creating an Array object with a range as its only element.
> [1..10].size
=> 1
If you want to call methods like each on a range, you have to wrap the range in parentheses to avoid the method being called on the range's last element rather than on the range itself.
=> (1..10).each { |i| print i }
12345678910
Other ways to achieve the same:
(1..50).each { |n| print n }
1.up_to(50) { |n| print n }
50.times { |n| print n }
You can cast your range (in parentheses) to an array ([1 2 3 4 5 6... 48 49 50]) and join each item (e.g. with ' ' if you want all items in one line).
puts (1..50).to_a.join(' ')
# => 1 2 3 4 5 6 7 ... 48 49 50

Ruby: increment all integers in a string by +1

I am looking for a succinct way to increment all the integers found in a string by +1 and return the full string.
For example:
"1 plus 2 and 10 and 100"
needs to become
"2 plus 3 and 11 and 101"
I can find all the integers very easily with
"1 plus 2 and 10 and 100".scan(/\d+/)
but I'm stuck at this point trying to increment and put the parts back together.
Thanks in advance.
You could use the block form of String#gsub:
str = "1 plus 2 and 10 and 100".gsub(/\d+/) do |match|
match.to_i + 1
end
puts str
Output:
2 plus 3 and 11 and 101
The gsub method can take in a block, so you can do this
>> "1 plus 2 and 10 and 100".gsub(/\d+/){|x|x.to_i+1}
=> "2 plus 3 and 11 and 101"
The thing with your regex is that it doesn't preserve your original string in the chain in order to put it back. What I did was to split it using spaces, detect which are words or integers using w.to_i != 0 (not counting 0 as an integer, you might want to improve this), add one, and join it back:
s = "1 plus 2 and 10 and 100"
s.split(" ").map{ |e| if (e.to_i != 0) then e.to_i+1 else e end }.join(" ")
=> "2 plus 3 and 11 and 101"

Start a loop from 1

I recently came upon the scary idea that Integer.count loops in Ruby start from 0 and go to n-1 while playing with the Facebook Engineering puzzlers. I did the dirty fix of adding one to the block variable in the beginning so that it would start at one instead.
Is there a prettier way?
Example:
10.times do |n|
n += 1
puts n
end #=> 012345789
Ruby supports a number of ways of counting and looping:
1.upto(10) do |i|
puts i
end
>> 1.upto(10) do |i|
> puts i
| end #=> 1
1
2
3
4
5
6
7
8
9
10
There's also step instead of upto which allows you to increment by a step value:
>> 1.step(10,2) { |i| puts i } #=> 1
1
3
5
7
9
You could use a range:
(1..10).each { |i| puts i }
Ranges give you full control over the starting and ending indexes (as long as you want to go from a lower value to a higher value).
Try
(1..10).each do |i|
# ... i goes from 1 to 10
end
instead. It is also easier to read when the value of i matters.
Old, but this might be something somebody's lookin for..
5.times.with_index(100){|i, idx| p i, idx};nil
#=>
0
100
1
101
2
102
3
103
4
104
There is of course the while-loop:
i = 1
while i<=10 do
print "#{i} "
i += 1
end
# Outputs: 1 2 3 4 5 6 7 8 9 10

Resources