Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Program to change vowel to its index number in string in Ruby

Tags:

ruby

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"
like image 622
Jay_Pandya Avatar asked Oct 24 '15 16:10

Jay_Pandya


2 Answers

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"
like image 100
Alexey Shein Avatar answered Nov 17 '22 00:11

Alexey Shein


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.

like image 30
Cary Swoveland Avatar answered Nov 16 '22 23:11

Cary Swoveland