Conditional statement not triggered - ruby

I wrote a piece of code that creates a random string based on an input. The user decides its length and whether it should contain numbers and special characters. I added a fail-safe routine because the end result is random:
def create
i = #number
while i != 0
# testing the script I have noticed that the if statement is (always) ignored.
if i == 2 && (#word_bank.include?(#special_chars) && #rand_ary.include?(#special_chars) == false)
#rand_ary << #special_chars[rand(0..#special_chars.size - 1)]
end
letter = rand(0..word_bank.size - 1)
#puts "#{i}, #{word_bank[letter]}"
#rand_ary << word_bank[letter]
word_bank.delete_at(letter)
i -= 1
end
#rand_string = #rand_ary.join()
puts #rand_string
#rand_string
end
If the user elects to include a special character, a counter runs from n - 0. When i = 2, and no character from a special character array is included, a random special character is manually included.
But this if-statement is never triggered. I can't figure out why.

If your variables have the kind of values I think they do, it's because include isn't the right method to use here.
If #word_bank and #rand_ary are arrays, include will check if any single element is equal to #special_chars. If #special_chars is itself an array, then it'll only return true if one of the elements in #word_bank/#rand_ary is an array.
['a', 'b', 'c', '!'].include?('!') # => true
['a', 'b', 'c', '!'].include?(['!']) # => false
['a', 'b', 'c', ['!']].include?(['!']) # => true
I think you're actually interested in whether there's any overlap between them. In that case, you can use the intersection (&) operator and check whether it's empty.
['a', 'b', 'c', '!'] & ['!'] # => ['!']
['a', 'b', 'c'] & ['!'] # => []

Related

Can't understand why this code to convert a simple array into a simple hash isn't working

Code attached below.
arrayy = [[1,'one'],[2,'two'],[3,'three']]
hashy = {}
i = 0
arrayy.each do
hashy[arrayy[i,0]] = arrayy [i,1]
i = i+1
end
puts hashy[1]
puts hashy[2]
puts hashy[3]
end
This code doesn't output anything. No errors. So, I'm guessing that the problem is that nothing is being added to the hash.
Not sure what you are trying to achieve here, but when you are doing arrayy[i,0] in the loop, you are saying that you want to grab zero elements.
When you pass in two numbers as the argument against an array, the first number is the index of the target value and the second number is the length. For example:
arr = ['a', 'b', 'c', 'd', 'e']
puts arr[2, 3]
This would put out ['c','d','e'], which is 'starting from the element with index 2, grab 3 elements'.
You're requesting zero elements, which is an empty array, so you're keying everything on an empty array and all elements collide. To fix that, just use the iterator, as each gives you the elements you need:
arrayy = [[1,'one'],[2,'two'],[3,'three']]
hashy = {}
arrayy.each do |key, value|
hashy[key] = value
end
p hashy
# => {1=>"one", 2=>"two", 3=>"three"}
In your code the actual result you're getting is this:
p hashy
# => {[]=>[[3, "three"]]}
Where here you can see the [] key being used. The p method is really handy for looking at the internal structure of something. p hashy is equivalent to puts hashy.inspect.
As Sergio points out you were probably referencing the arrays the wrong way. To navigate two levels deep you do this:
hashy[arrayy[i][0]] = arrayy [i][1]
Where [i,0] means "at index of the array i select the next 0 elements" whereas [i][0] means "at the array at index i select the value at index 0".
It's worth noting that the simplest solution is to use Array#to_h which already does this:
arrayy = [[1,'one'],[2,'two'],[3,'three']]
hashy = array.to_h
p hashy
# => {1=>"one", 2=>"two", 3=>"three"}

.start_with? method not recognizing substring 'a'

I want to translate a string into pig latin. The rules are as following:
Valid words are two or more letters long.
If a word begins with a consonant (a letter other than 'a', 'e', 'i', 'o', or 'u'), then that first letter is shifted to the end of the word.
Then add 'ay'.
I managed to come up with the method:
def translate(word)
if word.size <= 2
word
elsif
word.size > 2
!word.start_with?('a', 'e', 'i', 'o', 'u')
x = word.reverse.chop.reverse
x.insert(-1, word[0])
x << "ay"
else
word << "ay"
end
end
However, my test does not pass for certain strings,
Test Passed: Value == "c"
Test Passed: Value == "pklqfay"
Test Passed: Value == "yykay"
Test Passed: Value == "fqhzcbjay"
Test Passed: Value == "ndnrzzrhgtay"
Test Passed: Value == "dsvjray"
Test Passed: Value == "qnrgdfay"
Test Passed: Value == "npfay"
Test Passed: Value == "ldyuqpewypay"
Test Passed: Value == "arqokudmuxay"
Test Passed: Value == "spvhxay"
Test Passed: Value == "firvmanxay"
Expected: 'aeijezpbay' - Expected: "aeijezpbay", instead got: "eijezpbaay"
Expected: 'etafhuay' - Expected: "etafhuay", instead got: "tafhueay"
These tests passes:
Test.assert_equals(translate("billy"),"illybay","Expected: 'illybay'")
Test.assert_equals(translate("emily"),"emilyay","Expected: 'emilyay'")
I am not sure why.
If the length of word is greather or equal to 2 return word, if not then do the start_with step, but when would your else statement work?
Try modifying your length validation just to less than 2, then check if the word "start_with" a vowel, and return just the word plus ay, and if not the do the first character rotate step then adding the ay part, something like:
def translate(word)
if word.size < 2
word
elsif word.start_with?('a', 'e', 'i', 'o', 'u')
word << "ay"
else
x = word.reverse.chop.reverse
x.insert(-1, word[0])
x << "ay"
end
end
Your code fails because it doesn't really evaluates if the word begins with a constant, you are only checking it but doing nothing about it, its just an isolated line:
!word.start_with?('a', 'e', 'i', 'o', 'u')
Try including that line inside an if condition, like this:
def translate(word)
if word.size <= 2
word
else
if !word.start_with?('a', 'e', 'i', 'o', 'u')
x = word.reverse.chop.reverse
x.insert(-1, word[0])
x << "ay"
else
word << "ay"
end
end
end
Also notice that i removed the if word.size > 2, it is not necessary since you are already checking for word.size <= 2, so anything other than that is > 2.

Why does is the index of the array repeatedly be printed out to be 2?

I'm working on this function:
It's supposed to take in an array and match it with a given word to see if that word can be formed with the given array of strings.
I added the two commented lines because I wanted to see how the for-loop works.
def canformword(arr,word)
arrword = word.chars
arrleft = arr
flag = true
for i in 0...arrword.size
ch = arrword[i]
# puts arrword[i]
if !arrleft.include?(ch)
flag = false
break
else
ind = arrleft.index(ch)
# puts ind
arrleft.delete_at(ind)
end
end
if flag
puts 'can form word'
else
puts 'can not form word'
end
end
canformword(['y','b','z','e','a','u','t'], 'beauty')
canformword(['r','o','u','g','h'], 'tough')
When I uncomment those two lines, the following is the output:
Why does the output print out the index 2 repeatedly? I would think that it would print out the index of each letter in my arrleft array rather than repeatedly spitting out 2!
I understand the 1 it prints out, because that's the index of b, but the rest is weird to me.
b
1
e
2
a
2
u
2
t
2
y
0
can form word
t
can not form word
hear a better implementation that
def can_form_word?(chars_array, word)
(word.chars - chars_array).empty?
end
that's all.
here another implementation the Ruby way. Because your code is like C. I've been writing Ruby code for more than three years now, and I never used for loops.
def canformword(chars,word)
word.each_char do |char|
puts char
if !chars.include?(char)
return false # or puts "Can't form word"
end
end
true # or puts "Can form word"
end
this is because you are deleting the character at position ind(arrleft.delete_at(ind)); so each time array characters are shifting one cell left.
Now as all your letters 'e','a','u','t','y' are placed ordered way so it is showing 2,2,2,2 continuously.
Now look at 'y'; it is at position 0 ; so 0 is printed at end.
So the issue is because you are deleting the characters at position 'ind'.
So, to achieve this you can just do one thing ; do not delete the characters when found rather replace it by some numeric value like '0'.
You obtain 2 several times because you are deleting elements from the array. In that case you delete the second element every time so the next character, in the next iteration, take the index 2 again.
Problem
If you want do delete index 2 and 3 from an array, you need to delete them in decreasing order, becausing deleting index 2 would modify index of 3:
array = %w(a b c d e)
array.delete_at(3)
array.delete_at(2)
p array
#=> ["a", "b", "e"]
or
array = %w(a b c d e)
array.delete_at(2)
array.delete_at(2)
p array
#=> ["a", "b", "e"]
Solution
For your code, you just need to replace
arrleft.delete_at(ind)
with
arrleft[ind] = nil
Alternative
Since you take the numbers of characters into account, here's a modified version of a previous answer :
class Array
def count_by
each_with_object(Hash.new(0)) { |e, h| h[e] += 1 }
end
def subset_of?(superset)
superset_counts = superset.count_by
count_by.all? { |k, count| superset_counts[k] >= count }
end
end
def can_form_word?(chars, word)
word.chars.subset_of?(chars)
end
p can_form_word?(['y','b','z','e','a','u','t'], 'beauty')
#=> true
p can_form_word?(['y','b','z','e','u','t'], 'beauty')
#=> false
p can_form_word?(['a', 'c', 'e', 'p', 't', 'b', 'l'], 'acceptable')
#=> false
p ('acceptable'.chars - ['a', 'c', 'e', 'p', 't', 'b', 'l']).empty?
#=> true

Stop a loop if a certain character is passed to it Ruby

So I am working on a small assignment to transcribe DNA strands to RNA strands. My current code looks like this:
class Complement
def self.of_dna(str)
dna_rna = { 'G' => 'C', 'C' => 'G', 'T' => 'A', 'A' => 'U' }
rna = []
str.scan(/[GCTA]/).each do |x|
rna << dna_rna[x]
end
rna.join('')
end
end
It works perfectly, except for in one situation. If a DNA strand is passed that is partially correct, for example ACGTXXXCTTAA, my method will translate the DNA to RNA and just leave out the X's, giving me a result of UGCAGAAUU rather than just "". How can I make it so the loop will fail and exit when it receives a letter that isn't DNA related?
EDIT:
The test I am trying to get to pass looks like this:
def test_dna_correctly_handles_partially_invalid_input
# skip
assert_equal '', Complement.of_dna('ACGTXXXCTTAA')
end
I attempted #Holger Just's idea from below, and received this error:
1) Error:
ComplementTest#test_dna_correctly_handles_completely_invalid_input:
ArgumentError: ArgumentError
/Users/LukasBarry/exercism/ruby/rna-transcription/rna_transcription.rb:6:in `block in of_dna'
/Users/LukasBarry/exercism/ruby/rna-transcription/rna_transcription.rb:5:in `each'
/Users/LukasBarry/exercism/ruby/rna-transcription/rna_transcription.rb:5:in `of_dna'
rna_transcription_test.rb:43:in `test_dna_correctly_handles_completely_invalid_input'
The usual failure I've been getting from the above method is this:
1) Failure:
ComplementTest#test_dna_correctly_handles_partially_invalid_input [rna_transcription_test.rb:48]:
Expected: ""
Actual: "UGCAGAAUU"
Any help would be greatly appreciated.
Try this
class Complement
def self.of_dna(str)
return "" if str =~ /[^GCTA]/
...
end
end
Fun fact, you don't even need a loop to replace characters
str = 'GATTACA'
str.tr('ATCG', 'UAGC')
# => 'CUAAUGU'
Is all you need.
You can also match X in your regex and perform some erorr handling if it is found in the string. This could look something like this:
class Complement
def self.of_dna(str)
dna_rna = { 'G' => 'C', 'C' => 'G', 'T' => 'A', 'A' => 'U' }
rna = []
str.scan(/[GCTAX]/).each do |x|
return '' if x == 'X'
rna << dna_rna[x]
end
rna.join('')
end
end
I prefer using Hash#fetch for this because it'll raise a KeyError for you on mismatch, allowing you to write less code that validates inputs (i.e., less defensive programming), which I think is more valuable than cleverness (in which case I would recommend String#tr).
class DNA
TranslationMap = { 'G' => 'C', 'C' => 'G', 'T' => 'A', 'A' => 'U' }
attr_reader :dna
def initialize(dna)
#dna = dna
end
def to_rna
dna.each_char.map do |nucleotide|
TranslationMap.fetch(nucleotide)
end.join('')
rescue KeyError
return false
end
end
Feel free to adapt what happens when the error is rescued to fit your needs. I recommend raising a more specific exception (e.g. DNA::InvalidNucleotide) for the caller to handle.
In use:
dna = DNA.new 'GCTA'
# => #<DNA:0x007fc49e903818 #dna="GCTA">
dna.to_rna
# => "CGAU"
dna = DNA.new 'ACGTXXXCTTAA'
# => #<DNA:0x007fc49f064800 #dna="ACGTXXXCTTAA">
dna.to_rna
# => false

