Capitalizing words in an array, Ruby - ruby

I'm going through App Academy's Ruby Prep questions, and I want to know why this solution works. It appears that the words array is never altered and yet the method works. Is this a glitch in the matrix, or is it right under my nose?
def capitalize_words(string)
words = string.split(" ")
idx = 0
while idx < words.length
word = words[idx]
word[0] = word[0].upcase
idx += 1
end
return words.join(" ")
end

The method works because word contains a reference to the array position. So when you assign:
word = words[idx]
You're just using word as a shorthand to operate on that array element, which gets modified by:
word[0] = word[0].upcase
--
Also, if you'd like to come back to this answer after learning some Ruby, here's a simplified version of the method:
def capitalize_words(string)
string.split.map(&:capitalize).join(' ')
end

String#[]= is a mutating operation. To illustrate using a concise, contained excerpt from your code:
word = "foo"
word[0] = word[0].upcase # <-- verbatim from your code
word #=> "Foo"
word is still the same exact object contained in the array words (arrays simply contain references to objects, not the data within them), but it has been mutated in-place. It’s generally best to avoid mutations whenever possible as it makes it non-obvious what is happening (as you can see).
Your code could also be more concisely written using map & capitalize (and without any mutations):
string.split(' ').map(&:capitalize).join(' ')

word = word[idx] creates a copy of your data. It will then modify that copy instead of the words in the original array.
Simple solution would be:
def capitalize_words(string)
words = string.split(" ")
idx = 0
while idx < words.length
words[idx][0] = words[idx][0].upcase
idx += 1
end
return words.join(" ")
end

Related

Could someone explain line 2 to line 9 of this Ruby code?

