I need to turn a string that contains (among other letters) a sequence of 3 letters consisting of a non-vowel, an "o" and the same non-vowel again
into
a string that contains (other letters and) only that non-vowel.
like
"kok" #=> "k"
"mom" #=> "m"
"lol" #=> "l"
"kokmomloljk" #=> "kmljk"
I would like my code to be as compact as possible and only use string methods.
str.each_char { | i | if i == /[^aeiou]/ and i == str[i.index + 2] and str[i.index + 1] == "o"
str = str.delete(str.slice(str[i.index + 1], 2))
end
}
The output is the unchanged string. Thank you in advance.
R = /
([^aeiou]) # match a consonant in capture group 1
o # match an 'o'
\1 # match the contents of capture group 1
/x # free-spacing regex definition mode
def my_method(str)
str.gsub(R,'\1')
end
my_method "my dog kok has fleas"
#=> "my dog k has fleas"
my_method "much momentum"
#=> "much mentum"
my_method "'plolly' is not a word"
#=> "'plly' is not a word"
my_method "abkokcmomloljkde"
#=> "abkcmljkde"
my_method "bub"
#=> "bub"
I was wondering if you could do this in a more functional non-destructieve way with map and, yes you can, but not more compact than the other answers:
str = "iskakmomlol"
VOWEL = /[aeiou]/
RESONANT = /[^aeiou]/
str.chars.map.with_index { |c, i|
prevb, prev, nxt, scnd = str[i - 2], str[i - 1], str[i + 1], str[i + 2]
if i > str.length - 1 || i == 0 then c
elsif c =~ RESONANT && nxt =~ VOWEL && c == scnd then c
elsif c =~ VOWEL && prev =~ RESONANT && nxt =~ RESONANT
elsif c =~ RESONANT && prevb == c && prev =~ VOWEL
else c
end
}.compact.join # "iskmljk"
Actually, this can be shorter:
R = /([^aeiou])[aeiou]\1/
str.chars.map.with_index { |c, i|
c unless str[i-1..i+1][R] || str[i-2..i][R]
}.compact.join # "iskmljk"
i figured since the "o" is a fix character in the sequence to be accepted, i can just go through with a count var and see if the characters next to it are equal to each other and non-vowels. also i found that slice can be also passed two parameters so that it slices starting at an offset and stopping after the given length.
index = 0
while index < str.length
index = index + 1
if str[index] == "o" and str[index-1] == str[index+1] and str[index-1] != /[^aeiou]/
str.slice!(index, 2)
Related
def encrypt(string)
alphabet = ("a".."b").to_a
result = ""
idx = 0
while idx < string.length
character = string[idx]
if character == " "
result += " "
else
n = alphabet.index(character)
n_plus = (n + 1) % alphabet.length
result += alphabet[n_plus]
end
idx += 1
end
return result
end
puts encrypt("abc")
puts encrypt("xyz")
I'm trying to get "abc" to print out "bcd" and "xyz" to print "yza". I want to advance the letter forward by 1. Can someone point me to the right direction?
All I had to do was change your alphabet array to go from a to z, not a to b, and it works fine.
def encrypt(string)
alphabet = ("a".."z").to_a
result = ""
idx = 0
while idx < string.length
character = string[idx]
if character == " "
result += " "
else
n = alphabet.index(character)
n_plus = (n + 1) % alphabet.length
result += alphabet[n_plus]
end
idx += 1
end
return result
end
puts encrypt("abc")
puts encrypt("xyz")
Another way to solve the issue, that I think is simpler, personally, is to use String#tr:
ALPHA = ('a'..'z').to_a.join #=> "abcdefghijklmnopqrstuvwxyz"
BMQIB = ('a'..'z').to_a.rotate(1).join #=> "bcdefghijklmnopqrstuvwxyza"
def encrypt(str)
str.tr(ALPHA,BMQIB)
end
def decrypt(str)
str.tr(BMQIB,ALPHA)
end
encrypt('pizza') #=> "qjaab"
decrypt('qjaab') #=> "pizza"
Alternatively if you don't want to take up that memory storing the alphabet you could use character codings and then just use arithmetic operations on them to shift the letters:
def encrypt(string)
result = ""
idx = 0
while idx < string.length
result += (string[idx].ord == 32 ? (string[idx].chr) : (string[idx].ord+1).chr)
idx += 1
end
result
end
Other strange thing about ruby is that you do not need to explicitly return something at the end of the method body. It just returns the last thing by default. This is considered good style amongst ruby folks.
Your question has been answered, so here are a couple of more Ruby-like ways of doing that.
Use String#gsub with a hash
CODE_MAP = ('a'..'z').each_with_object({}) { |c,h| h[c] = c < 'z' ? c.next : 'a' }
#=> {"a"=>"b", "b"=>"c",..., "y"=>"z", "z"=>"a"}
DECODE_MAP = CODE_MAP.invert
#=> {"b"=>"a", "c"=>"b",..., "z"=>"y", "a"=>"z"}
def encrypt(word)
word.gsub(/./, CODE_MAP)
end
def decrypt(word)
word.gsub(/./, DECODE_MAP)
end
encrypt('pizza')
#=> "qjaab"
decrypt('qjaab')
#=> "pizza"
Use String#gsub with Array#rotate
LETTERS = ('a'..'z').to_a
#=> ["a", "b", ..., "z"]
def encrypt(word)
word.gsub(/./) { |c| LETTERS.rotate[LETTERS.index(c)] }
end
def decrypt(word)
word.gsub(/./) { |c| LETTERS.rotate(-1)[LETTERS.index(c)] }
end
encrypt('pizza')
#=> "qjaab"
decrypt('qjaab')
#=> "pizza"
I have a school assignment where i have to find the longest run of adjacent equal characters in a given string with Ruby. My program works fine without the last loop, but once i added it gave me the error:
(repl):47: syntax error, unexpected keyword_end
(repl):53: syntax error, unexpected end-of-input, expecting keyword_end
puts longestRun
^
Here is my Code
puts 'What is your string?'
givenString = gets.chomp
def maxBlock(str)
maxRun = 0
currentRun = 1
characterCounter = 1
if str.length == 0
maxRun = 0
#If no input, longest run is zero
elsif str.length == 1
maxRun = 1
#If string is one character, longest run is 1
elsif str.length == 2 and str[characterCounter] != str[characterCounter + 1]
maxRun = 1
#if string is two chars and they do not equal, longest run is 1
elsif str.length == 3 and str[0] != str[1] and str[1] != str[2]
maxRun = 1
#if string is three chars and they do not equal, longest run is 1
else
str.each_char do|st|
#Go through each char, compare it to the next, find longest run
if st == str[characterCounter]
currentRun++
if currentRun > maxRun
maxRun = currentRun
end
else
currentRun = 1
end
characterCounter++
end
end
end
longestRun = maxBlock(givenString)
puts longestRun
EDIT: I am a highschool student, and only have a base knowledge of programming.
EDIT: I just made a few stupid mistakes. I appreciate everyone's help. Here is my working program without the use of anything too complicated.
puts 'What is your string?'
givenString = gets.chomp
def maxBlock(str)
maxRun = 0
currentRun = 1
characterCounter = 0
if str.length == 0
maxRun = 0
#If no input, longest run is zero
elsif str.length == 1
maxRun = 1
#If string is one character, longest run is 1
elsif str.length == 2 and str[characterCounter] != str[characterCounter + 1]
maxRun = 1
#if string is two chars and they do not equal, longest run is 1
elsif str.length == 3 and str[0] != str[1] and str[1] != str[2]
maxRun = 1
#if string is three chars and they do not equal, longest run is 1
else
characterCounter += 1
str.each_char do|st|
#Go through each char, compare it to the next, find longest run
if st == str[characterCounter]
currentRun += 1
if currentRun > maxRun
maxRun = currentRun
end
else
currentRun = 1
end
characterCounter += 1
end
end
return maxRun
end
longestRun = maxBlock(givenString)
puts longestRun
String Scans and Sorting
There are algorithms for this, but Ruby offers some nice shortcuts. For example:
def longest_string str
str.scan(/((\p{Alnum})\2+)/).collect { |grp1, grp2| grp1 }.sort_by(&:size).last
end
longest_string 'foo baaar quuuux'
#=> "uuuu"
This basically just captures all runs of repeated characters, sorts the captured substrings by length, and then returns the last element of the length-sorted array.
Secondary Sorting
If you want to do a secondary sort, such as first by length and then by alphabetical order, you could replace Enumerable#sort_by with the block form of Enumerable#sort. For example:
def longest_string str
str.scan(/((\p{Alnum})\2+)/).
collect { |grp1, grp2| grp1 }.
sort {|a, b| [a.size, a] <=> [b.size, b] }.
last
end
longest_string 'foo quux baar'
#=> "uu"
This is one way you could do it.
str = "111 wwwwwwwwaabbbbbbbbbbb$$$$****"
r = /
(.) # Match any character in capture group 1
\1* # Match the contents of capture group 1 zero or more times
/x # Free-spacing regex definition mode
str.gsub(r).max_by(&:size)
#=> "bbbbbbbbbbb"
I used the form of String#gsub without a second argument or block, as that returns an enumerator that generates the strings matched by the regex. I then chained that enumerator to the method Enumerable#max_by to find the longest string of consecutive characters. In other words, I used gsub merely to generate matches rather than to perform substitutions.
One could of course write str.gsub(/(.)\1*/).max_by(&:size).
Here is a simplified version that should work in all cases:
puts 'What is your string?'
given_string = gets.chomp
def max_block(str)
max_run = 0
current_run = 1
str.each_char.with_index do |st, idx|
if st == str[idx + 1]
current_run += 1
else
current_run = 1
end
max_run = current_run if current_run > max_run
end
max_run
end
longest_run = max_block(given_string)
puts longest_run
You were on the right track but Ruby can make things a lot easier for you. Notice how with_index gets rid of a lot of the complexity. Iterators, oh yeah.
I also changed your method name and variables to camel_case.
Happy coding!
The instructions for my program are to make a method that takes a string and returns dashes around any odd numbers, but with the exception that the end result may not begin or end with a dash "-". For example, if I enter "95601137", it should return
"9 - 5 - 6 0 - 1 - 1 - 3 - 7"
Here's the code:
def dashes(number)
string = number.to_s
i=0
final = []
while i<string.length
digit = string[i].to_i
if ((digit)%2 != 0) && (i==0) && (string.length == 1) && ((string.length - 1) == 0)
final.push(string[i])
elsif ((digit)%2 != 0) && (i==0) && (string.length != 1) && ((string.length - 1) > 0)
final.push(string[i] + "-")
elsif ((digit)%2 != 0) && (i>0) && (i!=(string.length - 1)) && ((string.length != 1))
final.push("-" + string[i] + "-")
elsif ((digit)%2 != 0) && (i!=0) && (i==(string.length - 1))
final.push("-" + string[i])
else final.push(string[i])
end
i+=1
end
return final
end
puts("Give me any number.")
answer = gets
puts dashes(answer)
My program has two issues:
When I enter 9, it returns "9-". How come?
When I enter a string that ends with an odd number, it puts a dash at the end, which it's not expected. I thought I made my if conditions strict, too.
I'd like to propose another solution:
def dashes(num)
str = num.to_s
dashed_arr = str.chars.map.with_index do |digit, index|
next digit if digit.to_i.even?
if index.zero? # is it first digit?
"#{digit} -"
elsif index == str.length - 1 # is it last digit?
"- #{digit}"
else
"- #{digit} -"
end
end
dashed_arr.join(' ').gsub('- -', '-')
end
puts dashes(95601137)
puts dashes(909)
# => "9 - 5 - 6 0 - 1 - 1 - 3 - 7"
# => "9 - 0 - 9"
Let's review it step by step:
str = num.to_s - convert our number to a string representation, i.e. "95601137"
str.chars - split our string by chars. We get an array of chars, like this: ["9", "5", "6", "0", "1", "1", "3", "7"], because we want to iterate them, one by one.
map takes one array and converts it to another array by the code supplied in do ... end.
For example, let's take this code:
[1, 2, 3].map do |num|
num * 2
end
# => [2, 4, 6]
If you need also an index of your element, you can similarly use .map.with_index (index starts from zero):
[1, 2, 3].map.with_index do |num, index|
num * index # index takes values 0, 1, 2
end
# => [0, 2, 6]
So, in the block in code above, we have each digit from our number as digit and its 0-based position as index.
next digit if digit.to_i.even?. We don't need even digits, so we can skip them. .to_i is needed to convert digit to integer, so we can query is it even or odd. next digit just returns current digit and moves execution to next digit.
Now we're left only with odd digits, so we just need to put dashes correctly, depending on the digit position: if it's a first digit, append a dash, if it's a last digit then put the dash in front of the digit, otherwise just surround digit with dashes:
if index.zero? # first digit, index is zero-based
"#{digit} -"
elsif index == str.length - 1 # last digit
"- #{digit}"
else
"- #{digit} -"
end
We save intermediate result in dashed_arr variable to make code readable. Right now it contains the following: ["9 -", "- 5 -", "6", "0", "- 1 -", "- 1 -", "- 3 -", "- 7"]. As you can see, we're almost done, we just need to connect all the elements of array back to string.
dashed_arr.join(' '). Join back elements of array into a string with single space as a separator. We get this string: 9 - - 5 - 6 0 - 1 - - 1 - - 3 - - 7. Hmm, it seems we need to delete some consecutive dashes.
Let's do it with gsub: dashed_arr.join(' ').gsub('- -', '-'). gsub just searches all occurrences of first string and replaces them with the second string, which is exactly what we need: 9 - 5 - 6 0 - 1 - 1 - 3 - 7.
We're done! I hope it was also fun for you as for me.
One more implementation, inspired by Cary Swoveland's first implementation:
str = '95601137'
separators = str.each_char.map(&:to_i).each_cons(2).map do |pair|
pair.any?(&:odd?) ? ' - ' : ' '
end
str.chars.zip(separators).join
# => "9 - 5 - 6 0 - 1 - 1 - 3 - 7"
Here are two ways, for:
str = "95601137"
Use an enumerator
With this method you would use String#each_char to create an enumerator to which you would send the methods Enumerator#next and Enumerator#peek:
enum = str.each_char
s = ''
loop do
c = enum.next
s << c
n = enum.peek
s << ((c.to_i.odd? || n.to_i.odd?) ? ' - ' : ' ')
end
s #=> "9 - 5 - 6 0 - 1 - 1 - 3 - 7"
The key here is that when the enumerator is at the last element ('7') peek raises a StopIteration exception. The exception is handled by Kernel#loop by breaking out of the loop.
Use String#gsub
We can use gsub three times, each with a regular expression, to:
put the dashes in the correct locations
space as desired around the dashes
space as desired between even digits
r = /
(\A\d) # match beginning of string followed by a digit, save in group 1
| # or
(\d\z) # match a digit followed by end of string, save in group 2
| # or
(\d) # match a digit, save in capture group 3
/x # extended mode
str = "95601137"
str.gsub(r) do |s|
if $1
$1.to_i.odd? ? "#{$1}-" : $1
elsif $2
$2.to_i.odd? ? "-#{$2}" : $2
elsif $3
$3.to_i.odd? ? "-#{$3}-" : $3
else
raise ArgumentError, "'#{s}' is not a digit"
end
end.gsub(/-+/, ' - ').gsub(/(\d)(\d)/,'\1 \2')
#=> "9 - 5 - 6 0 - 1 - 1 - 3 - 7"
Replace:
answer = gets
with:
answer = gets.strip
Then, you will get the correct output for input: 9
Because, when you give 9 as input and hit enter, your input is actually: "9\n" which has length 2. That's why it was printing 9-.
Using: gets.strip you just strip out the \n.
To remove the last - (if any), you can use chomp:
final_result = final.join('').chomp('-')
And, to remove the first - (if any), you can use this logic:
if final_result[0] == '-'
final_result[1..-1]
else
final_result
end
Here is the full working version of the code (keeping as much as of your code):
def dashes(number)
string = number.to_s
i = 0
final = []
while i < string.length
digit = string[i].to_i
if ((digit)%2 != 0) && (i==0) && (string.length == 1) && ((string.length - 1) == 0)
final.push(string[i])
elsif ((digit)%2 != 0) && (i==0) && (string.length != 1) && ((string.length - 1) > 0)
final.push(string[i] + "-")
elsif ((digit)%2 != 0) && (i>0) && (i!=(string.length - 1)) && ((string.length != 1))
final.push("-" + string[i] + "-")
elsif ((digit)%2 != 0) && (i!=0) && (i==(string.length - 1))
final.push("-" + string[i])
else
final.push(string[i])
end
i += 1
end
final_result = final.join('').gsub('--', '-').chomp('-')
puts "final_result: #{final_result.inspect}"
if final_result[0] == '-'
final_result[1..-1]
else
final_result
end
end
puts("Give me any number.")
answer = gets.strip
puts dashes(answer)
# > Give me any number.
# > 95601137
# > "9-5-60-1-1-3-7"
A naive implementation, there's probably some smarter ways to do it:
def dashes(number)
number # => 95601137
.to_s # => "95601137"
.chars # => ["9", "5", "6", "0", "1", "1", "3", "7"]
.map(&:to_i) # => [9, 5, 6, 0, 1, 1, 3, 7]
.map {|i| i.odd? ? "-#{i}-" : i } # => ["-9-", "-5-", 6, 0, "-1-", "-1-", "-3-", "-7-"]
.join # => "-9--5-60-1--1--3--7-"
.gsub(/^-|-$/, '') # => "9--5-60-1--1--3--7"
.gsub('--', '-') # => "9-5-60-1-1-3-7"
end
def get_string(no_of_times)
1.upto(no_of_times) do
string_input = gets.chomp
count_holes(string_input)
end
end
def count_holes(word)
count = 0
word.each_char do |char|
if char == "A" || char == "D" || char == "O" || char == "P" || char == "Q" || char == "R"
count += 1
elsif char == "B"
count += 2
end
end
$arr_of_holes << count
end
test_cases = gets.chomp.to_i
$arr_of_holes = []
get_string(test_cases)
puts $arr_of_holes
Hi all. I do not like the long condition in if statement while iterating over each character. So i wanted to ask you all if there is a better way to do this in ruby.
Thanks
This can be done with a case selection, as multiple terms can be supplied to each when:
case char
when "A", "D", "O", "P", "Q", "R"
count += 1
when "B"
count += 2
end
You can use Array#include?:
if %q{A D O P Q R}.include? char
count += 1
elsif char == "B"
count += 2
end
Alternative way using Hash:
def count_holes(word)
holes = {
'A' => 1,
'D' => 1,
'O' => 1,
'P' => 1,
'Q' => 1,
'B' => 2,
}
count = word.chars.map { |char| holes.fetch(char, 0) }.inject :+
$arr_of_holes << count
end
Slightly more compact than nacyot's answer:
count += case char
when "B" then 2
when "A", "D", "O".."R" then 1
else 0
end
The else line may not be required if there is not such case.
One more way:
word = "BROADLY"
"ADOPQR".each_char.reduce(0) { |t,c| t + word.count(c) } + 2*word.count("B")
#=> 6
Trying to convert a simple math string "1 2 3 * + 4 5 - /" to an array of integers and/or symbols like [1, 2, 3, :*, :+, 4, 5, :-, :/].
Is there a more elegant (and extendable) solution than this?
def tokenize s
arr = s.split(/ /)
symbols = %w{ + - / * }
arr.collect! do |c|
if symbols.include?(c)
c.to_sym
else
c.to_i
end
end
end
def tokenize(str)
str.split.map! { |t| t[/\d/] ? t.to_i : t.to_sym }
end
Instead of checking if the token is in a set of operations, you can just check if it contains a numeral digit (for your use case). So it's either an integer, or an operation.
Also note that Array#collect! and Array#map! are identical, and String#split by default splits on white space.
How about...
h = Hash[*%w{+ - / *}.map { |x| [x, x.to_sym] }.flatten]
And then,
arr.map { |c| h[c] || c.to_i }
So together:
def tokenize s
h = Hash[*%w{+ - / *}.map { |x| [x, x.to_sym] }.flatten]
s.split.map { |c| h[c] || c.to_i }
end
def tokenize s
s.scan(/(\d+)|(\S+)/)
.map{|num, sym| num && num.to_i || sym.to_sym}
end