So,my question was, Why am I getting NoMethodError regarding sort method in function next_bigger when being called ?
Here's the code :
class String
def sort
self.chars.sort.join
end
end
def next_bigger n
s = n.to_s
return -1 if n <= 10 || s == s.sort.reverse #This line resulting NoMethodError
(2..n).each do |p|
x = (s[0...-(p)] + s[-(p)..-1].sort).to_i
return x if x > n
end
-1
end
p next_bigger 12
You're not getting the NoMethodError on the line you think you are. It's happening 2 lines later. The other place where you call sort.
x = (s[0...-(p)] + s[-(p)..-1].sort).to_i
If you index a range of a string outside it's bounds, you get nil
""[1..-1] #=> nil
You get NoMethodError because you're calling sort on that nil.
Related
Can you please tell me why it is passing nil to check method? I am getting error main.rb:5:in `check': undefined method `%' for nil:NilClass (NoMethodError)
my_array = Array.new
$output = String.new
def check(n)
if n%3 == 0
$output = $output + 'Pop '
elsif n.even?
$output = $output + 'Crackle '
elsif n.odd?
$output = $output + 'Snap '
end
end
for x in 1..6
my_array[x] = gets.chomp.to_i
end
my_array.each { |x| check(x) }
puts $output.my_array
The reason you are getting a nil in the beginning of the array is that you are manually setting the keys in the array which creates a hole since arrays are 0 indexed in Ruby:
ary = Array.new
ary[1] = "a"
ary[2] = "b"
ary[3] = "c"
# => [nil, "a", "b", "c"]
While you could salvage this code with:
my_array = Array.new
$output = String.new
def check(n)
if n%3 == 0
$output = $output + 'Pop '
elsif n.even?
$output = $output + 'Crackle '
elsif n.odd?
$output = $output + 'Snap '
end
end
for x in 0..5
my_array[x] = gets.chomp.to_i
end
my_array.each { |x| check(x) }
puts $output.my_array
A more idiomatically correct way to write this in Ruby is:
str = 5.times.map do
n = gets.chomp.to_i
if n%3 == 0
'Pop'
elsif n.even?
'Crackle'
elsif n.odd?
'Snap'
end
end.join(" ")
puts str
for String.new and Array.new are rarely used if ever used. Use blocks instead of methods unless you're planning to reuse it later. In Ruby you can use the methods from Enumerable to both iterate over and transform arrays, hashes, ranges and other types of objects so there rarely is a reason to iterate and modify an external variable like in other languages.
With for x in 0..5 you would then have
t.rb:21:in `<main>': undefined method `my_array' for "":String (NoMethodError)
because my_array is not a method that you can send to $output.
There are many ways to do the same thing in Ruby.
my_array = []
def check(n)
case
when n % 3 == 0
'Pop'
when n.even?
'Crackle'
when n.odd?
'Snap'
else 'boom !' # not necessary in this particular case
end
end
(1..6).each do | i |
print "#{i} Enter a number > "
my_array << gets.to_i
end
puts my_array.collect { |e| check(e) }.join(' ')
Execution :
$ ruby t.rb
1 Enter a number > 44
2 Enter a number > 66
3 Enter a number > 30494
4 Enter a number > 383849
5 Enter a number > 2234
6 Enter a number > 4333
Crackle Pop Crackle Snap Crackle Snap
Don't use global variables, like $output. In the ancient (imperative programming style) languages, it was a common bug to inadvertently modify a variable accessible from anywhere.
The object oriented paradigm has been invented to isolate variables (encapsulated in an
object) to make it more difficult to modify them accidentally.
You could have use an instance variable :
#output = ''
if n%3 == 0
#output << 'Pop '
but beeing defined in the special 'main' object, it is not protected against unwanted access.
chomp is not necessary before to_i, see this post
Use iterators instead of loops. for is imperative style (C, Java), which imposes you to manage
the begin and end indexes. In an object oriented language, you simply send an iterate message to a
collection object, which takes cares of the internal details.
if and case are expressions which return the last computed value. check() returns that value.
Your my_array.each { |x| check(x) } mutates the variable $output and returns no result. In a big program, a later maintenance could insert some processing that modifies $output before you use it (bug).
The functional programming paradigm (Scala, Elixir, Kotlin) tends to use immutable variables and use functions to transform data.
The new my_array.collect { |e| check(e) }.join(' ') iterates over my_array, transforms each element calling the function check(), produces a new (immutable) collection with these transformed elements, which is then transformed by the function join() to produce the final result.
You have
for x in 1..6
my_array[x] = gets.chomp.to_i
end
Which populates the array from indexes 1 through 6, all arrays begin at index 0 so, in your method
my_array.each { |x| check(x) }
The .each method will iterate through each element of the array, starting at 0, which in this case would be nil because you never assigned a value to that index, you could change your range to
for x in 0..6
my_array[x] = gets.chomp.to_i
end
And that would work, remember to use 2 dots and not 3, as
0..6
0...6
are different, the first one is inclusive, the second one is exclusive.
You can check up more about ranges here
I am trying to implement a merge sort algorithm. I have the following code:
def merge_sort(array)
if array.length < 2
return array
else
length = array.length
i = array[0..array.length/2-1]
j = array[array.length/2 .. -1]
first = merge_sort(i)
second = merge_sort(j)
sorted_array = []
until first.empty? || second.empty? do
if first[0] >= second[0]
sorted_array << second.shift
else
sorted_array << first.shift
end
end
end
end
I get a NoMethodError for NilClass with it.
From my understanding, the unless block should check for empty array, and stop execution before a Nil class ever occurs.
Why do I get this error?
If array.length < 2 then your merge_sort will return array. Otherwise, merge_sort will return whatever until some_condition do ... end evaluates to. It so happens that until evaluates to nil so your method behaves like this:
def merge_sort(array)
if array.length < 2
return array
else
# Do a bunch of stuff...
return nil
end
end
That means that first and second will be nil most of the time and there's your NoMethodError. Perhaps you want to return sorted_array after your until:
def merge_sort(array)
if array.length < 2
array
else
#...
sorted_array = []
until first.empty? || second.empty? do
#...
end
sorted_array # <------------------- sort of important
end
end
I have the following code which looks for the letters "u"and "e" in a word and add 1 to the index so that it shows position in the word starting from 1 rather than 0, and then have it combined into an array.
def vowel_indices(word)
x = (word.index("u") + 1)
y = (word.index("e") + 1)
print = [x,y]
end
I am getting the following error message when running:
#<NoMethodError: undefined method `+' for nil:NilClass>
What is nil in this? From what I can see my variables are assigned correctly.
"What is nil in this?"
As # Cary Swoveland and #Lahiru already said, if a word is passed in that doesn't have a 'u' or 'e' that exception will be raised:
001 > def vowel_indices(word)
002?> x = (word.index("u") + 1) #word.index("u") returns nil if the word arg passed in doesn't have a 'u'
003?> y = (word.index("e") + 1)
004?> print = [x,y] #I've never seen the 'print =' syntax...any reason you're not just returning [x,y] here?
005?> end
=> :vowel_indices
006 > vowel_indices("cruel")
=> [3, 4]
007 > vowel_indices("cat")
NoMethodError: undefined method `+' for nil:NilClass
from (irb):2:in `vowel_indices' # This tells you exactly where the exception is coming from
from (irb):7
from /Users/kenenitz/.rvm/rubies/ruby-2.2.0/bin/irb:11:in `<main>'
Two quick & dirty ways to handle this would be to either add a conditional to check for the presence of each letter, or you can rescue NoMethodError:
#conditional check (preferred as it is more precise)
def vowel_indices(word)
u_index = word.index("u")
e_index = word.index("e")
x = u_index ? u_index + 1 : nil #verify that u_index is not nil before calling :+ method
y = e_index ? e_index + 1 : nil # same for e_index
[x,y]
end
#rescuing NoMethodError, not preferred in this case but including as a possible solution just so that you're familiar w/ this approach
def vowel_indices(word)
begin
x = word.index("u") + 1
y = word.index("e") + 1
rescue NoMethodError
x = nil
y = nil
end
[x,y]
end
With either solution I provide, if a word is missing a 'u' or 'e', the return value would contain nil which would most likely require some sort of special handling elsewhere in your program:
vowel_indices("cat")
=> [nil, nil]
vowel_indices("cut")
=> [2, nil]
It's because for some values assigned as word may not contain a u or e.
If you are using Rails, then you can overcome this by modifying your code like this:
def vowel_indices(word)
x = (word.try(:index, "u").try(:+, 1)
y = (word.try(:index, "e").try(:+, 1)
print = [x,y]
end
In this way, you can prevent the method trying to call :+ method for if the class is Nil.
In instances which we are not sure about the input, it's better to use try.
word = "Hello" # example
def vowel_indices(word)
x = (word.index("u") + 1) # x = (nil + 1) *There is no "u" in "Hello"
y = (word.index("e") + 1) # y = (1 + 1) *Output as expected based on index
print = [x,y]
end
p word.index("u") # will return nil if you need to check the return value
I'm trying to solve this exercise from Ruby Monk website, which says:
Try implementing a method called occurrences that accepts a string
argument and uses inject to build a Hash. The keys of this hash should
be unique words from that string. The value of those keys should be
the number of times this word appears in that string.
I've tried to do it like this:
def occurrences(str)
str.split.inject(Hash.new(0)) { |a, i| a[i] += 1 }
end
But I always get this error:
TypeError: no implicit conversion of String into Integer
Meanwhile, the solution for this one is quite the same (I think):
def occurrences(str)
str.scan(/\w+/).inject(Hash.new(0)) do |build, word|
build[word.downcase] +=1
build
end
end
Okay so your issue is that you are not returning the correct object from the block. (In your case a Hash)
#inject works like this
[a,b]
^ -> evaluate block
| |
-------return-------- V
In your solution this is what is happening
def occurrences(str)
str.split.inject(Hash.new(0)) { |a, i| a[i] += 1 }
end
#first pass a = Hash.new(0) and i = word
#a['word'] = 0 + 1
#=> 1
#second pass uses the result from the first as `a` so `a` is now an integer (1).
#So instead of calling Hash#[] it is actually calling FixNum#[]
#which requires an integer as this is a BitReference in FixNum.Thus the `TypeError`
Simple fix
def occurrences(str)
str.split.inject(Hash.new(0)) { |a, i| a[i] += 1; a }
end
#first pass a = Hash.new(0) and i = word
#a['word'] = 0 + 1; a
#=> {"word" => 1}
Now the block returns the Hash to be passed to a again. As you can see the solution returns the object build at the end of the block thus the solution works.
Don't understand why #nums.pop won't work in the value method. It seems to tell me that it can't do that for nil, but if I just say #nums, it shows that there is indeed something in the array. So then why can't I pop it out?
class RPNCalculator
def initialize
#value = value
nums ||= []
#nums = nums
end
def push(num)
#nums << num
end
def plus
if #nums[-2] == nil || #nums[-1] == nil
raise "calculator is empty"
else
#value = #nums.pop + #nums.pop
#nums.push(#value)
end
end
def minus
if #nums[-2] == nil || #nums[-1] == nil
raise "calculator is empty"
else
#value = #nums[-2] - #nums[-1]
#nums.pop(2)
#nums.push(#value)
end
end
def divide
if #nums[-2] == nil || #nums[-1] == nil
raise "calculator is empty"
else
#value = #nums[-2].to_f / #nums[-1].to_f
#nums.pop(2)
#nums.push(#value)
end
end
def times
if #nums[-2] == nil || #nums[-1] == nil
raise "calculator is empty"
else
#value = #nums.pop.to_f * #nums.pop.to_f
#nums.push(#value)
end
end
def value
#nums #Don't understand why #nums.pop won't work here
end
def tokens(str)
str.split(" ").map { |char| (char.match(/\d/) ? char.to_i : char.to_sym)}
end
def evaluate(str)
tokens(str).each do |x|
if x == ":-"
minus
elsif x == ":+"
plus
elsif x == ":/"
divide
elsif x ==":*"
times
else
push(x)
end
end
value
end
end
Error relates to the following part of a spec:
it "adds two numbers" do
calculator.push(2)
calculator.push(3)
calculator.plus
calculator.value.should == 5
end
Error says either:
Failure/Error: calculator.value.should == 5
expected: 5
got: [5] <using ==>
OR if .pop is used
Failure/Error: #calculator = RPNCalculator.new
NoMethodError:
undefined method 'pop' for nil:NilClass
Your initialize method assigning #value = value calls the function at def value which returns #nums which has not yet been created in initialize since #nums is created afterwards with nums ||= []; #nums = nums therefore it's nil. This is why .pop won't work.
You've created #nums as an array with nums ||= [] and you're using it with push and pop so why are you checking for the value with value.should == 5 (Integer) when calling value returns an (Array). You would need to write it like value.first.should == 5 or value[0].should == 5 ... otherwise you should change value to return just the element you want
def value
#nums.pop # or #nums[0], or #nums.first or #nums.last however you plan on using it
end
The problem is #value = value in your initialize method. Fix that then you can add the .pop in value.
EDIT
Also your evaluation is calling methods before you've populated #nums with the values. Then the methods "raise" errors. You can't call minus after only one value has been pushed to #nums.
Here's how I would do the flow for splitting the string
# Multiplication and Division need to happen before addition and subtraction
mylist = "1+3*7".split(/([+|-])/)
=> ["1", "+", "3*7"]
# Compute multiplication and division
mylist = mylist.map {|x| !!(x =~ /[*|\/]/) ? eval(x) : x }
=> ["1", "+", 21]
# Do the rest of the addition
eval mylist.join
=> 22
I realize this isn't exactly how you're going about solving this... but I think splitting by order of mathematical sequence will be the right way to go. So first evaluate everything between (), then only multiplication and division, then all addition and subtraction.
EDIT I just looked into what a RPN Calculator is. So don't mind my splitting recommendation as it doesn't apply.