def caesar_cipher(string, shift_factor)
string.length.times do |i|
if string[i].ord >= 97 && (string[i].ord + shift_factor) <= 122 || string[i].ord >= 65 && (string[i].ord + shift_factor) <= 90
string[i] = (string[i].ord + shift_factor).chr
elsif string[i].ord >= 97 && string[i].ord <= 122 || string[i].ord >= 65 && string[i].ord <= 90
string[i] = (string[i].ord + shift_factor - 122 + 96).chr
end
end
string
end
puts "Enter a string:"
string_input = gets.chomp
puts "Enter shift factor:"
shift_factor_input = gets.chomp.to_i
result_string = caesar_cipher(string_input, shift_factor_input)
puts result_string
https://github.com/OlehSliusar/caesar_cipher
A command line Caesar Cipher that takes in a string and the shift factor and then outputs the modified string.
I am unable to understand code line 2 to line 9. I am confused on how the .times method is used in this context. Could someone explain to me what is he doing from line 2 to line 9? How I understand .times method is that it act as a iterator as iterate based on the number time stated.
So say 5.times { puts "Dog" } = > will result in putting "Dog" five time. Hence my understanding on the method .times is very different from the way the author used it.
This is an extended comment which does not answer the question (so no upvotes please).
That piece of code is ugly and arcane, not at all Ruby-like. Here's a another way that makes better use of Ruby's tools and is effectively self-documenting.
Code
def caesar_cipher_encrypt(string, shift_size)
mapping = Hash.new { |h,k| k }.
merge(make_map('a', shift_size)).
merge(make_map('A', shift_size))
string.gsub(/./, mapping)
end
def make_map(first_char, shift_size)
base = first_char.ord
26.times.with_object({}) { |i,h| h[(base+i).chr] = (base+((i+shift_size) % 26)).chr }
end
Example
shift_size = 2
encrypted_str = caesar_cipher_encrypt("Mary said to Bob, 'Get lost!'.", shift_size)
#=> "Octa uckf vq Dqd, 'Igv nquv!'."
Explanation
The first step is to create a hash that maps letters into their shifted counterparts. We begin with
h = Hash.new { |h,k| k }
#= {}
This creates an empty hash with a default value given by the block. That means that if h does not have a key k, h[k] returns k. Since all keys of 'h' will be letters, this means the value of a digit, space, punctuation mark or any other non-letter will be itself. See Hash::new.
We then have
f = make_map('a',2)
#=> {"a"=>"c", "b"=>"d", "c"=>"e",..., "x"=>"z", "y"=>"a", "z"=>"b"}
g = h.merge(f)
#=> {"a"=>"c", "b"=>"d", "c"=>"e",..., "y"=>"a", "z"=>"b"}
f = make_map('A',2)
#=> {"A"=>"C", "B"=>"D", "C"=>"E",..., "X"=>"Z", "Y"=>"A", "Z"=>"B"}
mapping = g.merge(f)
#=> {"a"=>"c", "b"=>"d", "c"=>"e",..., "y"=>"a", "z"=>"b",
# "A"=>"C", "B"=>"D", "C"=>"E",..., "Y"=>"A", "Z"=>"B"}
mapping['!']
#=> "!"
We may now simply use the form of String#gsub that uses a hash to perform substitutions.
"Mary said to Bob, 'Get lost!'.".gsub(/./, mapping)
#=> "Octa uckf vq Dqd, 'Igv nquv!'."
Decrypting
The receiver of an encrypted message can decrypt it as follows.
def caesar_cipher_decrypt(string, shift_size)
caesar_cipher_encrypt(string, -shift_size)
end
caesar_cipher_decrypt(encrypted_str, shift_size)
#=> "Mary said to Bob, 'Get lost!'."
.times do means "execute this code a certain number of times" as you said.
.times do |i| loops a certain number of times and counts each time in i
string.length gets the number of characters in the string.
string.length.times executes a block of code a number of times equal to the number of characters in the string.
string[i] accesses the i-th character in the string.
Putting it all together:
string.length.times do |i|
do_stuff_with string[i]
end
you have code which iterates through each character in the string and does something to it. in this case, the code shifts each character according to caesar cipher.
When you use iterators like string.each_char in ruby or foreach(item in items) in other languages, you're not generally allowed to modify the collection while you iterate. Using .times and string[i] lets the code modify the string while it iterates. because the loop doesn't keep track of string, it just knows that it needs to execute some number of times.
As others have pointed out, there are more elegant, more ruby-like ways to do what this code does, but the writer of the code chose .times because it acts just like a for-loop, which is a common programming paradigm.
Perhaps this will explain it:
string = 'foo'
string.length.times {|i| puts string[i]}
Its a way of iterating through each letter in the string. They could probably do the same thing via:
string.chars.collect{|character| p(character)}.join
and have cleaner code as a result (where p(character) would be replaced by the required manipulation of the current character)
For example this code:
'foo'.chars.collect{|c| (c.ord + 1).chr}.join
Iterates through the string and returns a new string with each character replaced with the next one in the alphabet. That is: "gpp"

Ruby: Undefined method 'length' for nil:NilClass (NoMethodError)

I am attempting to write a program that takes in a string and outputs the longest word in that string. Now, I know that my code looks pretty hairy but I am pretty new to the Ruby language so please just bear with me. I don't understand any of the other explanations given regarding this issue. I am not looking for the answer. All I want is for a kind human being to please explain to me why my program halts at line 16 with the problem stated in the title of this question. Please and thank you!
# longest_word.rb
# A method that takes in a string and returns the longest word
# in the string. Assume that the string contains only
# letters and spaces. I have used the String 'split' method to
# aid me in my quest. Difficulty: easy.
def longest_word(sentence)
array = sentence.split(" ")
idx = 0
ordered_array = []
puts(array.length)
while idx <= array.length
if (array[idx].length) < (array[idx + 1].length)
short_word = array[idx]
ordered_array.push(short_word)
idx += 1
elsif array[idx].length > array[idx + 1].length
long_word = array[idx]
ordered_array.unshift(long_word)
idx += 1
else l_w = ordered_array[0]
return l_w
end
end
end
puts("\nTests for #longest_word")
puts(longest_word("hi hello goodbye"))
At some point in your while loop, you come to a state where idx is pointing to the last item in the array. At that point, asking for array[idx+1] returns nil, and NilClass does not have a method 'length'
The simple fix would be to change the while loop condition so that idx+1 is always within the array.
May I recommend a shorter solution.
words1= "This is a sentence." # The sentence
words2 = words1.split(/\W+/) # splits the words by via the space character
words3 = words2.sort_by {|x| x.length} #sort the array
words3[-1] #gets the last word
or
def longest_word(sentence)
sentence.split(/\W+/).sort_by {|x| x.length}[-1]
end

