Why the Ruby each iterator goes first in the execution? - ruby

I've came across a weird thing doing simple tasks in Ruby. I just want to iterate the alphabet with the each method but the iteration goes first in the execution:
alfawit = ("a".."z")
puts "That's an alphabet: \n\n #{ alfawit.each { |litera| puts litera } } "
and this code results in this: (abbreviated)
a
b
c
⋮
x
y
z
That's an alphabet:
a..z
Any ideas why it works like this or what supposedly I did wrong?
Thanks in advance.

Because your each call is interpolated in your string literal that's executed before the fixed string. Also, each returns an Enumerable, in fact you print even that. Try this one
alfawit = ("a".."z")
puts "That's an alphabet: \n\n"
alfawit.each { |litera| puts litera }
or
puts "That's an alphabet: \n\n"
("a".."z").each { |litera| puts litera }
you can use interpolation if you want but in this way
alfawit = ("a".."z")
puts "That's an alphabet: \n\n#{alfawit.to_a.join("\n")}"

You can easily see what's going on if you extract the interpolation part into a variable:
alfawit = ("a".."z")
foo = alfawit.each { |litera| puts litera }
puts "That's an alphabet: \n\n #{ foo } "
The second line is causing the trouble: each invokes the block for each element of the range and then returns the receiver, so that foo becomes alfawit.
Here's another way to get the desired result:
alfawit = "a".."z"
puts "That's an alphabet:", alfawit.to_a
puts outputs each argument on a new line, but for array arguments, it outputs each element on a new line. Result:
That's an alphabet:
a
b
c
⋮
x
y
z
Likewise, you can turn the range into an argument list via *:
alfawit = "a".."z"
puts "That's an alphabet:", *alfawit
That's equivalent to:
puts "That's an alphabet:", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"

Related

How do I access the current array when using map/select

I have the following (working) code I'm trying to convert into a more concise snippet using either #map or #select.
def duplicate_string(string)
s_list = []
string.downcase.chars.each do |char|
if string.index(char) != string.rindex(char) && s_list.include?(char) == false
s_list << char if char != ' '
end
end
s_list
end
puts duplicate_string("I am a duplicate string") == ["i", "a", "t"] #true
This is what I've come up with so far, but I don't know how to access the current array that's been stored by #map or #select and using self isn't working
def duplicate_string_with_map(string)
string.downcase.chars.select { |char| string.index(char) != string.rindex(char) && self.include?(char) == false && char != ' ' }
end
The following code would solve your purpose:
def duplicate_string_with_map(string)
(string.downcase.chars.select { |char| string.index(char) != string.rindex(char) && char != ' ' }).uniq
end
Here you need not check include condition as you are already ensuring string.index(char) != string.rindex(char).
However, for a better ruby approach, I would suggest you to re-open String class and write a method there.
It would look something like this:
class String
def duplicate_characters_array
(downcase.chars.select { |char| index(char) != rindex(char) && char != ' ' }).uniq
end
end
string = "I am a duplicate string"
string.duplicate_characters_array
You don't need to access the array and you don't need to use Array#map.
There are many ways to reach the goal. One of them is to split the string in chars then group the chars (get a hash), reject the groups of space character and the groups smaller than two elements and return the keys of the remaining groups:
"I am a duplicate string"
.downcase
.chars
.group_by{|i| i}
.reject{|k, v| k == ' ' || v.length < 2}
.keys
# ["a", "i", "t"]
Here we can make use of a helper method, Array#difference. The method is explained here. Note that that link contains a link to an SO answer where I cite examples of its use. Though I proposed that the method be added to the Ruby core there appears to be little interest in doing so.
class Array
def difference(other)
h = other.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
reject { |e| h[e] > 0 && h[e] -= 1 }
end
end
Here we can use this helper as follows.
def duplicate_string(str)
a = str.gsub(/\s/,'').downcase.reverse.chars
a.difference(a.uniq).uniq.reverse
end
duplicate_string "I am a duplicate string"
#=> ["a", "i", "t"]
The steps are as follows.
str = "I am a duplicate string"
b = str.gsub(/\s/,'')
#=> "Iamaduplicatestring"
c = b.downcase
#=> "iamaduplicatestring"
d = c.reverse
#=> "gnirtsetacilpudamai"
a = d.chars
#=> ["g", "n", "i", "r", "t", "s", "e", "t", "a", "c", "i", "l", "p",
# "u", "d", "a", "m", "a", "i"]
e = a.uniq
#=> ["g", "n", "i", "r", "t", "s", "e", "a", "c", "l", "p", "u", "d", "m"]
f = a.difference(e)
#=> ["t", "i", "a", "a", "i"]
g = f.uniq
#=> ["t", "i", "a"]
g.reverse
#=> ["a", "i", "t"]
The key step is the calculation of f. For each element c of e, f contains n-1 instances of c, where n is the number of instances of c in a. The method therefore excludes characters other than spaces that appear in the string exactly once.

