How do I recreate Enumerable's count method? - ruby

I'm trying to recreate Enumerable's count method as found in "Projects: Advanced Building Blocks".
The definition in the Ruby docs is that count
"Returns the number of items in enum through enumeration. If an argument is given, the number of items in enum that are equal to item are counted. If a block is given, it counts the number of elements yielding a true value."
What exactly is the default argument though?
The way I approached this so far is as follows:
The parameter is set to something when no argument is passed so:
Case, when self is not a string:
when argument given and block given (eg. [1,2,3].count(3) { |x| x == 3 }):
returns warning and count of the argument.
when argument given and no block (eg. [1,2,3].count(3)):
returns count of the argument.
when no argument and no block (eg. [1,2,3].count):
returns size of the instance.
else (no argument given and block given) (eg. [1,2,3].count { |x| x == 3 }:
returns count based on specifications given in block.
The two questions I have are basically:
What is the default argument for count?
What is the global variable used in warning?
Here's my code:
module Enumerable
def my_count arg='default value'
if kind_of? String
# code
else # I think count works the same way for hashes and arrays
if arg != 'default value'
count = 0
for index in 0...size
count += 1 if arg == self[index]
end
warn "#{'what goes here'}: warning: block not used" if block_given?
count
else
return size if arg == 'default value' && !block_given?
count = 0
for index in 0...size
count += 1 if yield self[index]
end
count
end
end
end
end

Don't use a default argument. Use *args to collect all the arguments into an array.
def my_count(*args, &block)
if args.length == 1 && !block_given?
# use args[0]
elsif args.length == 1 && block_given?
# use block
elsif args.length == 0 && !block_given?
# no argument/block
else
# raise error
end
end

Related

Case code in Ruby program not working with a passed value

Have written some test code for a program, trying to pass 2 values, a file and a number. The below doesn't work at all, but if I have something like puts "test" (outside the case) it works.
def read_album(music_file, number)
puts number #(this does something)
case number.to_i
when(number > 1)
puts "done"
when(number < 2)
puts "something"
when(number == 3)
puts "none of this inside case is working"
end
end
def main()
a_file = File.new("albums.txt", "r")
print("Choose album id: ")
choice_of_album = gets().to_i
read_album(a_file, choice_of_album)
end
main()
Your cases are not doing what you think. The expressions given to when are evaluated and the result will be compared to the case value using the case equality operator ===. An expression such as number > 1 will evaluate to either true or false. It makes no sense to compare this result to an integer in Ruby.
You should compare against the constants directly.
case number
when 1
# handle 1
when 2
# handle 2
when 3
# handle 3
else
# handle unknown case; error?
end
Note that other classes may override === to provide useful behavior. The Range and Regexp classes, for example, do this.
case number
when 1..3
# handle 1, 2 and 3
end
case string
when /pattern/
# handle pattern
end
Notably, the Proc class also does this!
def greater_than(n)
proc { |x| x > n }
end
case number
when greater_than(2)
# handle number > 2
end
You need to drop the number.to_i from the case statement.
Or do something like
case number.to_i
when 1..2
puts "foo"
when 3..100
puts "bar"
else
puts "foobar"
end
end
From the Ruby docs
Case statements consist of an optional condition, which is in the position of an argument to case, and zero or more when clauses. The first when clause to match the condition (or to evaluate to Boolean truth, if the condition is null) “wins”, and its code stanza is executed. The value of the case statement is the value of the successful when clause, or nil if there is no such clause.
Your version would evaluate to somehting like
if (number > 1) === number.to_i
and since you are comparing a number with a boolean expression this will not evaluate to true. If you had an else in the case statement this would have been called.

How can I do the opposite .include?

So the goal here is to print the index of the element if the element is in the array or print -1 if the element is not in the array. I have to do this using loops. PLEASE HELP!
def element_index(element, my_array)
while my_array.map.include? element do
puts my_array.index(element)
break
end
until my_array.include? element do
puts -1
break
end
end
p element_index("c", ["a","b","c"])
If it's OK to use Array#index, then
def element_index(elem, collection)
collection.index(elem) || -1
end
Or if it's a homework that you should not use Array#index, or you want to do this on arbitrary collections, then
def element_index(elem, collection)
collection.each_with_index.reduce(-1) do |default, (curr, index)|
curr == elem ? (return index) : default
end
end
By the way, I always turn to Enumerable#reduce when I want to iterate over a collection (array, map, set, ...) to compute one value.
This is an easy way but maybe it doesn't meet the criteria for "using loops":
def element_index(x, arr)
arr.index(x) || -1
end
element_index("c", ["a","b","c"]) #=> 2
element_index("d", ["a","b","c"]) #=> -1
To explicitly use a loop:
def element_index(x, arr)
arr.each_index.find { |i| arr[i] == x } || -1
end
As pointed out in the comments, we could instead write
arr.each_index.find(->{-1}) { |i| arr[i] == x }
element_index("c", ["a","b","c"]) #=> 2
element_index("d", ["a","b","c"]) #=> -1
I know this is an assignment, but I'll first cover this as if it were real code because it's teaching you some not-so-great Ruby.
Ruby has a method for doing this, Array#index. It returns the index of the first matching element (there can be more than one), or nil.
p ["a","b","c"].index("c") # 2
p ["a","b","c"].index("d") # nil
Returning -1 is inadvisable. nil is a safer "this thing does not exist" value because its never a valid value, always false (-1 and 0 are true in Ruby), and does not compare equal to anything but itself. Returning -1 indicates whomever came up with this exercise is converting it from another language like C.
If you must, a simple wrapper will do.
def element_index(element, array)
idx = array.index(element)
if idx == nil
return -1
else
return idx
end
end
I have to do this using loops.
Ok, it's homework. Let's rewrite Array#index.
The basic idea is to loop through each element until you find one which matches. Iterating through each element of an array is done with Array#each, but you need each index, that's done with Array#each_index. The element can be then gotten with array[idx].
def index(array, want)
# Run the block for each index of the array.
# idx will be assigned the index: 0, 1, 2, ...
array.each_index { |idx|
# If it's the element we want, return the index immediately.
# No need to spend more time searching.
if array[idx] == want
return idx
end
}
# Otherwise return -1.
# nil is better, but the assignment wants -1.
return -1
end
# It's better to put the thing you're working on first,
# and the thing you're looking for second.
# Its like verb( subject, object ) or subject.verb(object) if this were a method.
p index(["a","b","c"], "c")
p index(["a","b","c"], "d")
Get used to using list.each { |thing| ... }, that's how you loop in Ruby, along with many other similar methods. There's little call for while and for loops in Ruby. Instead, you ask the object to loop and tell it what to do with each thing. It's very powerful.
I have to do this using loops.
You approach is very creative. You have re-created an if statement using a while loop:
while expression do
# ...
break
end
Is equivalent to:
if expression
# ...
end
With expression being something like array.include? element.
How can I do the opposite?
To invert a (boolean) expression, you just prepend !:
if !expression
# ...
end
Applied to your while-hack:
while !expression do
# ...
break
end
The whole method would look like this:
def element_index(element, my_array)
while my_array.include? element do
puts my_array.index(element)
break
end
while !my_array.include? element do
puts -1
break
end
end
element_index("c", ["a","b","c"])
# prints 2
element_index("d", ["a","b","c"])
# prints -1
As I said at the beginning, this approach is very "creative". You are probably supposed to find the index using a loop (see Schwern's answer) instead of calling the built-in index.

Calling methods within methods ruby

I'm trying to figure out how to call methods inside methods in ruby.
Here's my code:
def testNegative number
if number < 0 # No negative numbers.
return 'Please enter a number that isn\'t negative.'
end
end
def testIfZero number
if number == 0
return 'zero'
end
end
def englishNumber number
testNegative(number)
testIfZero(number)
end
puts englishNumber -1
puts englishNumber 0
Currently, the output I'm getting is an empty line and then "zero". I was wondering why I don't see "Please enter a number that isn't negative" as the output of put englishNumber -1. How can I fix things so that "Please enter a number that isn't negative" is returned and the programs ends?
I'll use underscores instead of camel case in my answer, because that is what people usually do for Ruby methods.
Your code doesn't work right now because test_negative is returning a value but english_number is not doing anything with the value. You could change english_number to:
def english_number(number)
test_negative(number) or test_if_zero(number)
end
That way if test_negative returns a non-nil and non-false value, then english_number will use that as its return value and not bother running test_if_zero.
Later, if you need to add more lines of code to english_number, you can do it like this:
def english_number(number)
error_message = test_negative(number) || test_if_zero(number)
return error_message if error_message
# More lines of code here
end
By the way, it sounds like you might actually want to use exceptions. When you raise an exception, the program will end and the exception message will be printed to the user unless you do something to catch and handle the exception. I would change test_negative to:
def test_negative(number)
if number < 0 # No negative numbers.
raise 'Please enter a number that isn\'t negative.'
end
end
Your code
def englishNumber number
testNegative(number)
testIfZero(number)
end
calls first testNegative(number) and ignores the result.
Then it calls testIfZero(number) and returns the result to the caller.
For englishNumber -1 the result of testIfZero(number)is nil and putswrites an empty line.
For englishNumber 0 you get the expected string 'zero'.
If you need a the results as alternative you must return the results with an or condition (or ||).
A complete example:
def testNegative number
if number < 0 # No negative numbers.
return 'Please enter a number that isn\'t negative.'
end
end
def testIfZero number
if number == 0
return 'zero'
end
end
def englishNumber number
testNegative(number) or testIfZero(number)
end
puts englishNumber -1
puts englishNumber 0
or with an alternate syntax:
def testNegative number
return 'Please enter a number that isn\'t negative.' if number < 0 # No negative numbers.
end
def testIfZero number
return 'zero' if number == 0
end
def englishNumber number
testNegative(number) || testIfZero(number)
end
puts englishNumber -1
puts englishNumber 0
I'm not sure if I understand your comments correct, but maybe you are looking for a solution like this:
def testNegative number
puts 'Please enter a number that isn\'t negative.' if number < 0 # No negative numbers.
end
def testIfZero number
puts 'zero' if number == 0
end
def englishNumber number
testNegative(number)
testIfZero(number)
end
englishNumber -1
englishNumber 0
The puts command can also be used inside the methods, you don't need to return a result.
Remark outside the scope of the question: The usage of a method call inside a method is not the simplest solution (but I think you want to check the usage of method calls).
I would use:
def english_number number
if number < 0 # No negative numbers.
return 'Please enter a number that isn\'t negative.'
elsif number == 0
return 'zero'
end
end

variable addition within index returns nil in ruby

I've come across this error twice now in exercises when I'm trying to iterate over indexes in a string with a conditional. When I break it all out with individual cases the logic seems to work, but Ruby doesn't like something about the way it's expressed.
def NumberAddition(str)
def is_num(num)
(0..9).include? num.to_i
end
i = 0
sum = 0
while i < str.length
if is_num(str[i])
if !is_num(str[i+1])
sum += str[i].to_i
else
mult = str[i]
n = 1
while is_num(str[i+n])
mult << str[i+n]
n += 1
end
sum += mult.to_i
end
end
i += 1
end
sum
end
NumberAddition("75Number9")
throws this error:
no implicit conversion of nil into String
(repl):18:in `NumberAddition'
for the line:
mult << str[i+n]
so obviously instead of returning, say, the string "5" for str[i+n],
where i=0 and n=1, it finds nil. Is there a way to express this with my methodology or do I need to retool the whole loop?
Your is_num function doesn't take into account that nil.to_i is 0. That's why you're getting error, because you're trying to append nil to a string. You need to use something like this:
def is_num(num)
# convert string to integer and back to string should be equal to string itself
num.to_i.to_s == num
end
Or, if you want to make sure that you concatenating strings, just convert the argument to a string
mult << str[i+n].to_s # nil gets converted to an empty string

Ruby method with few or zero parameters - how to do it?

I have two methods in Ruby:
def reverse(first,second)
#items[first..second]=#items[first..second].reverse
end
And:
def reverse
#items.reverse!
end
Can I combine these into one function and use an if first.nil? && second.nil? condition, or it is better to keep it like it is?
First option is to use default valued parameters:
def reverse(first = nil, second = nil)
if first && second
#items[first..second]=#items[first..second].reverse
else
#items.reverse!
end
end
The second option is to use variable number of args:
def reverse(*args)
if args.length == 2
#items[args.first..args.last]=#items[args.first..args.last].reverse
else
#items.reverse!
end
end

Resources