Use Ruby program. Input: sentence Modify: words Output: modified sentence - ruby

I am new to Ruby. This is a programming interview question to use any language. I am trying to do it in Ruby.
Write a program to input a given sentence. Replace each word with the firstletter/#ofcharactersbetween1st&lastletter/lastletter of the word. All non-alpha (numbers, punctuation, etc.) should not be changed.
Example input: There are 12 chickens for 2 roosters.
Desired output: T3e a1e 12 c6s f1r 2 r6s.
I have the concept but need help with better approach and how to put the parts together:
s="There are 12 chickens for 2 roosters."
..
=> "There are 12 chickens for 2 roosters."
a = s.split(" ")
=> ["There", "are", "12", "chickens", "for", "2", "roosters."]
puts a.length
7
=> nil
puts a[0].length
5
=> nil
puts a[0].length-2
3
=> nil
puts a[0][0]
84
=> nil
puts a[0][0].chr
T
=> nil
puts a[0].length-2
3
=> nil
puts a[0][-1].chr
e
=> nil

Try this:
s = "There are 12 chickens for 2 roosters."
s.gsub(/([A-Za-z]+)/) { $1[0] + ($1.size - 2).to_s + $1[-1] }
It uses gsub which replaces all parts of the string matching the regular expression pattern.
The pattern in this case is /([A-Za-z]+)/ and groups occurrences of one or more characters in the ranges A-Z and a-z.
{ $1[0] + ($1.size - 2).to_s + $1[-1] } is a block executed for every occurrence. $1 is the first group matched in the pattern. The block replaces the occurrence with its first character $1[0], its length -2 to string ($1.size - 2).to_s and its last character $1[-1].

Related

How to calculate elements of many arrays in Ruby?

I have a question about array in ruby
I have an array which contains many elements (strings with uppercases and down case ) and i want to know how many element (how many string) in this array contains an uppercase letters :
i obtain many element but i dont know how to calculate them
thank you.
array.each do |arr|
print arr.scan(/[A-Z]/)
end
Following your example, what you need is match? if you want a boolean result regarding if the element matches or not with an uppercase letter on it:
['foo', 'Foo', 'FoO'].each { |string| p string.match?(/[A-Z]/) }
# false
# true
# true
You can use count and pass a block to check if the current element returns true when evaluating if it contains uppercase characters. The result is the total of elements yielding a true value from the block:
['foo', 'Foo', 'FoO'].count { |string| /[[:upper:]]/ =~ string }
# 2
So i did:
a = ["HellO", "hello", "World", "worlD"]
b = 0
a.each do |x|
b += x.scan(/[A-Z]/).length
end
puts b # Which equals 4 in this case
The problem I had in some of the answers above with my array.
For Cary's answer I get 3 which somehow missing one of the capital letters.
For Sebastian's answer I get 3 as well which is also somehow missing one of the capital letters.
My array has 2 capitals in the first string, 1 in the third and 1 in the fourth.
Of course the more normal ruby way would be with b += x.scan(/[A-Z]/).count instead of .length but it worked for me in irb.
Some sample output from my console of the three methods:
030 > a.grep(/\p{Lu}/).size
=> 3
:031 > a.count {|string| /[[:upper:]]/ =~ string}
=> 3
:026 > a.each do |x|
:027 > b += x.scan(/[A-Z]/).length
:028?> end
=> ["HellO", "hello", "World", "worlD"]
:029 > b
=> 4
It appears as if the two regex examples above just check for any capital in the string and count it as one so if you had multiple in the same string like my first "HellO" then it only counts as one:
:039 > ["HellO"].grep(/\p{Lu}/).size
=> 1
:040 > ["HellO"].count {|string| /[[:upper:]]/ =~ string}
=> 1
Of course this may not matter to you but if the string is longer than one word it very well may:
2.5.3 :045 > a = ["Hello World"]
2.5.3 :047 > a.each do |x|
2.5.3 :048 > b += x.scan(/[A-Z]/).count
2.5.3 :049?> end
=> ["Hello World"]
2.5.3 :050 > b
=> 2
2.5.3 :051 > a.count {|string| /[[:upper:]]/ =~ string}
=> 1
2.5.3 :052 > a.grep(/\p{Lu}/).size
=> 1
With two words in one string it you can see the difference.
Of course I am counting the total capital letters when you asked,
i want to know how many element (how many string) in this array contains an uppercase letters
In which case either of the other two answers above do beautifully :)

Split a string by multiple delimiters

