Ruby: undefined method `digits' for 3212:Fixnum (NoMethodError) - ruby

The code below is meant to take in an integer, square each digit, and return the integer with the squared digits.
However, I kept having this error:
`square_digits': undefined method `digits' for 3212:Fixnum (NoMethodError)
from `
'
I don't understand why I have this error as the .digits method is an included method in ruby and I'm using it on an integer, yet it gives me a NoMethodError.
def square_digits(digit)
puts digit
puts digit.inspect
puts digit.class
if digit <= 0
return 0
else
#converts the digit into digits in array
split_digit = digit.digits
puts "split digit class and inspect"
puts split_digit.inspect
puts split_digit.class
# multiples each digit by itself
squared = split_digit.map{ |a| a * a}
squared.reverse!
# makes digits into string
string = squared.join('')
# converts string into integer
string.to_i
end
end
Does anyone know what is going on??

I guess you are using older version of Ruby than 2.4.0. If so then this method will not be available. It is added in 2.4.0. See this link
To add support for you old ruby version you can just add below code before your method definition.
class Integer
def digits(base: 10)
quotient, remainder = divmod(base)
quotient == 0 ? [remainder] : [*quotient.digits(base: base), remainder]
end
end
After adding this code snippet you should be able to run your method.

Related

rand(Range) - no implicit conversion of Range into Integer

A follow up on the question How to create a random time between a range
.
Kernel#rand works with Time range:
require 'time'
rand(Time.parse('9 am')..Time.parse('11:30 am'))
But when I tried with a custom class, I ended up with the error:
`rand': no implicit conversion of Range into Integer (TypeError)
class Int
include Comparable
attr_reader :num
def initialize(num)
#num = num
end
def succ
Int.new(num + 1)
end
def <=>(other)
num <=> other.num
end
def to_s
"Int(#{num})"
end
def to_int
#num
end
alias_method :inspect, :to_s
end
puts rand(Int.new(1)..Int.new(3))
Why? What am I missing in the custom class? Can we use such a custom class in rand(Range)?
I don't know of any documentation for what specifically Kernel#rand expects from a Range argument but we can get a look at what's going on by overriding respond_to? in your class and then watching as things fall apart:
def respond_to?(m)
puts "They want us to support #{m}"
super
end
Doing that tells us that rand wants to call the #- and #+ methods on your Int instances. This does make some sense given that rand(a..b) is designed for working with integers.
So we throw in quick'n'dirty implementations of addition and subtraction:
def -(other)
self.class.new(to_int - other.to_int)
end
def +(other)
self.class.new(to_int + other.to_int)
end
and we start getting rand Ints out of our calls to rand.
I'm not sure where (or if) this is documented so you'll have to excuse a bit of hand waving. I normally spend some time rooting around the Ruby source code to answer this sort of question but I lack the time right now.
To add a bit more to #mu-is-too-short's answer, I checked the source of Random#rand and the following is the current implementation logic for rand(Range):
Get the begin, end, and vmax from the Range object (call range_values), where vmax is computed as (call id_minus):
vmax = end - begin
vmax will be used as the upper bound of the random number generation later.
This requires the custom class to have - method defined.
Generate a random number based on the type of vmax:
If it is not Float and can be coerced to Integer (rb_check_to_int), generate a random Integer less than vmax.
In this case, the - method should either return an Integer, or an object which responds to to_int method.
If it is Numeric and can be converted to Float with to_f, (rb_check_to_float), generate a random Float number less than vmax.
In this case, the - method should return a Numeric number which can be converted to Float with method to_f.
Add the random number to begin to yield the result (call id_add).
This requires the custom class to have + method defined, which accepts the result of the random number generated in step 2 (either Integer, or Float) and returns the final result for rand.
I believe this error is because you are trying to use rand() on objects of your custom class.
`rand': no implicit conversion of Range into Integer (TypeError)
This error message clearly mentions that ruby was unable to convert your range into integer. Based on your code snippet, following works and might be what you are looking for.
puts rand(Int.new(1).to_int..Int.new(3).to_int)

Convert Integer to Octal using class Integer and multiple methods

I have a script started but am receiving an error message. I typically have the right idea, but incorrect syntax or formatting.
Here are the exact instructions given:
Extend the Integer class by adding a method called to_oct which returns a string representing the integer in octal. We'll discuss the algorithm in class. Prompt the user for a number and output the octal string returned by to_oct.
Add another method to your Integer extension named "to_base". This method should take a parameter indicating the base that the number should be converted to. For example, to convert the number 5 to binary, I would call 5.to_base(2). This would return "101". Assume that the input parameter for to_base is an integer less than 10. to_base should return a string representing the decimal in the requested number base.
#!/usr/bin/ruby
class Integer
def to_base(b)
string=""
while n > 0
string=(n%b)+string
n = n/b
end
end
def to_oct
n.to_base(8)
end
end
puts "Enter a number: "
n=gets.chomp
puts n.to_base(2)
When I run the script I do get the enter a number prompt, but then I get this error message:
tryagain.rb:16:in `<main>': undefined method `to_base' for "5":String (NoMethodError)
As suggested, do something like this:
class Integer
def to_base b
to_s b #same as self.to_s(b)
end
def to_oct
to_base 8 #same as self.to_base(8)
end
end
5.to_base 2 #=> "101"
65.to_oct #=> "101"

ruby code with the NoMethodError

