I have a method that takes in a string as an argument, replaces each letter with the next letter in the alphabet and then capitalizes every vowel. I have gotten both of those to work individually (the replacing and capitalization), but at this point, I just don't know how to make them work together.
def LetterChanges(str)
new_str = str.downcase.split("")
new_str.each do |x|
x.next!
end
new_str.to_s.tr!('aeiou','AEIOU')
return new_str.join("")
end
LetterChanges("abcdef")
new_str.to_s is not stored anywhere. It doesn't affect the original array.
return new_str.join("").tr('aeiou', 'AEIOU')
This will convert the array back to a string and you can operate on that and return it.
That could be resolved with gsub.
"abcdef".gsub(/./){|char| char.next}.gsub(/[aeiou]/){|vowel| vowel.upcase}
#=> "bcdEfg"
so that method could be
def letter_changes_gsub(str)
str.gsub(/./){|char| char.next}.gsub(/[aeiou]/){|vowel| vowel.upcase}
end
That is faster and more simple that work with arrays.
Other answers already showed you how to combine both parts of your code. But there's another issue: String#next is continuing witch "aa" after "z":
"z".next #=> "aa"
You could add an if statement to handle this case:
str.chars.map do |char|
if char == 'z'
'a'
else
char.next
end
end.join
or:
str.chars.map { |char| char == 'z' ? 'a' : char.next }.join
But there's a much simpler way: let String#tr perform the entire substitution:
str.downcase.tr('a-z', 'bcdEfghIjklmnOpqrstUvwxyzA')
Or slightly shorter:
str.downcase.tr('a-z', 'bcdEfghIjk-nOp-tUv-zA')
2.1.0 :012 > 'abcdef'.split('').map(&:next).join.tr('aeiou', 'AEIOU')
=> "bcdEfg"
I would not recommend doing this in one line, of course. But to get at your confusion of how these methods might string together, here is one solution that works. When in doubt, use IRB to call each method and watch how Ruby responds. That will help you figure out where your code is breaking down.
In practice, I would break this into multiple methods. It's too many things for one method to do. And also a lot harder to find bugs (and test), as you found out.
def rotate(string)
string.split('').map(&:next).join
end
def capitalize_vowels(string)
string.tr('aeiou', 'AEIOU')
end
How about:
def string_thing(string)
string.downcase.tr('abcdefghijklmnopqrstuvwxyz','bcdEfghIjklmnOpqrstUvwxyzA')
end
#tr just will replace each character in the first parameter with the corresponding one in the second parameter.
This can be achieved with the combination of gsub and tr:
"abcdef".gsub(/[A-z]/) { |char| char.next }.tr('aeiou', 'AEIOU')
#=> "bcdEfg"
"Fun times!".gsub(/[A-z]/) { |char| char.next }.tr('aeiou', 'AEIOU')
#=> "GvO Ujnft!"
Related
I am having trouble with getting my for-loop to process a string. It's just a simple method to tell whether or not a word is a palindrome (a word that is spelled that same way backwards and forwards). I have tweaked the for-loop multiple times but keep getting the same error message below. Could anyone point me in the right direction?
Code:
def palindrome?(string)
string2 = ""
for i in string
string2 = string[i] + string2
end
if string2 == string1
return true
end
end
palindrome?("abcba")
Error:
hours.rb:7:in `palindrome?': undefined method `each' for 5:Fixnum (NoMethodError)
from hours.rb:17:in `<main>'
The problem is that you can't iterate over a string (like you can for example in Python). You'll first need to convert it to an Array with .split:
for c in string.split
string2 = c + string2
end
That being said, you shouldn't use for loops in Ruby. They're translated internally to an each method, hence your confusing error. It's better to just write each from the get-go:
string.split.each do |c|
string2 = c + string2
end
No Ruby programmer would ever use for in any circumstance, it's only used by people new to Ruby ;-)
Note that the Array.each is just one iteration method; for example there's also the String.each_char method:
string.each_char do |c|
string2 = c + string2
end
Lastly, your code is not correct in several other locations. I'm not going to point out all these errors to you, as it will be much more beneficial and educational for you if you solve this programming exercise yourself ;-)
TL;DR
Aside from being inefficient, your code doesn't work because a String is not an Array, nor does it mix in Enumerator to provide an #each method.
While the String#[] method allows indexing into the string, there is no String#each method to invoke. As a result, you can't use a String object in a Ruby for-loop because it's just syntactic sugar for #each.
Understanding the Exception
I'm not sure what version of Ruby you're running, but the exception you've listed in your post is not reproducible on my system. When running on Ruby 2.3.1, the code generates a fairly clear exception:
for i in string
string2 = string[i] + string2
end
NoMethodError: undefined method `each' for "abcba":String
This is pretty straighforward. It tells you that String has no #each method, which is what the syntactic sugar of for i in string is really invoking under the hood. If you want to iterate, you need some form of Enumerator or Enumerable to work with.
Iterating Over a String
The String class has a number of useful methods for converting a string to an iterable object. Some examples include:
String#each_char can be passed a block, or return an Enumerator
String#chars returns an Array
String#split also returns an Array (e.g. 'abcba'.split //)
For example, your code could be refactored to use a block like so:
string = 'abcba'
tmpstr = ''
string.each_char { |char| tmpstr < char; puts true if tmpstr == 'abcba' }
#=> "abcba"
However, while this highlights how to solve for your exception, it is still needlessly complex and inefficient.
Leveraging Built-In Methods
Unless you're doing this for homework, the right way to do this in Ruby is to leverage the built-in methods that operate at C speeds and don't create temporary Ruby objects that need to be garbage-collected later. For example, to test whether a given string reads the same backwards or forwards, you can simply compare a reversed string to the original using the String#reverse and String#eql? methods.
def palindrome? str
str.reverse.eql? str
end
palindrome? 'abcba'
#=> true
palindrome? 'abcde'
#=> false
You can also use String#== instead of #eql? if you prefer, but I think using the latter is clearer in this case. The method chain makes it clear that you're invoking a String method rather than a bit of language syntax for the comparison. That distinction can be a real help when learning the ins and outs of Ruby's core, but in this case the result will be the same either way.
As Carpetsmoker pointed out, you can't iterate directly over a string. However, Ruby provides both positive and negative indexing for elements. Negative indices are located relative to the end of the array or string. This allows you to do your checking quite efficiently, and short circuit your testing as soon as you identify you don't have a palindrome:
def palindrome?(str)
(0...str.length/2).all? { |i| str[i] == str[-(i+1)] }
end
If you want to be more object-oriented about it, you can convert it to a method in class String:
class String
def palindrome?
(0...length/2).all? { |i| self[i] == self[-(i+1)] }
end
end
p "abcba".palindrome? # => true
Note — Edited to utilize Cary Swoveland's excellent suggestion about using all? rather than an explicit return from the block. This makes it a one-liner.
what you're looking for is:
def palindrome?(string)
string2 = ""
for i in 0...string.length
string2 = string[i] + string2
end
if string2 == string
return true
end
end
Note you could define it simpler:
def palindrome?(string)
string == string.reverse
end
You can write it like this:
def palindrome?(str)
str == str.reverse
end
I am learning methods in Ruby and thought that the best way to learn them was to create a method that already exists. However, there are two problems that I am running in to:
I do not know what the capitalize method looks like
My solution (it does more than the original method does) seems like it can be refactored into something more elegant.
This is what I have come up with:
# method that capitalizes a word
def new_capitalize(string)
if string[0].downcase == "m" && string[1].downcase == "c"
puts "#{string[0].upcase}#{string[1].downcase}#{string[2].upcase}#{string[3..-1].downcase}"
else
puts "#{string[0].upcase}#{string[1..-1].downcase}"
end
end
name1 = "ryan"
name2 = "jane"
new_capitalize(name1) # prints "Ryan"
new_capitalize(name2) # prints "Jane"
str = "mCnealy"
puts str.capitalize
# prints "Mcnealy"
new_capitalize(str)
# prints "McNealy"
It seems as if the first part of my if statement could be made much more efficient. It does not need to be even close to my solution as long as it prints the second capital if the name begins with "mc"
Also, if someone could point me to where the built in capitalize method's code could be found that would be great too!
Thank you in advance!
Alright, how about:
module NameRules
refine String do
def capitalize
if self[0..1].downcase == 'mc'
"Mc#{self[2..-1].capitalize}"
else
super
end
end
end
end
Then to use it:
class ThingWithNames
using NameRules
def self.test(string)
string.capitalize
end
end
ThingWithNames.test('mclemon') # => "McLemon"
ThingWithNames.test('lemon') # => "Lemon"
If we were starting from scratch and not using the C implemented code:
module NameRules
refine String do
def capitalize
if self[0..1].downcase == 'mc'
"Mc#{self[2..-1].capitalize}"
else
new_string = self.downcase
new_string[0] = new_string[0].upcase
new_string
end
end
end
end
Reference materials:
String#capitalize source
A really good presentation on refinements
First, in my opinion, doing anything other than capitalizing the first letter of the string should be a different method or an optional arg you pass. Second, if you are trying to mimic the core lib behavior than you could monkey-patch String.
class String
def capitalize
self[0].upcase << self[1..-1].downcase
end
end
The closest to an official ruby implementation is probably Rubinius
https://github.com/rubinius/rubinius/blob/377d5c958bc8239514fb98701b75859c6b51b9d4/core/string.rb#L332
Looking through this I notice something I have never seen before on line 83.end.map(&:chomp) so end is an object? (I realize that might be 100% wrong.) Can someone explain what and how that works there? What exactly is advantage?
No, end is not an object, but object.some_method do ... end is an object (or rather it's evaluated to an object) - namely the object returned by the some_method method.
So if you do object.some_method do ... end.some_other_method, you're calling some_other_method on the object returned by some_method.
The full code snippet you're referring to is below:
def initialize(dict_file)
#dict_arr = File.readlines(dict_file).select do |word|
!word.include?("-") && !word.include?("'")
end.map(&:chomp)
end
notice that the end you're talking about is the end of the block that starts on the 2nd line (it matches the do on line 2).
Perhaps if you see it parenthesized, and rewritten with curly braces, it will make more sense:
def initialize(dict_file)
#dict_arr = (File.readlines(dict_file).select { |word|
!word.include?("-") && !word.include?("'")
}).map(&:chomp)
end
It's often helpful to examine what Ruby is doing, step-by-step. Let's see what's going with the method ComputerPlayer#initialize:
def initialize(dict_file)
#dict_arr = File.readlines(dict_file).select do |word|
!word.include?("-") && !word.include?("'")
end.map(&:chomp)
end
First, create a file:
File.write("my_file", "cat\ndog's\n")
When we execute:
ComputerPlayer.new("my_file")
the class method IO#readlines is sent to File, which returns an array a:
a = File.readlines("my_file")
#=> ["cat\n", "dog's\n"]
Enumerable#select is sent to the array a to create an enumerator:
b = a.select
#=> #<Enumerator: ["cat\n", "dog's\n"]:select>
We can convert this enumerator to an array to see what it will pass to it's block:
b.to_a
=> ["cat\n", "dog's\n"]
The enumerator is invoked by sending it the method each with a block, and it returns an array c:
c = b.each { |word| !word.include?("-") && !word.include?("'") }
#=> ["cat\n"]
Lastly, we send Enumerable#map with argument &:chomp (the method String#chomp converted to a proc) to the array c:
c.map(&:chomp)
#=> ["cat"]
A final point: you can improve clarity by minimizing the use of !. For example, instead of
...select do |word|
!word.include?("-") && !word.include?("'")
consider
...reject do |word|
word.include?("-") || word.include?("'")
You might also use a regex.
In Ruby, I have an array of simple values (possible encodings):
encodings = %w[ utf-8 iso-8859-1 macroman ]
I want to keep reading a file from disk until the results are valid. I could do this:
good = encodings.find{ |enc| IO.read(file, "r:#{enc}").valid_encoding? }
contents = IO.read(file, "r:#{good}")
...but of course this is dumb, since it reads the file twice for the good encoding. I could program it in gross procedural style like so:
contents = nil
encodings.each do |enc|
if (s=IO.read(file, "r:#{enc}")).valid_encoding?
contents = s
break
end
end
But I want a functional solution. I could do it functionally like so:
contents = encodings.map{|e| IO.read(f, "r:#{e}")}.find{|s| s.valid_encoding? }
…but of course that keeps reading files for every encoding, even if the first was valid.
Is there a simple pattern that is functional, but does not keep reading the file after a the first success is found?
If you sprinkle a lazy in there, map will only consume those elements of the array that are used by find - i.e. once find stops, map stops as well. So this will do what you want:
possible_reads = encodings.lazy.map {|e| IO.read(f, "r:#{e}")}
contents = possible_reads.find {|s| s.valid_encoding? }
Hopping on sepp2k's answer: If you can't use 2.0, lazy enums can be easily implemented in 1.9:
class Enumerator
def lazy_find
self.class.new do |yielder|
self.each do |element|
if yield(element)
yielder.yield(element)
break
end
end
end
end
end
a = (1..100).to_enum
p a.lazy_find { |i| i.even? }.first
# => 2
You want to use the break statement:
contents = encodings.each do |e|
s = IO.read( f, "r:#{e}" )
s.valid_encoding? and break s
end
The best I can come up with is with our good friend inject:
contents = encodings.inject(nil) do |s,enc|
s || (c=File.open(f,"r:#{enc}").valid_encoding? && c
end
This is still sub-optimal because it continues to loop through encodings after finding a match, though it doesn't do anything with them, so it's a minor ugliness. Most of the ugliness comes from...well, the code itself. :/
I am new to ruby and currently trying to operate on each character separately from a base String in ruby. I am using ruby 1.8.6 and would like to do something like:
"ABCDEFG".each_char do |i|
puts i
end
This produces a undefined method `each_char' error.
I was expecting to see a vertical output of:
A
B
C
D
..etc
Is the each_char method defined only for 1.9? I tried using the plain each method, but the block simply ouputs the entire string in one line. The only way I figure how to do this, which is rather inconvenient is to create an array of characters from the begining:
['A','B','C','D','...'].each do|i|
puts i
end
This outputs the desired:
A
B
C
..etc
Is there perhaps a way to achive this output using an unmodified string to begin with?
I think the Java equivalent is:
for (int i = 0; i < aString.length(); i++){
char currentChar = aString.charAt(i);
System.out.println(currentChar);
}
I have the same problem. I usually resort to String#split:
"ABCDEFG".split("").each do |i|
puts i
end
I guess you could also implement it yourself like this:
class String
def each_char
self.split("").each { |i| yield i }
end
end
Edit: yet another alternative is String#each_byte, available in Ruby 1.8.6, which returns the ASCII value of each char in an ASCII string:
"ABCDEFG".each_byte do |i|
puts i.chr # Fixnum#chr converts any number to the ASCII char it represents
end
Extending la_f0ka's comment, esp. if you also need the index position in your code, you should be able to do
s = 'ABCDEFG'
for pos in 0...s.length
puts s[pos].chr
end
The .chr is important as Ruby < 1.9 returns the code of the character at that position instead of a substring of one character at that position.
"ABCDEFG".chars.each do |char|
puts char
end
also
"ABCDEFG".each_char {|char| p char}
Ruby version >2.5.1
there is really a problem in 1.8.6.
and it's ok after this edition
in 1.8.6,you can add this:
requre 'jcode'
But now you can do much more:
a = "cruel world"
a.scan(/\w+/) #=> ["cruel", "world"]
a.scan(/.../) #=> ["cru", "el ", "wor"]
a.scan(/(...)/) #=> [["cru"], ["el "], ["wor"]]
a.scan(/(..)(..)/) #=> [["cr", "ue"], ["l ", "wo"]]
Returns an array of characters in str. This is a shorthand for str.each_char.to_a. If a block is given, which is a deprecated form, works the same as each_char.
from ruby-doc.org
also now you can do string.chars