I want to split a string by whitespaces, commas, and dots. Given this input :
"hello this is a hello, allright this is a hello."
I want to output:
hello 3
a 2
is 2
this 2
allright 1
I tried:
puts "Enter string "
text=gets.chomp
frequencies=Hash.new(0)
delimiters = [',', ' ', "."]
words = text.split(Regexp.union(delimiters))
words.each { |word| frequencies[word] +=1}
frequencies=frequencies.sort_by {|a,b| b}
frequencies.reverse!
frequencies.each { |wor,freq| puts "#{wor} #{freq}"}
This outputs:
hello 3
a 2
is 2
this 2
allright 1
1
I do not want the last line of the output. It considers the space as a
word too. This may be because there were consecutive delimiters (,, &, " ").
Use a regex:
str = 'hello this is a hello, allright this is a hello.'
str.split(/[.,\s]+/)
# => ["hello", "this", "is", "a", "hello", "allright", "this", "is", "a", "hello"]
This allows you to split a string by any of the three delimiters you've requested.
The stop and comma are self-explanatory, and the \s refers to whitespace. The + means we match one or more of these, and means we avoid empty strings in the case of 2+ of these characters in sequence.
You might find the explanation provided by Regex101 to be handy, available here: https://regex101.com/r/r4M7KQ/3.
Edit: for bonus points, here's a nice way to get the word counts using each_with_object :)
str.split(/[.,\s]+/).each_with_object(Hash.new(0)) { |word, counter| counter[word] += 1 }
# => {"hello"=>3, "this"=>2, "is"=>2, "a"=>2, "allright"=>1}

Ruby return matching character size

I'm trying to make a game which involves it returning the amount of characters that you've guessed that are in the correct position of the randomly generated word (eg., word is "board", and you enter "boat", you'd get 2/5, and you entered "board", you'd get 5/5).
word = File.readlines("wordslist.txt").sample;
guess = gets
same = guess.each_char.zip(word.each_char).select{ |g,w| g == w }.size
It works fine for any guess under the word length. If the word was "bye" and I entered "byk" it would return 3/3, but if I entered "by" it would return 2/3. Just looking to see if I'm doing anything wrong.
This is happening because both File.readlines and gets do not trim the trailing newline character from the returned string.
irb(main):001:0> File.read("wordslist.txt")
=> "hello\nbye\n"
irb(main):002:0> File.readlines("wordslist.txt")
=> ["hello\n", "bye\n"]
irb(main):003:0> gets
bye
=> "bye\n"
When your dictionary contains "bye\n" and you type "byk\n", there are actually 3 matches, "b", "y", and "\n". If you enter "by\n" though, the newline character doesn't match. The newline character will only match if the input strings are of the same length, and the returned value would be 1 more than you'd expect.
To fix this, you can call .chomp on both the strings to remove the trailing whitespace before comparing the characters:
word = File.readlines("wordslist.txt").sample.chomp;
guess = gets.chomp
same = guess.each_char.zip(word.each_char).select{ |g,w| g == w }.size
Tip: You can use .count instead of .select and .size:
same = guess.each_char.zip(word.each_char).count{ |g,w| g == w }
def number_same_position(guess, word)
(0..word.size-1).count { |i| guess[i] == word[i] }
end
number_same_position("boat", "board") #=> 3
number_same_position("bait", "board") #=> 1
number_same_position("sulk", "board") #=> 0
number_same_position("boater", "board") #=> 3
number_same_position("", "board") #=> 0
One could instead use three dots ((0...word.size)) but I always use two dots. Recall string[i] #=> nil when i >= string.size.

How to mask all but last four characters in a string