Reversing a Ruby String, without .reverse method

I am working on this coding challenge, and I have found that I am stuck. I thought it was possible to call the .string method on an argument that was passed in, but now I'm not sure. Everything I've found in the Ruby documentation suggests otherwise. I'd really like to figure this out without looking at the solution. Can someone help give me a push in the right direction?
# Write a method that will take a string as input, and return a new
# string with the same letters in reverse order.
# Don't use String's reverse method; that would be too simple.
# Difficulty: easy.
def reverse(string)
string_array = []
string.split()
string_array.push(string)
string_array.sort! { |x,y| y <=> x}
end
# These are tests to check that your code is working. After writing
# your solution, they should all print true.
puts(
'reverse("abc") == "cba": ' + (reverse("abc") == "cba").to_s
)
puts(
'reverse("a") == "a": ' + (reverse("a") == "a").to_s
)
puts(
'reverse("") == "": ' + (reverse("") == "").to_s
)
This is the simplest one line solution, for reversing a string without using #reverse, that I have come across -
"string".chars.reduce { |x, y| y + x } # => "gnirts"
Additionally, I have never heard of the #string method, I think you might try #to_s.
Easiest way to reverse a string
s = "chetan barawkar"
b = s.length - 1
while b >= 0
print s[b]
b=b-1
end
You need to stop the search for alternative or clever methods, such as altering things so you can .sort them. It is over-thinking the problem, or in some ways avoiding thinking about the core problem you have been asked to solve.
What this test is trying to get you you to do, is understand the internals of a String, and maybe get an appreciation of how String#reverse might be implemented using the most basic string operations.
One of the most basic String operations is to get a specific character from the string. You can get the first character by calling string[0], and in general you can get the nth character (zero-indexed) by calling string[n].
In addition you can combine or build longer strings by adding them together, e.g. if you had a="hell" and b="o", then c = a + b would store "hello" in the variable c.
Using this knowledge, find a way to loop through the original string and use that to build the reverse string, one character at a time. You may also need to look up how to get the length of a string (another basic string method, which you will find in any language's string library), and how to loop through numbers in sequence.
You're on the right track converting it to an array.
def reverse(str)
str.chars.sort_by.with_index { |_, i| -i }.join
end
Here is a solution I used to reverse a string without using .reverse method :
#string = "abcde"
#l = #string.length
#string_reversed = ""
i = #l-1
while i >=0 do
#string_reversed << #string[i]
i = i-1
end
return #string_reversed
Lol, I am going through the same challenge. It may not be the elegant solution, but it works and easy to understand:
puts("Write is a string that you want to print in reverse")
#taking a string from the user
string = gets.to_s #getting input and converting into string
def reverse(string)
i = 0
abc = [] # creating empty array
while i < string.length
abc.unshift(string[i]) #populating empty array in reverse
i = i + 1
end
return abc.join
end
puts ("In reverse: " + reverse(string))
Thought i'd contribute my rookie version.
def string_reverse(string)
new_array = []
formatted_string = string.chars
new_array << formatted_string.pop until formatted_string.empty?
new_array.join
end
def reverse_str(string)
# split a string to create an array
string_arr = string.split('')
result_arr = []
i = string_arr.length - 1
# run the loop in reverse
while i >=0
result_arr.push(string_arr[i])
i -= 1
end
# join the reverse array and return as a string
result_arr.join
end

Swap adjacent elements in array

I am trying to build a method in Ruby that will take in a string that has been split into an array of letters and then iterate through the array, swapping the element at index n with that at index n+1. The method will then join the new array into a string and push it to another array.
Here is an example of what I am looking to do:
string = "teh"
some_method(string)
some ruby magic here
array << new_string
end
Expected output:
["eth", "the"]
This is for a spell checker program I am writing for school. The method will check if letters in a misspelled word are swapped by checking to see if the output array elements are in the dictionary. If they are, it will return the word with that is most likely the correct word. I haven't had any luck finding articles or documentation on how to build such a method in ruby or on an existing method to do this. I've been tinkering with building this method for awhile now but my code isn't behaving anything like what I need. Thanks in advance!
As #Sergio advised, you want to use parallel assignment for this:
def reverse_em(str)
(0...str.size-1).map do |i|
s = str.dup
s[i], s[i+1] = s[i+1], s[i]
s
end
end
candidates = reverse_em "alogrithm"
#=> ["laogrithm", "aolgrithm", "algorithm", "alorgithm",
# "alogirthm", "alogrtihm", "alogrihtm", "alogritmh"]
dictionary_check(candidates)
#=> algorithm
# al·go·rithm
# noun \ˈal-gə-ˌri-thəm\
# a set of steps that are followed in order to solve a
# mathematical problem or to complete a computer process
Without splitting it into arrays then joining to new arrays (because that doesn't seem necessary):
def some_method(string)
swapped_strings = []
(0...string.size-1).each do |i|
temp_string = string.dup
temp_string[i], temp_string[i+1] = temp_string[i+1], temp_string[i]
swapped_strings << temp_string
end
swapped_strings
end

How do I make multiple combinations with a string in ruby?

Input should be a string:
"abcd#gmail.com"
Output should be an Array of strings:
["abcd#gmail.com",
"a.bcd#gmail.com",
"ab.cd#gmail.com",
"abc.d#gmail.com",
"a.b.cd#gmail.com",
"a.bc.d#gmail.com",
"a.b.c.d#gmail.com"]
The idea: "Make every possible combination in the first string part ("abcd") with a dot. Consecutive dots are not allowed. There are no dots allowed in the beginning and in the end of the first string part ("abcd")"
This is what I've came up with so far:
text,s = "abcd".split""
i=0
def first_dot(text)
text.insert 1,"."
end
def set_next_dot(text)
i = text.rindex(".")
text.delete_at i
text.insert(i+1,".")
end
My approach was
write a function, that sets the first dot
write a function that sets the next dot
...(magic)
I do not know how to put the pieces together. Any Idea? Or perhaps a better way?
thanx in advance
edit:
I think I found the solution :)
I will post it in about one hour (it's brilliant -> truth tables, binary numbers, transposition)
...and here the solution
s = "abc"
states = s.length
possibilites = 2**states
def set_space_or_dot(value)
value.gsub("0","").gsub("1",".")
end
def fill_with_leading_zeros(val, states)
if val.length < states
"0"*(states-val.length)+val
else
val
end
end
a = Array.new(possibilites,s)
a = a.map{|x| x.split ""}
b = [*0...possibilites].map{|x| x.to_s(2).to_s}
b = b.map{|x| fill_with_leading_zeros x,states}
b = b.map{|x| x.split ""}
c = []
for i in 0 ... a.size
c[i] = (set_space_or_dot (a[i].zip b[i]).join).strip
end
Changing pduersteler answer a little bit:
possibilities = []
string = "abcd#example.com"
(string.split('#')[0].size-1).times do |pos|
possibility = string.dup
possibilities << possibility.insert(pos+1, '.')
end
How about this (probably needs a bit more fine-tuning to suit your needs):
s = "abcd"
(0..s.size-1).map do |i|
start, rest = [s[0..i], s[(i+1)..-1]]
(0..rest.size-1).map { |j| rest.dup.insert(j, '.') }.map { |s| "#{start}#{s}"}
end.flatten.compact
#=> ["a.bcd", "ab.cd", "abc.d", "ab.cd", "abc.d", "abc.d"]
An option would be to iterate n times through your string moving the dot, where n is the amount of chars minus 1. This is what you're doing right now, but without defining two methods.
Something like this:
possibilities = []
string = "abcd#example.com"
(string.split('#')[0].size-1).times do |pos|
possibilities << string.dup.insert(pos+1, '.')
end
edit
Now tested. THanks to the comments, you need to call .dup on the string before the insert. Otherwise, the dot gets inserted into the string and will stay there for each iteration causing a mess. Calling .dup onthe string will copy the string and works on the copy instead, leaving the original string untouched.

Resources