Advancing Vowel to the Next Vowel in Ruby

I'm working on beginner Ruby tutorials. I'm trying to write a method that will advance vowels to the next vowel. 'a' will become 'e', 'e' will become 'i', 'u' will become 'a', etc etc. I've been trying various ideas for quite a while, to no avail.
I think I'm on the right track in that I need to create an array of the vowels, and then use an index to advance them to the next array in the vowel. I just can't seem to create the right method to do so.
I know this isn't workable code, but my outline is along these lines. Where I run into issues is getting my code to recognize each vowel, and advance it to the next vowel:
def vowel_adv(str)
vowels = ["a", "e", "i", "o", "u"]
str = str.split('')
**str_new = str.map do |letter|
if str_new.include?(letter)
str_new = str_new[+1]
end**
# The ** section is what I know I need to find working code with, but keep hitting a wall.
str_new.join
end
Any help would be greatly appreciated.
Because we have only a few vowels, I would first define a hash containing vowel keys and vowel values:
vowels_hash = {
'a' => 'e',
'A' => 'E',
'e' => 'i',
'E' => 'I',
'i' => 'o',
'I' => 'O',
'o' => 'u',
'O' => 'U',
'u' => 'a',
'U' => 'A'
}
Then I would iterate over the alphabets present in each word / sentence like so:
word.split(//).map do |character|
vowels_hash[character] || character
end.join
Update:
BTW instead of splitting the word, you could also use gsub with regex + hash arguments like so:
word.gsub(/[aeiouAEIOU]/, vowels_hash)
Or like so if you want to be Mr. / Ms. Fancy Pants:
regex = /[#{vowels_hash.keys.join}]/
word.gsub(regex, vowels_hash)
Here's your code with the fewest corrections necessary to make it work:
def vowel_adv(str)
vowels = ["a", "e", "i", "o", "u"]
str = str.split('')
str_new = str.map do |char|
if vowels.include?(char)
vowels.rotate(1)[vowels.index(char)]
else
char
end
end
str_new.join
end
vowel_adv "aeiou"
=> "eioua"
Things that I changed include
addition of a block variable to the map block
returning the thing you're mapping to from the map block
include? is called on the Array, not on the possible element
finding the next vowel by looking in the array of vowels, not by incrementing the character, which is what I think you were trying to do.
Here's an improved version:
VOWELS = %w(a e i o u)
ROTATED_VOWELS = VOWELS.rotate 1
def vowel_adv(str)
str.
chars.
map do |char|
index = VOWELS.index char
if index
ROTATED_VOWELS[index]
else
char
end
end.
join
end
static Arrays in constants
nicer array-of-string syntax
String#chars instead of split
use the index to test for inclusion instead of include?
no assignment to parameters, which is a little confusing
no temporary variables, which some people like and some people don't but I've done here to show that it's possible
And, just because Ruby is fun, here's a different version which copies the string and modifies the copy:
VOWELS = %w(a e i o u)
ROTATED_VOWELS = VOWELS.rotate 1
def vowel_adv(str)
new_str = str.dup
new_str.each_char.with_index do |char, i|
index = VOWELS.index char
if index
new_str[i] = ROTATED_VOWELS[index]
end
end
new_str
end
The string class has a nice method for this. Demo:
p "Oh, just a test".tr("aeiouAEIOU", "uaeioUAEIO") # => "Ih, jost u tast"
To riff of of steenslag's answer a little.
VOWELS = %w{a e i o u A E I O U}
SHIFTED_VOWELS = VOWELS.rotate 1
def vowel_shifter input_string
input_string.tr!(VOWELS.join, SHIFTED_VOWELS.join)
end
and just for fun, consonants:
CONSONANTS = ('a'..'z').to_a + ('A'..'Z').to_a - VOWELS
SHIFTED_CONSONANTS = CONSONANTS.rotate 1
def consonant_shifter input_string
input_string.tr!(CONSONANTS.join, SHIFTED_CONSONANTS.join)
end

Resources