I've been attempting a coding exercise to mask all but the last four digits or characters of any input.
I think my solution works but it seems a bit clumsy. Does anyone have ideas about how to refactor it?
Here's my code:
def mask(string)
z = string.to_s.length
if z <= 4
return string
elsif z > 4
array = []
string1 = string.to_s.chars
string1[0..((z-1)-4)].each do |s|
array << "#"
end
array << string1[(z-4)..(z-1)]
puts array.join(", ").delete(", ").inspect
end
end
positive lookahead
A positive lookahead makes it pretty easy. If any character is followed by at least 4 characters, it gets replaced :
"654321".gsub(/.(?=.{4})/,'#')
# "##4321"
Here's a description of the regex :
r = /
. # Just one character
(?= # which must be followed by
.{4} # 4 characters
) #
/x # free-spacing mode, allows comments inside regex
Note that the regex only matches one character at a time, even though it needs to check up to 5 characters for each match :
"654321".scan(r)
# => ["6", "5"]
/(.)..../ wouldn't work, because it would consume 5 characters for each iteration :
"654321".scan(/(.)..../)
# => [["6"]]
"abcdefghij".scan(/(.)..../)
# => [["a"], ["f"]]
If you want to parametrize the length of the unmasked string, you can use variable interpolation :
all_but = 4
/.(?=.{#{all_but}})/
# => /.(?=.{4})/
Code
Packing it into a method, it becomes :
def mask(string, all_but = 4, char = '#')
string.gsub(/.(?=.{#{all_but}})/, char)
end
p mask('testabcdef')
# '######cdef'
p mask('1234')
# '1234'
p mask('123')
# '123'
p mask('x')
# 'x'
You could also adapt it for sentences :
def mask(string, all_but = 4, char = '#')
string.gsub(/\w(?=\w{#{all_but}})/, char)
end
p mask('It even works for multiple words')
# "It even #orks for ####iple #ords"
Some notes about your code
string.to_s
Naming things is very important in programming, especially in dynamic languages.
string.to_s
If string is indeed a string, there shouldn't be any reason to call to_s.
If string isn't a string, you should indeed call to_s before gsub but should also rename string to a better description :
object.to_s
array.to_s
whatever.to_s
join
puts array.join(", ").delete(", ").inspect
What do you want to do exactly? You could probably just use join :
[1,2,[3,4]].join(", ").delete(", ")
# "1234"
[1,2,[3,4]].join
# "1234"
delete
Note that .delete(", ") deletes every comma and every whitespace, in any order. It doesn't only delete ", " substrings :
",a b,,, cc".delete(', ')
# "abcc"
["1,2", "3,4"].join(', ').delete(', ')
# "1234"
Ruby makes this sort of thing pretty trivial:
class String
def asteriskify(tail = 4, char = '#')
if (length <= tail)
self
else
char * (length - tail) + self[-tail, tail]
end
end
end
Then you can apply it like this:
"moo".asteriskify
# => "moo"
"testing".asteriskify
# => "###ting"
"password".asteriskify(5, '*')
# => "***sword"
Try this one
def mask(string)
string[0..-5] = '#' * (string.length - 4)
string
end
mask("12345678")
=> "####5678"
I will add my solution to this topic too :)
def mask(str)
str.match(/(.*)(.{4})/)
'#' * ($1 || '').size + ($2 || str)
end
mask('abcdef') # => "##cdef"
mask('x') # => "x"
I offer this solution mainly to remind readers that String#gsub without a block returns an enumerator.
def mask(str, nbr_unmasked, mask_char)
str.gsub(/./).with_index { |s,i| i < str.size-nbr_unmasked ? mask_char : s }
end
mask("abcdef", 4, '#')
#=> "##cdef"
mask("abcdef", 99, '#')
#=> "######"
Try using tap
def mask_string(str)
str.tap { |p| p[0...-4] = '#' * (p[0...-4].length) } if str.length > 4
str
end
mask_string('ABCDEF') # => ##CDEF
mask_string('AA') # => AA
mask_string('S') # => 'S'

Ruby transform string of range measurements into a list of the measurements?

I have a sample string that I would like to transform, from this:
#21inch-#25inch
to this:
#21inch #22inch #23inch #24inch #25inch
Using Ruby, please show me how this can be done.
You can scan your string and working with range of strings:
numbers = "#21inch-#25inch".scan(/\d+/)
=> ["21", "25"]
Range.new(*numbers).map{ |s| "##{s}inch" }.join(" ")
=> "#21inch #22inch #23inch #24inch #25inch"
This solution working only if your string has a format like in your instance. For other cases you should write your own specific solution.
R = /
(\D*) # match zero or more non-digits in capture group 1
(\d+) # match one or more digits in capture group 2
([^\d-]+) # match on or more chars other the digits and hyphens in capture group 3
/x # free-spacing regex definition mode
def spin_out(str)
(prefix, first, units),(_, last, _) = str.scan(R)
(first..last).map { |s| "%s%s%s" % [prefix,s,units] }.join(' ')
end
spin_out "#21inch-#25inch"
#=> "#21inch #22inch #23inch #24inch #25inch"
spin_out "#45cm-#53cm"
#=> "#45cm #46cm #47cm #48cm #49cm #50cm #51cm #52cm #53cm"
spin_out "sz 45cm-sz 53cm"
#=> "sz 45cm sz 46cm sz 47cm sz 48cm sz 49cm sz 50cm sz 51cm sz 52cm sz 53cm"
spin_out "45cm-53cm"
#=> "45cm 46cm 47cm 48cm 49cm 50cm 51cm 52cm 53cm"
For str = "#21inch-#25inch", we obtain
(prefix, first, units),(_, last, _) = str.scan(R)
#=> [["#", "21", "inch"], ["-#", "25", "inch"]]
prefix
#=> "#"
first
#=> "21"
units
#=> "inch"
last
#=> "25"
The subsequent mapping is straightforward.
You can use a regex gsub with a block match replacement, like this:
string = "#21inch-#25inch"
new_string = string.gsub(/#\d+\w+-#\d+\w+/) do |match|
first_capture, last_capture = match.split("-")
first_num = first_capture.gsub(/\D+/, "").to_i
last_num = last_capture.gsub(/\D+/, "").to_i
pattern = first_capture.split(/\d+/)
(first_num..last_num).map {|num| pattern.join(num.to_s) }.join(" ")
end
puts "#{new_string}"
Running this will produce this output:
First: #21inch Last: #25inch
First num: 21 Last num: 25
Pattern: ["#", "inch"]
#21inch #22inch #23inch #24inch #25inch
The last line of output is the answer, and the previous lines show the progression of logic to get there.
This approach should work for other, slightly different unit formats, as well:
#32ft-#49ft
#1mm-5mm
#2acres-5acres
Making this suit multiple purposes will be quite simple. With a slight variation in the regex, you could also support a range format #21inch..#25inch:
/(#\d+\w+)[-.]+(#\d+\w+)/
Happy parsing!

Resources