I'm trying to create a sorting algorithm without the sorting function in Ruby. I've based it of the idea of insertion sort. The idea is that the function checks whether the nth value of each two words are same, and if so n increases by one until one value is larger than the other. In that case the words might get switched. However, my function keeps freezing. Any ideas why?
words = ["my","favorite","animal", "are", "the", "elephant", "and", "the", "antelope", "and", "the", "favela"]
#Convert all letters into numbers. Output individual words as arrays.
converted_words = words.map(&:chars).map { |letters| letters.map { |letter| letter.to_i 36 } }
puts converted_words.to_s
i = 1
x = 0
while i < converted_words.length
if converted_words[i][x] == converted_words[i-1][x]
x = x + 1
else
if converted_words[i][x] < converted_words[i-1][x]
converted_words[i], converted_words[i-1] = converted_words[i-1], converted_words[i]
i = 0
x = 0
else
i = i + 1
end
end
end
puts converted_words.to_s
Your code does not "freeze"; running it raises this exception:
NoMethodError (undefined method '<' for nil:NilClass)
in the line:
if converted_words[i][x] < converted_words[i-1][x]
We immediately see the problem, though the cause is not yet known. The receiver of the method < is converted_words[i][x]. As the error message says that nil does not have a method <, we infer that converted_words[i][x] is nil.1 That means that an index is out-of-range (examples of an index being out-of-range are [1,2][412] #=> nil and [1,2][-3] #=> nil). If i were out-of-range the expression would reduce to nil[x] < ..., which would raise an exception that nil does not have a method NilClass#\[\]]. That's not our exception message so we conclude that x must be out-of-range.
To see why this is happening, suppose:
words = ["a", "ab"]
Then
converted_words =
words.map(&:chars).map { |letters| letters.map { |letter| letter.to_i 36 } }
#=> [[10], [10, 11]]
i = 1
x = 0
while i < converted_words.length
#=> while 1 < 2 => while true, so enter the loop
if converted_words[i][x] == converted_words[i-1][x]
#=> if converted_words[1][0] == converted_words[0][0] => if 10 == 10 => true
so execute
x = x + 1
#=> x = 0 + 1 => 1
and attempt to repeat the loop.
while i < converted_words.length
#=> while 1 < 2 => while true, so repeat the loop
if converted_words[i][x] == converted_words[i-1][x]
#=> if converted_words[1][1] == converted_words[0][1] => if 11 == nil => false
so execute (else).
if converted_words[i][x] < converted_words[i-1][x]
#=> converted_words[0][1] < converted_words[-1][1] => if nil < 11
#=> NoMethodError (undefined method '<' for nil:NilClass)
Error messages contain valuable information. Study them carefully!
1 The error message "nil has no method <" is here equivalent to, "NilClass has no instance method <".
I believe I have solved the issue. Thank you for all your help.
I reordered my algorithm: First checking if converted_words[i][x] < converted_words[i-1][x], and then checking if converted_words[i][x] == converted_words[i-1][x].
I also need to check whether if converted_words[i][x] != nil && converted_words[i-1][x] != nil, in order to avoid a NoMethodError (thanks Cary Swoveland).
Finally I combined the two algorithms.
I also realized that I did not need to convert the letters to numbers, as ruby knows which letters are larger. So instead, I left the characters as letters.
I'm aware that the code is not very efficent. If you have any suggestions on how to improve or simplify the algorithm, I would be happy to hear them.
Here's the code:
words = ["my","favorite","animals", "are", "the", "elephant", "and", "the", "antelope", "and", "the", "favela"]
puts words.to_s
#Convert all letters into numbers. Output individual words as arrays.
ordered_words = words.map(&:chars).map { |letters| letters.map { |letter| letter } }
i = 1
x = 0
while i < ordered_words.length
if ordered_words[i][x] != nil && ordered_words[i-1][x] != nil
if ordered_words[i][x] < ordered_words[i-1][x]
ordered_words[i], ordered_words[i-1] = ordered_words[i-1], ordered_words[i]
i = 1
x = 0
else
if ordered_words[i][x] == ordered_words[i-1][x]
x = x + 1
else
i = i + 1
x = 0
end
end
else
if ordered_words[i][x] == nil && ordered_words[i-1][x] == nil
i = i + 1
x = 0
else
if ordered_words[i][x] == nil
ordered_words[i], ordered_words[i-1] = ordered_words[i-1], ordered_words[i]
i = 1
x = 0
else
i = i + 1
x = 0
end
end
end
end
joined_words = []
ordered_words.each do |word|
joined_words.push(word.join)
end
puts joined_words.to_s
Related
I'm trying to work through a level 5 kata by using while loops. Essentially the problem is to turn each letter rotors[n] number of times and then move on to the next rotors number until you get an output word.
flap_display(["CAT"],[1,13,27])
should output ["DOG"]
Here's what I have so far
def flap_display(lines, rotors)
stuff = "ABCDEFGHIJKLMNOPQRSTUVWXYZ?!##&()|<>.:=-+*/0123456789"
i = 0
j = 0
new_word = lines
while i < rotors.length
while j < new_word[0].length
new_word[0][j] = stuff[stuff.index(new_word[0][j]) + rotors[i]]
j += 1
end
i += 1
j = 0
end
new_word
end
This technically traverses the stuff string and assigns the right letters. However it fails two important things: it does not skip each letter when it rotates to the correct position (C should stop rotating when it hits D, A when it hits O etc) and it does not account for reaching the end of the stuff list and eventually returns a nil value for stuff[stuff.index(new_word[0][j]) + rotors[i]]. How can I fix these two problems using basic loops and enumerables or maybe a hash?
A fuller statement of the problem is given here. This is one Ruby-like way it could be done.
FLAPS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ ?!##&()|<>.:=-+*/0123456789"
NBR_FLAPS = FLAPS.size
def flap_display(str, rot)
rot_cum = rot.each_with_object([]) { |n,a| a << a.last.to_i + n }
str.gsub(/./) { |c| FLAPS[(c.ord + rot_cum.shift - 65) % NBR_FLAPS] }
end
flap_display("CAT", [1,13,27])
#=> "DOG"
flap_display("DOG", [-1,-13,-27])
#=> "CAT"
flap_display("CAT", [5,37,24])
#=> "H*&"
'A'.ord #=> 65 and rot_cum contains the cumulative values of rot:
arr = [1, 13, 27]
rot_cum = arr.each_with_object([]) { |n,a| a << a.last.to_i + n }
#=> [1, 14, 41]
I've written a.last.to_i rather than a.last to deal with the case where a is empty, so a.last #=> nil, meaning a.last.to_i => nil.to_i => 0. See NilClass#to_i. Those opposed to such trickery could write:
rot_cum = arr.drop(1).each_with_object([arr.first]) { |n,a| a << a.last + n }
def encrypt(string)
alphabet = ("a".."b").to_a
result = ""
idx = 0
while idx < string.length
character = string[idx]
if character == " "
result += " "
else
n = alphabet.index(character)
n_plus = (n + 1) % alphabet.length
result += alphabet[n_plus]
end
idx += 1
end
return result
end
puts encrypt("abc")
puts encrypt("xyz")
I'm trying to get "abc" to print out "bcd" and "xyz" to print "yza". I want to advance the letter forward by 1. Can someone point me to the right direction?
All I had to do was change your alphabet array to go from a to z, not a to b, and it works fine.
def encrypt(string)
alphabet = ("a".."z").to_a
result = ""
idx = 0
while idx < string.length
character = string[idx]
if character == " "
result += " "
else
n = alphabet.index(character)
n_plus = (n + 1) % alphabet.length
result += alphabet[n_plus]
end
idx += 1
end
return result
end
puts encrypt("abc")
puts encrypt("xyz")
Another way to solve the issue, that I think is simpler, personally, is to use String#tr:
ALPHA = ('a'..'z').to_a.join #=> "abcdefghijklmnopqrstuvwxyz"
BMQIB = ('a'..'z').to_a.rotate(1).join #=> "bcdefghijklmnopqrstuvwxyza"
def encrypt(str)
str.tr(ALPHA,BMQIB)
end
def decrypt(str)
str.tr(BMQIB,ALPHA)
end
encrypt('pizza') #=> "qjaab"
decrypt('qjaab') #=> "pizza"
Alternatively if you don't want to take up that memory storing the alphabet you could use character codings and then just use arithmetic operations on them to shift the letters:
def encrypt(string)
result = ""
idx = 0
while idx < string.length
result += (string[idx].ord == 32 ? (string[idx].chr) : (string[idx].ord+1).chr)
idx += 1
end
result
end
Other strange thing about ruby is that you do not need to explicitly return something at the end of the method body. It just returns the last thing by default. This is considered good style amongst ruby folks.
Your question has been answered, so here are a couple of more Ruby-like ways of doing that.
Use String#gsub with a hash
CODE_MAP = ('a'..'z').each_with_object({}) { |c,h| h[c] = c < 'z' ? c.next : 'a' }
#=> {"a"=>"b", "b"=>"c",..., "y"=>"z", "z"=>"a"}
DECODE_MAP = CODE_MAP.invert
#=> {"b"=>"a", "c"=>"b",..., "z"=>"y", "a"=>"z"}
def encrypt(word)
word.gsub(/./, CODE_MAP)
end
def decrypt(word)
word.gsub(/./, DECODE_MAP)
end
encrypt('pizza')
#=> "qjaab"
decrypt('qjaab')
#=> "pizza"
Use String#gsub with Array#rotate
LETTERS = ('a'..'z').to_a
#=> ["a", "b", ..., "z"]
def encrypt(word)
word.gsub(/./) { |c| LETTERS.rotate[LETTERS.index(c)] }
end
def decrypt(word)
word.gsub(/./) { |c| LETTERS.rotate(-1)[LETTERS.index(c)] }
end
encrypt('pizza')
#=> "qjaab"
decrypt('qjaab')
#=> "pizza"
I want to find the next largest integer out of integers that consist of the characters in a given integer. If the given integer is the biggest, return -1.
def next_bigger(n)
perm = n.to_s.chars.sort.permutation.to_a.uniq
to_return = []
perm.each do |x|
to_return.push(x.join)
end
tracker = to_return.find_index(n.to_s)
if to_return[tracker + 1] != nil
return to_return[tracker + 1].to_i
else
-1
end
end
This code works. I don't know how to make it lighter. Right now it takes forever to run. Where would you start?
You can use recursion to obtain a highly efficient procedure.
Code
def next_largest(n)
nxt = nl(n.to_s.chars.map(&:to_i))
return nil if nxt.nil?
nxt.map(&:to_s).join.to_i
end
def nl(arr, remaining_digits=arr.sort)
if arr.size == 1
return (remaining_digits.first > arr.first) ? remaining_digits : nil
end
first = arr.first
remaining_arr = arr.drop(1)
remaining_digits.each_index do |i|
d = remaining_digits[i]
rest =
case i
when 0 then remaining_digits.drop(1)
when remaining_digits.size-1 then remaining_digits[0..-2]
else [*remaining_digits[0..i-1], *remaining_digits[i+1..-1]]
end
return [d, *rest] if d > first
if d == first
arr = nl(remaining_arr, rest)
return [d, *arr] if arr
end
end
nil
end
Examples
(1..10000).to_a.sample(10).sort.each do |n|
v = next_largest(n)
print "%4d => " % n
puts(v ? ("%4d" % v) : "No next number")
end
647 => 674
1137 => 1173
4010 => 4100
4357 => 4375
6542 => No next number
6832 => 8236
6943 => 9346
7030 => 7300
8384 => 8438
9125 => 9152
next_largest(613_492_385_167)
#=> 613492385176
All of these calculations took a small fraction of a second.
Explanation
(to be provided as time permits...)
# Write a method that takes in a string. Your method should return the
# most common letter in the array, and a count of how many times it
# appears.
#
# Difficulty: medium.
def most_common_letter(string)
letter = 0
letter_count = 0
idx1 = 0
mostfreq_letter = 0
largest_letter_count = 0
while idx1 < string.length
letter = string[idx1]
idx2 = 0
while idx2 < string.length
if letter == string[idx2]
letter_count += 1
end
idx2 += 1
end
if letter_count > largest_letter_count
largest_letter_count = letter_count
mostfreq_letter = letter
end
idx1 += 1
end
return [mostfreq_letter, largest_letter_count]
end
# These are tests to check that your code is working. After writing
# your solution, they should all print true.
puts(
'most_common_letter("abca") == ["a", 2]: ' +
(most_common_letter('abca') == ['a', 2]).to_s
)
puts(
'most_common_letter("abbab") == ["b", 3]: ' +
(most_common_letter('abbab') == ['b', 3]).to_s
)
So in my mind the program should set a letter and then once that is set cycle through the string looking for letters that are the same, and then once there is one it adds to letter count and then it judges if its the largest letter count and if it is those values are stored to the eventual return value that should be correct once the while loop ends. However I keep getting false false. Where am I going wrong?
Your code does not return [false, false] to me; but it does return incorrect results. The hint by samgak should lead you to the bug.
However, for a bit shorter and more Rubyish alternative:
def most_common_letter(string)
Hash.new(0).tap { |h|
string.each_char { |c| h[c] += 1 }
}.max_by { |k, v| v }
end
Create a new Hash that has a default value of 0 for each entry; iterate over characters and count the frequency for each of them in the hash; then find which hash entry is the largest. When a hash is iterated, it produces pairs, just like what you want for your function output, so that's nice, too.
Im trying to write a loop that counts the number of capitals in a word. When I run this code I get an error stating that nil can't be compared to a string. I don't understand this because I am comparing a a string to a string?
passwort = gets.chomp
i = 0
capitals = 0
while(i < (passw0ort.length) + 1) do
if('A' <= passwort[i] && passwort[i] <= 'Z')
capitals = capitals + 1
else
end
i = i + 1
end
Greetings Patrick
The reason why you are getting nil is that you are counting more elements than there are.
i<(passw0ort.length)+1 should be i<(passw0ort.length), because .length returns a number bigger than zero, if the string is not empty, while arrays are zero-indexed. The last time the loop runs, you try to access an element after the string, which does not exist.
So on your last loop iteration, you are comparing a string to nil, which results in this error.
Just use scan method of String class to filter out only capital letters
capitals = passwort.scan(/[A-Z]/).count
Your loop is too "long":
passwort = "test"
i = 0
while i < passwort.length + 1
puts "passwort[#{i}] == #{passwort[i].inspect}"
i = i + 1
end
Output:
passwort[0] == "t"
passwort[1] == "e"
passwort[2] == "s"
passwort[3] == "t"
passwort[4] == nil # <- this is your nil error
You can fix this by changing i < passwort.length + 1 to i < passwort.length.
However, instead of using temporary variables and while loops, you can use String#chars to get the string's characters as an array and Array#count to count the ones satisfying your condition:
passwort = "123fooBAR"
passwort.chars.count { |char| 'A' <= char && char <= 'Z' }
#=> 3
Or simply String#count:
passwort.count("A-Z") #=> 3
From the docs:
The sequence c1-c2 means all characters between c1 and c2.