private method called noMethodError ruby

I've been trying to work the following problem out and ran into the error. The point of the problem is to use a given key sequence to encrypt a string. For example, when given "cat" and [1,2,3] the result should be "dcw"
Any suggestions? the error was the following
def vigenere_cipher(string, key_sequence)
keyIndex=0
string=string.each_char.map do |c|
c=c.shift!(c,keyIndex)
keyIndex+=1
if keyIndex=key_sequence.length
keyIndex=0
end
end
return string
end
def shift!(c,keyIndex)
alphabet = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
inititalLetterIndex=alphabet.index(c)
finalLetterIndex=alphabet[inititalLetterIndex+keyIndex]
return alphabet[finalLetterIndex]
end
vigenere_cipher("cat", [1,2,3])
# private method `shift!' called for "c":String (NoMethodError)
You are trying to call shift! on string object that does not define on String Class, instead you defined on main object. You can call it like shift!(c,keyIndex) instead of c.shift!(c,keyIndex)
If you want to call you method shift! on a string, you will have to define it on String class.
class String
def shift!(keyIndex)
# you can access `c` using `self` here
...
end
end
Then you can call it as c.shift!(keyIndex) (Note the arguments are different).
Step 1
cipher.rb:4:in `block in vigenere_cipher': private method `shift!' called for "c":String (NoMethodError)
shift! isn't defined in String class, but at the top level.
So replace c=c.shift!(c,keyIndex) by c=shift!(c,keyIndex)
Step 2
cipher.rb:17:in `[]': no implicit conversion of String into Integer (TypeError)
Line 16 defines :
finalLetterIndex=alphabet[inititalLetterIndex+keyIndex]
alphabet contains letters as Strings, so finalLetterIndex isn't an index (Numeric), but a String.
On line 17, you try to use this String as an index.
Replace line 16 with :
finalLetterIndex=inititalLetterIndex+keyIndex
Step 3
Your script doesn't raise any exception anymore. It also doesn't display anything, so add a puts to the last line :
puts vigenere_cipher("cat", [1,2,3]).inspect
It returns :
[0, 0, 0]
Step 4
keyIndex seems to be stuck at 0. Why?
Look at line 6 :
if keyIndex=key_sequence.length
It doesn't test an equality, it assigns keyIndex to key_sequence.length.
Since any number is truthy in Ruby, it executes the code inside the if statement. Replace with
if keyIndex==key_sequence.length
Step 5
Your code returns [nil, nil, 0]. Why?
string is defined as the result of map. map returns an Array, in which each element is the result of the last executed command inside the block : in this case, the if statement.
if returns nil when the condition isn't satisfied, and returns the last executed command otherwise. In this case 0.
Add c at the last line of your map block.
Step 6
Your code now returns ["c", "b", "v"]. Why?
You only shift by shiftIndex, not by the amount defined in key_sequence Array. Replace
c=shift!(c,keyIndex)
with
c=shift!(c,key_sequence[keyIndex])
Step 7
Your code returns ["d", "c", "w"]. Almost there!
Ruby is a dynamic language. You're free to overwrite the String string with an Array, but it will confuse others and your future self.
Use array or letters instead of string, and return letters.join
Your script now returns "dcw".
It should look like :
def vigenere_cipher(string, key_sequence)
keyIndex=0
letters=string.each_char.map do |c|
c=shift!(c,key_sequence[keyIndex])
keyIndex+=1
if keyIndex==key_sequence.length
keyIndex=0
end
c
end
return letters.join
end
def shift!(c,keyIndex)
alphabet = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
inititalLetterIndex=alphabet.index(c)
finalLetterIndex=inititalLetterIndex+keyIndex
return alphabet[finalLetterIndex]
end
Step 8
vigenere_cipher("Hello", [1,2,3])
raises
cipher.rb:17:in 'shift!': undefined method '+' for nil:NilClass (NoMethodError).
Well, 'H' isn't found in your alphabet. Use downcase :
array=string.downcase.each_char.map do |c|
Step 9
vigenere_cipher("Hello World", [1,2,3])
doesn't work either, because of the space. Delete anything that isn't a letter :
array=string.downcase.delete('^a-z').each_char.map do |c|
Step 10
vigenere_cipher("zzz", [1,2,3])
returns an empty String, because there's no letter after z.
Use modulo 26 :
return alphabet[finalLetterIndex%26]
Step 11
Remove typos, don't use camelCase for variables, remove unnecessary return and you get :
def vigenere_cipher(string, key_sequence)
key_index = 0
letters = string.downcase.delete('^a-z').each_char.map do |c|
c = shift(c, key_sequence[key_index])
key_index = (key_index + 1) % key_sequence.length
c
end
letters.join
end
def shift(c, key_index)
alphabet = ('a'..'z').to_a
initial_letter_index = alphabet.index(c)
final_letter_index = initial_letter_index + key_index
alphabet[final_letter_index % 26]
end
Step 12
Using each_char, zip and cycle, I'd rewrite the whole code this way :
class Integer
# 0 => 'a', 1 => 'b', ..., 25 => 'z', 26 => 'a'
def to_letter
('a'.ord + self % 26).chr
end
end
class String
# 'A' => '0', 'a' => 0, ..., 'z' => 25
def to_code
self.downcase.ord - 'a'.ord
end
end
def vigenere_cipher(string, key)
short_string = string.delete('^A-Za-z')
short_string.each_char.zip(key.cycle).map do |char, shift|
(char.to_code + shift).to_letter
end.join
end
Step 13
Wikipedia article uses a String as key :
def vigenere_cipher(string, key)
short_string = string.delete('^A-Za-z')
short_string.each_char.zip(key.each_char.cycle).map do |char, shift|
(char.to_code + shift.to_code).to_letter
end.join
end
vigenere_cipher('Attack at dawn!', 'LEMON').upcase # => "LXFOPVEFRNHR"
Step 14
You should also be able to decrypt the message :
def vigenere_cipher(string, key, decrypt = false)
short_string = string.delete('^A-Za-z')
short_string.each_char.zip(key.each_char.cycle).map do |char, shift|
(char.to_code + shift.to_code * (decrypt ? -1 : 1)).to_letter
end.join
end
vigenere_cipher("LXFOPVEFRNHR", 'LEMON', :decrypt) #=> "attackatdawn"
Well, that was longer than expected! :D

Regex to check alphanumeric string in ruby

I am trying to validate strings in ruby.
Any string which contains spaces,under scores or any special char should fail validation.
The valid string should contain only chars a-zA-Z0-9
My code looks like.
def validate(string)
regex ="/[^a-zA-Z0-9]$/
if(string =~ regex)
return "true"
else
return "false"
end
I am getting error:
TypeError: type mismatch: String given.
Can anyone please let me know what is the correct way of doing this?
If you are validating a line:
def validate(string)
!string.match(/\A[a-zA-Z0-9]*\z/).nil?
end
No need for return on each.
You can just check if a special character is present in the string.
def validate str
chars = ('a'..'z').to_a + ('A'..'Z').to_a + (0..9).to_a
str.chars.detect {|ch| !chars.include?(ch)}.nil?
end
Result:
irb(main):005:0> validate "hello"
=> true
irb(main):006:0> validate "_90 "
=> false
def alpha_numeric?(char)
if (char =~ /[[:alpha:]]/ || char =~ /[[:digit:]]/)
true
else
false
end
end
OR
def alpha_numeric?(char)
if (char =~ /[[:alnum:]]/)
true
else
false
end
end
We are using regular expressions that match letters & digits:
The above [[:alpha:]] ,[[:digit:]] and [[:alnum:]] are POSIX bracket expressions, and they have the advantage of matching Unicode characters in their category. Hope this helps.
checkout the link below for more options:
Ruby: How to find out if a character is a letter or a digit?
No regex:
def validate(str)
str.count("^a-zA-Z0-9").zero? # ^ means "not"
end
Great answers above but just FYI, your error message is because you started your regex with a double quote ". You'll notice you have an odd number (5) of double quotes in your method.
Additionally, it's likely you want to return true and false as values rather than as quoted strings.
Similar to the very efficient regex-ish approach mentioned already by #steenslag and nearly just as fast:
str.tr("a-zA-Z0-9", "").length.zero?
OR
str.tr("a-zA-Z0-9", "") == 0
One benefit of using tr though is that you could also optionally analyze the results using the same basic formula:
str = "ABCxyz*123$"
rejected_chars = str.tr("a-zA-Z0-9", "")
#=> *$
is_valid = rejected_chars.length.zero?
#=> false
Similar to #rohit89:
VALID_CHARS = [*?a..?z, *?A..?Z, *'0'..'9']
#=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
# "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
# "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
# "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
# "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
def all_valid_chars?(str)
a = str.chars
a == a & VALID_CHARS
end
all_valid_chars?('a9Z3') #=> true
all_valid_chars?('a9 Z3') #=> false
Use .match? in Ruby 2.4+.
Ruby 2.4 introduced a convenient boolean-returning .match? method.
In your case, I would do something like this:
# Checks for any characters other than letters and numbers.
# Returns true if there are none. Returns false if there are one or more.
#
def valid?( string )
!string.match?( /[^a-zA-Z0-9]/ ) # NOTE: ^ inside [] set turns it into a negated set.
end

How to break down a string that's in an array even further

I'm trying to break down a sentence that's part of one string, but break it down into letters in their own array and have that inside one big array.
So what I mean is:
def break("hello world")
the code in the method would than result in this:
[["h","e","l","l","o], ["w","o","r","l","d"]]
The reason why I need it like that is so I can rearrange the letters in the order I want later. I've tried several things, but no luck.
"hello world".split.map &:chars
# => [["h", "e", "l", "l", "o"], ["w", "o", "r", "l", "d"]]
I wouldn't use break as a method name. It's a key word in the language.
def break_it(str)
str.split.map { |word| word.each_char.to_a }
end
break_it("hello world")

Is there a one liner to destructively use `.split(//)` (e.g. turn string into array)?

So far I have:
my_array = "Foo bar na nas"
my_array.delete!(" ").downcase!
my_array = my_array.split(//).uniq
To get:
==> ["f", "o", "b", "a", "r", "n", "s"]
I can't seem to use .split!(//) like .delete! or .downcase! but I want to do all of this in one step. Is it possible?
Using my_array.delete!(" ").downcase!.split!(//) yields "': undefined method 'split!' for nil:NilClass" so I assume .split! just doesn't exist.
No. If you will read documentation you will get that destructive methods return nil when there is nothing to change, so you cannot chain them. If you want to change string to array of it's letters excluding whitespces you should rathe run:
my_array = "Foo bar na nas".downcase.gsub(/\W/, '').split(//).uniq
There also don't exist destructive method split!. Just how can it exist? Ruby is strong-typed language so you cannot change String into Array because they aren't related.
my_array.downcase.gsub(' ','').chars.uniq
Why not use split with a regular expression matching white space or nothing?
"Foo bar na nas".downcase.split(/\s*/).uniq
This returns
["f", "o", "b", "a", "r", "n", "s"]
split! does not exist because by convention methods with ! alter the object itself in ruby, and you can not coerce a string into an array because ruby is strongly typed.
"Foo bar na nas".downcase.split(//).uniq.keep_if { |item| item != " " }
#=> ["f", "o", "b", "a", "r", "n", "s"]
"Foo bar na nas t p".downcase.split(//).uniq.keep_if { |item| item != " " }
#=> ["f", "o", "b", "a", "r", "n", "s", "t", "p"]

Resources