Rob Matthews
Rob Matthews

Reputation: 161

How to "or" all elements in the range of an array

I am trying to say if x == consonants[0] or [1] or [2] all the way to [21] on one line. For some reason I thought consonants[0..21] would work but it doesn't:

consonants = ["b", "c", "d", "f", "g", "h", "j", "k", "l", "m", "n", "p", "q", "r", "s", "t",
"v", "w", "x", "y", "z"]
new_first = ["m", "a", "t", "t", "h", "e", "w", "s"]


new_first.each do |x|
if x == consonants[0]
    puts x.next!
elsif x == consonants[1]
    puts x.next!
elsif x == consonants[2]
    puts x.next!
elsif x == consonants[3]
    puts x.next!    
else
    puts "test failed"
end

end

Upvotes: 0

Views: 99

Answers (5)

the Tin Man
the Tin Man

Reputation: 160551

Consider this:

consonants = ('a' .. 'z').to_a - %w[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"]
shifted_consonants = consonants.zip(consonants.rotate).to_h # => {"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"}
'matthews'.chars.map{ |c| shifted_consonants[c] || c } # => ["n", "a", "v", "v", "j", "e", "x", "t"]

That took the range of 'a' to 'z' and converted it to an array, then subtracted the array of vowels, resulting in only consonants.

Next, it turned the array of consonants into a hash/look-up table shifted_consonants where each key is a current consonant and the value is the next consonant.

Finally, it takes each character in 'matthews' and looks to see if there is a value in shifted_consonants for that character. If not, nil is returned, which triggers || and returns the current character. If there is a hit in the hash, the next value for that consonant is returned, which short-circuits the || "or".

An alternate would be to take advantage of tr:

consonants = (('a' .. 'z').to_a - %w[a e i o u]).join # => "bcdfghjklmnpqrstvwxyz"
shifted_consonants = consonants.chars.rotate.join # => "cdfghjklmnpqrstvwxyzb"
'matthews'.tr(consonants, shifted_consonants).chars # => ["n", "a", "v", "v", "j", "e", "x", "t"]

Checking for speed:

CONSONANTS = ('a' .. 'z').to_a - %w[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"]
SHIFTED_CONSONANTS_HASH = CONSONANTS.zip(CONSONANTS.rotate).to_h # => {"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"}

CONSONANTS_STR = (('a' .. 'z').to_a - %w[a e i o u]).join # => "bcdfghjklmnpqrstvwxyz"
SHIFTED_CONSONANTS_STR = CONSONANTS_STR.chars.rotate.join # => "cdfghjklmnpqrstvwxyzb"

require 'fruity'

sample_string = 'matthews'
compare  do
  use_hash { sample_string.chars.map{ |c| SHIFTED_CONSONANTS_HASH[c] || c } }
  use_tr { sample_string.tr(CONSONANTS_STR, SHIFTED_CONSONANTS_STR).chars }
end

# >> Running each test 2048 times. Test will take about 1 second.
# >> use_tr is faster than use_hash by 10.000000000000009% ± 10.0%

The longer the sample string is, the greater the difference. Changing to:

sample_string = 'matthews' * 1000

I see a result of:

# >> Running each test 4 times. Test will take about 1 second.
# >> use_tr is faster than use_hash by 4x ± 0.1

Found in a comment, NOT in the question where it belongs...

my goal is to change the constonant to the next consonant and the vowel to the next vowel for the arraynew_first = ["m", "a",

Adjusting for that here's some changes. You can unravel the deltas:

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

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

SHIFTED_CONSONANTS_HASH = CONSONANTS.zip(SHIFTED_CONSONANTS).to_h # => {"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"}
SHIFTED_VOWELS_HASH = VOWELS.zip(SHIFTED_VOWELS).to_h # => {"a"=>"e", "e"=>"i", "i"=>"o", "o"=>"u", "u"=>"a"}

sample_string = 'matthews'

sample_string.chars.map{ |c| SHIFTED_CONSONANTS_HASH[c] || SHIFTED_VOWELS_HASH[c] } # => ["n", "e", "v", "v", "j", "i", "x", "t"]


CONSONANTS_STR = CONSONANTS.join # => "bcdfghjklmnpqrstvwxyz"
SHIFTED_CONSONANTS_STR = SHIFTED_CONSONANTS.join # => "cdfghjklmnpqrstvwxyzb"
SHIFTED_VOWELS_STR = SHIFTED_VOWELS.join # => "eioua"
CHARACTERS_STR = (CONSONANTS + VOWELS).join # => "bcdfghjklmnpqrstvwxyzaeiou"
SHIFTED_CHARACTERS_STR = SHIFTED_CONSONANTS_STR + SHIFTED_VOWELS_STR # => "cdfghjklmnpqrstvwxyzbeioua"

sample_string.tr(CHARACTERS_STR, SHIFTED_CHARACTERS_STR).chars # => ["n", "e", "v", "v", "j", "i", "x", "t"]

The changes won't affect the speed of the actual code: tr would still outrun using the hash lookups.

Upvotes: 0

tadman
tadman

Reputation: 211560

There's several ways to crack this nut, but it depends on your performance concerns, and how extensible this needs to be. Normally a chain of if statements which are of the form x == y and x == z can be folded into:

 case (x)
 when y, z
   # ... Executed on a match
 end

In your case you can even do this by using your array as a list of valid values:

 case (x)
 when *constants
   puts x.next!
 end

For larger lists you might want to fold this up into a Set since these are optimized for include? tests:

consonants_set = Set.new(consonants)

if (consonants_set.include?(x))
  puts x.next!
end

Since you're doing single letter matches you have a lot more options. For example, a regular expression:

consonants_regexp = Regexp.new('[%s]' % consonants.join)

if (consonants_regexp.match(x))
  puts x.next!
end

Or you can even do a simple substring match:

consonants_string = consonants.join

if (consonants_string[x])
  puts x.next!
end

Worth noting but you can iterate over the characters in strings:

'cwzbrly'.each_char do |c|
  puts c
end

That avoids the need to create and/or type in long arrays of the form [ 'a', 'b', ... ].

Upvotes: 4

aladan
aladan

Reputation: 29

If I correctly understand and you're expecting nuuixt as a result, you can do:

new_first.select { |letter| consonants.include?(letter) && letter.next! }

&& works here this way: if consonants.include?(letter) evaluates to true then block return letter.next! .

Upvotes: 0

davidhu
davidhu

Reputation: 10434

You can use regular expression

if x =~ /[aeiou]/
  puts 'test failed'
else
  puts x.next!    
end

/[aeiou]/ is saying match anything that is a, e, i, o, or u.

This will eliminate the need to create an array of consonants.

Upvotes: 0

gameCoder95
gameCoder95

Reputation: 359

You can do this:

if consonants.include? (x)
     your-code-here
end

This will check if there's an element equal to x inside the array.

Upvotes: 0

Related Questions