When running the following Ruby code:
#!/usr/bin/env ruby
ar=[]
class String
def to_int
self == self.to_i
end
end
ARGV.each do |a|
ar.push("#{a}")
end
ar.map(&:to_int).sort
ar.each do |x|
print x + " "
end
puts ""
I am getting the following error:
example.rb:14:in `sort': undefined method `<=>' for false:FalseClass (NoMethodError)
This program needs to be running with the command line argument with a list of numbers. Any help will be appreciated.
ARGV.sort.each { |x| print x + " " }
puts
class String
def to_int
self == self.to_i
end
end
This to_int method will return either a true or false. So when you run this line: ar.map(&:to_int).sort, the map method will map the entire array into true or false.
You array will look like [false,false,true,false], which will fail when you run the sort function.
I'm not sure what the purpose of the to_int function is, you just need to map with the simple to_i function, then sort it.
ar.map!(&:to_i).sort
Make sure you use the map! so your original array is modified.
If you do map the array into integers, you will have to modify the print line to
ar.each do |x|
print x.to_s + " "
end
Otherwise you will get an error:
String can't be coerced into Fixnum
When I run this using Ruby 2.3.0, I don't get that error.
I tried Ruby 2.0.0p648 (which comes with OS X), 2.1.5, and 2.2.4, and they don't raise that error either.
It's a bit unclear to me what you're trying to accomplish here.
You're doing things that don't make any sense, but I'll assume you're trying to learn Ruby and you're just trying different things.
#!/usr/bin/env ruby
ar=[]
# This is "monkey patching" String, and is a bad practice.
class String
# A method named "to_int" implies a conversion to integer. But the "to_i" method already does
# that, and this method doesn't convert to an integer, it converts to a boolean.
def to_int
# Comparing the string to itself as an integer. Why would it ever be true?
self == self.to_i
end
end
# I'm assuming that this is intended to convert the argument list to a list of strings.
# But ARGV should already a list of strings.
# And this would be better done as `ar = ARGV.map(&:to_s)`
ARGV.each do |a|
ar.push("#{a}");
end
# This creates an array of values returned by `to_int`, then sorts the array.
# Since `String#to_int` (defined above) returns booleans, it's an array of "false" values, so
# sorting does nothing. But then the value of the `sort` is ignored, since it's not assigned to
# a variable. If you want to modify the order of an existing array, use `sort!`.
# But if you're trying to sort the array `ar` by the numeric values, `ar.sort_by(&:to_i)` would
# do that.
ar.map(&:to_int).sort
ar.each do |x| print x + " "; end
puts ""
It seems like you're trying to print the arguments in their numeric order.
This can be accomplished with
puts ARGV.sort_by(&:to_i).join(" ")

Pushing numbers onto an array in Ruby

I'm trying to create a program outputting each number and whether it is divisible by numbers 2-9. I'm doing this by iterating over 2-9 and pushing each number onto an array, however, an error shows up when compiling:
/Users/XXX/XXX/XXX/XXX.rb:3: warning: already initialized constant ArrayOfMultiples
How do I remove this error?
Here is my code:
(1..200).each do |number|
output_str = ""
ArrayOfMultiples = Array.new
(2..9).each do |multiple|
if number%multiple == 0
ArrayOfMultiples.push(multiple)
end
end
output_str = number.to_s + " is divisble by " + ArrayOfMultiples.join(", ")
puts output_str
end
Start your variable with lower case, otherwise it is considered a constant. If you reinitialize a constant, you get that warning.
arrayOfMultiples
A simple program like the following can demonstrate this behaviour:
A = 1
A = 2
When you run the above script, it says:
test.rb:2: warning: already initialized constant A
test.rb:1: warning: previous definition of A was here
You could also make this a lot simpler:
(1..200).each do |x|
divisible_by = (2..9).select {|y| x%y==0}
puts "#{x}: #{divisible_by.join(", ")}"
end

why doesn't my refactored ruby using inject work?

I tried to do some refactoring to convert an each block into an inject, but it didn't work and I don't understand why.
Here's the code that works before refactoring:
class String
# Build the word profile for the given word. The word profile is an array of
# 26 integers -- each integer is a count of the number of times each letter
# appears in the word.
#
def profile
profile = Array.new(26) { 0 }
self.downcase.split(//).each do |letter|
# only process letters a-z
profile[letter.ord - 'a'.ord] += 1 unless letter.ord > 'z'.ord
end
profile
end
end
and here's my refactor that doesn't work:
class String
# Build the word profile for the given word. The word profile is an array of
# 26 integers -- each integer is a count of the number of times each letter
# appears in the word.
#
def profile
self.downcase.split(//).inject(Array.new(26) {0}) do |profile, letter|
# only process letters a-z
profile[letter.ord - 'a'.ord] += 1 unless letter.ord > 'z'.ord
end
end
end
When I try and execute the refactored method I'm getting
`block in profile': undefined method `[]=' for 1:Fixnum (NoMethodError)
If I understand that correctly, it's doesn't like the array reference operator on the profile object in my refactored version, which implies that the initialiser passed to inject isn't working. Is that understanding correct? And if so, why not?
Thanks!
The []= method returns the assigned value, so the value of profile in the next iteration will be 1 (since it's the value of the last iteration). In order to get the behavior you want, you'll have to do:
self.downcase.split(//).inject(Array.new(26) {0}) do |profile, letter|
# only process letters a-z
profile[letter.ord - 'a'.ord] += 1 unless letter.ord > 'z'.ord
profile
end
or
self.downcase.split(//).inject(Array.new(26) {0}) do |profile, letter|
# only process letters a-z
profile.tap { profile[letter.ord - 'a'.ord] += 1 unless letter.ord > 'z'.ord }
end

Resources