This is a program to change a vowel into its index:
def vowel_2_index(string)
return '' if string.nil?
arr = string.enum_for(:scan,/[aeiou]/i).map {Regexp.last_match.begin(0) }
s_arr = arr.map{|x| x+1 }
arr.each_with_index{|x,y| string[x] = s_arr[y].to_s}
string
end
Can anyone tell me why it's failing to pass 'Codewars is the best site in the world'?
When I try to pass a testcase like:
Test.assert_equals(vowel_2_index('Codewars is the best site in the world'),'C2d4w6rs 10s th15 b18st s23t25 27n th32 w35rld')
it outputs something like:
"C2d4w6rs 10s t15e18bes232527ite32i35 the world"
As you can see, when you start replacing vowels with their positions it works while the position is a single digit (i.e. 1
or 5
), but when position becomes 2-digit (10
and bigger) all found indexes start to shift and arr
information is not correct anymore.
I'd propose to use gsub
, since you want to do a global search and replace, you almost did it right:
str = 'Codewars is the best site in the world'
str.gsub(/[aeiou]/) { |item| Regexp.last_match.begin(0) + 1 }
# => "C2d4w6rs 10s th15 b18st s23t25 27n th32 w35rld"
All of the following refers to:
str = 'Codewars is the best site in the world'
As to the problem you are having, let's break it down:
enum = str.enum_for(:scan,/[aeiou]/i)
#=> #<Enumerator: "Codewars is the best site in the world":scan(/[aeiou]/i)>
To see the elements of this enumerator that are passed by map
to its block, we can convert it to an array:
enum.to_a
#=> ["o", "e", "a", "i", "e", "e", "i", "e", "i", "e", "o"]
Continuing:
arr = enum.map {Regexp.last_match.begin(0) }
#=> [1, 3, 5, 9, 14, 17, 22, 24, 26, 31, 34]
s_arr = arr.map{|x| x+1 }
#=> [2, 4, 6, 10, 15, 18, 23, 25, 27, 32, 35]
arr.each_with_index{|x,y| string[x] = s_arr[y].to_s}
#=> [1, 3, 5, 9, 14, 17, 22, 24, 26, 31, 34]
str
#=> "C2d4w6rs 10s t15e18bes232527ite32i35 the world"
The method should return:
#=> "C2d4w6rs 10s th15 b18st s23t25 27n th32 w35rld"
So you see the divergence begins with 'h'
in 'the'
, which is at index 13
. The problem occurs when element 10
of arr
is passed to the block. At this point,
str
#=> "C2d4w6rs is the best site in the world"
The block variables are set to:
x,y = [10, 3]
x #=> 10
y #=> 3
so the block calculation is:
str[10] = s_arr[3].to_s
#=> = "10"
and now:
str
#=> "C2d4w6rs 10s the best site in the world"
As you see, the indices of all the letters following 10
have increased by one. This wasn't a problem earlier because the first three characters were each replaced with a single-digit number. The remaining elements of arr
and s_arr
are now off by one, and after the next replacement the indices of those that remain will be off by two, and so on.
*********
I would use one of the following approaches.
VOWELS = 'aeiouAEIOU'
#1
pos = '0'
str.gsub(/./) { |c| pos.next!; VOWELS.include?(c) ? pos : c }
#=> "C2d4w6rs 10s th15 b18st s23t25 27n th32 w35rld"
#2
str.each_char.with_index(1).map { |c,i| VOWELS.include?(c) ? i.to_s : c }.join
#=> "C2d4w6rs 10s th15 b18st s23t25 27n th32 w35rld"
#3
str.size.times.map { |i| VOWELS.include?(str[i]) ? (i+1).to_s : str[i] }.join
#=> "C2d4w6rs 10s th15 b18st s23t25 27n th32 w35rld"
I have a slight preference for #1 because it operates on the string directly, as opposed to creating an array and then joining its elements back into a string. Moreover, I think it reads best.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With