How to cycle to the next value in an array, but only for specific values

I want to take a word, for example "Mathew", and turn it into "Nevjix" (but it should work for any word). The vowels cycle to the next vowel, and the consonants cycle to the next consonant.

I created two arrays, for vowels and consonants, and the code correctly identifies which is which, by displaying either the words "vowel" or "consonant".

I think I need to add something like a next! to the if statement, but I'm really not sure.

Here's what I have:

p "Input Agent Name"
name = gets.chomp

name = "#{name}".chars

array1 = ['a', 'e', 'i', 'o', 'u']
array2 = ['b', 'c', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'n', 'p', 'q', 'r', 's', 't', 'v', 'w', 'x', 'y', 'z']

p name


value = -1


until value == name.length
    value = value+1
    if array1.include? name[value]
        p "vowel"
    elsif array2.include? name[value]
        p "consonant"
    end
end


p name

Upvotes: 0

Views: 60

Answers (4)

the Tin Man
the Tin Man

Reputation: 160631

Here's how I'd go about it:

VOWELS = %w[a e i o u]
CONSONANTS = ('a' .. 'z').to_a - VOWELS
# => ["b", "c", "d", "f", "g", "h", "j", "k", "l", "m", "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z"]

CHAR_MAP = ((VOWELS + CONSONANTS).zip(VOWELS.rotate + CONSONANTS.rotate)).to_h
# => {"a"=>"e", "e"=>"i", "i"=>"o", "o"=>"u", "u"=>"a", "b"=>"c", "c"=>"d", "d"=>"f", "f"=>"g", "g"=>"h", "h"=>"j", "j"=>"k", "k"=>"l", "l"=>"m", "m"=>"n", "n"=>"p", "p"=>"q", "q"=>"r", "r"=>"s", "s"=>"t", "t"=>"v", "v"=>"w", "w"=>"x", "x"=>"y", "y"=>"z", "z"=>"b"}

CHAR_RE = Regexp.union(CHAR_MAP.keys)
# => /a|e|i|o|u|b|c|d|f|g|h|j|k|l|m|n|p|q|r|s|t|v|w|x|y|z/

'matthew'.gsub(CHAR_RE, CHAR_MAP) # => "nevvjix"

gsub makes it really easy to map words, substrings, individual characters, depending on the regular expression used. I let Regexp.union build the pattern, but it could have also been:

CHAR_RE = /[#{CHAR_MAP.keys.join}]/
# => /[aeioubcdfghjklmnpqrstvwxyz]/

or:

CHAR_RE = /[#{(VOWELS + CONSONANTS).join}]/
# => /[aeioubcdfghjklmnpqrstvwxyz]/

or, even better in this particular situation:

CHAR_RE = /./

which would return the same results.

There's a tiny build-up time to create the arrays, map and regular expression, so I'd create them as constants then reuse them throughout any code.

To handle upper-case characters, tweak it a bit:

LC_VOWELS = %w[a e i o u]
LC_CONSONANTS = ('a' .. 'z').to_a - LC_VOWELS

UC_VOWELS, UC_CONSONANTS = [LC_VOWELS, LC_CONSONANTS].map{ |a| a.map(&:upcase) }

CHAR_MAP = (
  (LC_VOWELS + UC_VOWELS + LC_CONSONANTS + UC_CONSONANTS).zip(
    [
      LC_VOWELS,
      UC_VOWELS,
      LC_CONSONANTS,
      UC_CONSONANTS
    ].map(&:rotate).inject(:+)
  )
).to_h
# => {"a"=>"e", "e"=>"i", "i"=>"o", "o"=>"u", "u"=>"a", "A"=>"E", "E"=>"I", "I"=>"O", "O"=>"U", "U"=>"A", "b"=>"c", "c"=>"d", "d"=>"f", "f"=>"g", "g"=>"h", "h"=>"j", "j"=>"k", "k"=>"l", "l"=>"m", "m"=>"n", "n"=>"p", "p"=>"q", "q"=>"r", "r"=>"s", "s"=>"t", "t"=>"v", "v"=>"w", "w"=>"x", "x"=>"y", "y"=>"z", "z"=>"b", "B"=>"C", "C"=>"D", "D"=>"F", "F"=>"G", "G"=>"H", "H"=>"J", "J"=>"K", "K"=>"L", "L"=>"M", "M"=>"N", "N"=>"P", "P"=>"Q", "Q"=>"R", "R"=>"S", "S"=>"T", "T"=>"V", "V"=>"W", "W"=>"X", "X"=>"Y", "Y"=>"Z", "Z"=>"B"}

CHAR_RE = /./

'Matthew'.gsub(CHAR_RE, CHAR_MAP) # => "Nevvjix"

For this problem tr would also do it, but it's only applicable when changing/translating individual characters. It lacks the ability to use a regular expression, so, while it's faster, it's less flexible.

Upvotes: 1

Nabeel
Nabeel

Reputation: 2302

String#tr could be used to achieve this. I'm not sure if you want to preserve casing (in which case you could either expand the two sets of strings or add some logic).

'matthew'.tr('bcdfghjklmnpqrstvwxyzaeiou', 'cdfghjklmnpqrstvwxyzbeioua')
#=>"nevvjix"

Alternatively to preserve casing you could write it as such

puts 'Input Agent Name'
name = gets.chomp.chars

def transform_character(char)
  char.tr('bcdfghjklmnpqrstvwxyzaeiou', 'cdfghjklmnpqrstvwxyzbeioua')
end

name.map! do |char|
  if char == char.upcase
    transform_character(char.downcase).upcase
  else
    transform_character(char)
  end
end

puts name.join

I've opted to use a simple if/else rather than the ternary operator (below) for clarity.

name.map! do |char|
  char == char.upcase ? transform_character(char.downcase).upcase : transform_character(char)
end

Upvotes: 2

Mike Robinson
Mike Robinson

Reputation: 8995

You can probably simplify your solution by realizing that "a consonant" is ... "not-a-vowel." (You don't actually need to maintain two different lists ...) The second elsif is unnecessary, because, "if it is not 'one,' it must be 'the other.'"

Upvotes: 0

Buddy
Buddy

Reputation: 11038

You should use a single map from the original letter to the re-mapped letter. And use map to iterate over the input string:

lookup = { 'a' => 'e', 'b' => 'c', 'c' => 'd', ..... }
name.chars.map! {|c| lookup[c]}

Upvotes: 1

Related Questions