Read and Write Ruby - ruby

Can't seem to get my data to be read as an integer and print out the data, instead gets the 2nd option which is Error: first line of file is not a number.
def write(aFile, number)
aFile.puts(number)
index = 0
while (index < number)
aFile.puts(index)
index += 1
end
end
def read(aFile)
count = aFile.gets
if (is_numeric?(count))
count = count.to_i
else
count = 0
puts "Error: first line of file is not a number"
end
index = 0
while (count < index)
line = aFile.gets
puts "Line read: " + line
index += 1
end
end
def main
aFile = File.new("mydata.txt", "w") # open for writing
if aFile # if nil this test will be false
write(aFile, 10)
aFile.close
aFile = File.new("mydata.txt", "r")
read(aFile)
aFile.close
else
puts "Unable to open file to write or read!"
end
end
def is_numeric?(obj)
if /[^0-9]/.match(obj) == nil
true
end
false
end
main
Any help on how to fix this would be great.

Your problem is a lack of return
def is_numeric?(obj)
if /[^0-9]/.match(obj) == nil
true
end
false
end
This function ALWAYS returns false. There is no case where it ever returns true. Ruby functions always return at an explicit return() and if none is called, then the last line is returned. That means the true you have there does nothing. It's simply thrown away and false is returned.
A simplified form of this existing function is just:
def is_numeric?(obj)
false
end
To fix this problem, you need to return when it’s true:
def is_numeric?(obj)
if /[^0-9]/.match(obj) == nil
return(true)
end
false
end
You can also simplify this to:
def is_numeric?(obj)
/[^0-9]/.match(obj).nil?
end
Also, if you’re using Ruby 2.4+, a more efficient way to do this would be to use the match? method and a negation. match sets up some handy MatchData (and backreferences), but since you if you don't need any of that, you can save the overhead by using match?, which simply returns a boolean.
def is_numeric?(obj)
!/[^0-9]/.match?(obj)
end
Another problem is your logic of count < index.
while (count < index)
line = aFile.gets
puts "Line read: " + line
index += 1
end
Since index is 0, the only time count will be less than index, is if count is less than 0. Perhaps you meant while (count > index)?
Notes:
https://www.ruby-lang.org/en/news/2016/12/25/ruby-2-4-0-released/

Related

Why is this ruby method not terminating at return?

newbie ruby question
code challenge: string collapse: remove all adjacent duplicates in a given string. Note: removal of adjacent duplicates can create new adjacent duplicates, you must remove these as well.
I'm pretty sure I solved this with simple recursion but when I walk through this step by step in a debugger the code doesn't terminate when it reaches the return line and begins adding then removing letters to the string until finally terminating.
for example
zzzxaaxy => zxaaxy => zxxy => zy => zxxy => zy => zxxy =>
^ code hits return line here and should stop but instead continues
def same_char_collapse(str)
ar = str.split('')
ar.map.each_with_index do |char1, idx|
char2 = ar[idx+1]
if char1 == char2
ar.delete_at(idx)
ar.delete_at(idx)
same_char_collapse(ar.join(''))
end
end
return ar.join('')
end
The return keyword does not mean "terminate instantly and return my value", it means "return to the method that called me."
So when you call your method and it returns "zxxy" instead of "zy", it is because it is just returning the result of ar.join() in the first call to same_char_collapse.
def same_char_collapse(str)
ar = str.split('')
ar.map.each_with_index do |char1, idx|
char2 = ar[idx+1]
if char1 == char2
ar.delete_at(idx)
ar.delete_at(idx)
# same_char_collapse(ar.join('')) doesn't change the value we are returning.
# you could delete this line and the method would still return the same thing 'zxxy'
same_char_collapse(ar.join(''))
end
end
# ar's value is zxxy from the loop since the same_char_collapse(ar.join('')) line
# doesn't change ar. only the ar.delete_at(idx) lines modified ar.
return ar.join('')
end
same_char_collapse('zzzxaaxy') # first call to same_char_collapse
Essentially, if you want to fix your solution, you should make sure you are using the value produced in the recursive call.
thanks for the help and after lots of trial and error I eventually worked
out a solution
def dupes?(word)
word.each_char.with_index do |char, idx|
if char == word[idx+1]
return true
end
end
return false
end
def removeDupe(word)
ar = word.split('')
ar.map.with_index do |char,idx|
if char == ar[idx+1]
ar.delete_at(idx)
ar.delete_at(idx)
end
end
return ar.join('')
end
def same_char_collapse(str)
if dupes?(str)
same_char_collapse(removeDupe(str))
else
return str
end
end

Why am I getting "not a number" error in this code? [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 3 years ago.
Improve this question
I'm just hoping someone might be able to help me out with this code:
def write(aFile, number)
index = 1
while (index < number)
aFile.puts(index.to_s)
index += 1
end
end
def read(aFile)
count = aFile.gets
if (is_numeric?(count))
count = count.to_i
else
count = 0
puts "Error: first line of file is not a number"
end
index = 0
while (count < index)
line = aFile.gets
puts "Line read: " + line
end
end
# Write data to a file then read it in and print it out
def main
aFile = File.new("mydata.txt", "w")
if aFile
write(aFile, 11)
aFile.close
else
puts "Unable to open file to write!"
end
aFile = File.new("mydata.txt", "r")
if aFile
read(aFile)
aFile.close
else
puts "Unable to open file to read!"
end
end
# returns true if a string contains only digits
def is_numeric?(obj)
if /[^0-9]/.match(obj) == nil
true
end
false
end
main
The result I'm trying to get is this:
Line read: 0
Line read: 1
...
Line read: 10
But I'm getting:
Error: first line of file is not a number
Why is this the case? Something must be wrong with my code.
def is_numeric?(obj)
if /[^0-9]/.match(obj) == nil
true
end
false
end
Result of a code block (such as method body) is the last expression evaluated in it. Your true becomes the value of the if and is ignored, because the next expression evaluated is false, which is what is always returned. There are several ways you can improve this.
def is_numeric?(obj)
return true if /[^0-9]/.match(obj).nil?
false
end
def is_numeric?(obj)
/[^0-9]/.match(obj).nil?
end
def is_numeric?(obj)
/[^0-9]/ !~ obj
end
def is_numeric?(obj)
Integer(obj) rescue false
end
And many more.

Read file and write file

Can't seem to get my data to be read as an integer and print out the data. plus there is a close stream (IOError) for count = aFile.gets in def read (afile) function.
This program includes Array, files and loops. The purpose of this program is to take a number 10 and write the number to a file then on each line increment from zero to 10 that is passed.
# takes a number and writes that number to a file then on each line
# increments from zero to the number passed
def write(aFile, number)
# You might need to fix this next line:
aFile.puts("number")
index = 0
while (index < number)
aFile.puts(number.to_s)
index += 1
end
end
# Read the data from the file and print out each line
def read(aFile)
# Defensive programming:
count = aFile.gets
if (is_numeric(count))
count = count.to_i
else
count = 0
puts "Error: first line of file is not a number"
end
index = 0
while (count < index)
line = aFile.gets
puts "Line read: " + line
end
end
# Write data to a file then read it in and print it out
def main
aFile = File.new("mydata.txt", "w") # open for writing
if aFile # if nil this test will be false
write(aFile, 10)
else
puts "Unable to open file to write!"
end
if aFile
read(aFile)
end
aFile.close
end
# returns true if a string contains only digits
def is_numeric?(obj)
if /[^0-9]/.match(obj) == nil
true
end
false
end
main
If you want to make your code work, change:
aFile = File.new("mydata.txt", "w")
to:
aFile = File.new("mydata.txt", "r+")
You can change:
count = aFile.gets
if (is_numeric(count))
to:
count = aFile.gets.to_i
if (count.is_a?(Fixnum))
and then get rid of the is_numeric?(obj) method.
Also you're not incrementing the counter, you can fix that as well.
Here is a skinned code working, you'd easily add the features I removed.
def write(a_file, number)
(1..number).each { |n| a_file.puts(n) }
end
def read(a_file)
a_file.each { |line| puts line }
end
def main
a_file = File.new("mydata.txt", "w")
if a_file
write(a_file, 10)
else
puts "Unable to open file to write!"
end
a_file.close
a_file = File.open("mydata.txt", "r")
if a_file
read(a_file)
end
a_file.close
end
main
The main bugs I've found:
After writing, close the file and open again fro writing
See here for reading: What are all the common ways to read a file in Ruby?
aFile.puts(number.to_s) you should puts index, which is the incrementing variable
(is_numeric(count)) missing ?
Side note: use Ruby notation for variables: a_file is good, aFile is not.

Does anyone see the error in this simple Ruby function?

This function is supposed to take a string and return the characters in reverse order.
def reverse(string)
reversedString = "";
i = string.length - 1
while i >= 0
reversedString = reversedString + string[i]
i -= 1
end
puts reversedString
end
however all the tests return false:
puts(
'reverse("abc") == "cba": ' + (reverse("abc") == "cba").to_s
)
puts(
'reverse("a") == "a": ' + (reverse("a") == "a").to_s
)
puts(
'reverse("") == "": ' + (reverse("") == "").to_s
)
Does anyone see what the problem is?
Try to use the default String class reverse method like this:
"Hello World".reverse
"Hello World".reverse!
Check Ruby's String class API at https://ruby-doc.org/core-2.4.0/String.html
If you want to make your custom method, you could use a map like this:
string = String.new
"Hello World".chars.each { | c | string.prepend c }
The problem is your function isn't returning its result, it's printing it. It needs to return reversedString.
As a rule of thumb, functions should return their result. Another function should format and print it.
def reverse(string)
reversedString = "";
i = string.length - 1
while i >= 0
reversedString = reversedString + string[i]
i -= 1
end
return reversedString
end
Note: This was probably an exercise, but Ruby already has String#reverse.
It's good that you're writing tests, but the way you're writing them it's hard to tell what went wrong. Look into a Ruby testing framework like MiniTest.
require "minitest/autorun"
class TestReverse < Minitest::Test
def test_reverse
assert_equal "cba", reverse("abc")
assert_equal "a", reverse("a")
assert_equal "", reverse("")
end
end
That would have told you that your function is returning nil.
1) Failure:
TestReverse#test_reverse [test.rb:16]:
Expected: "cba"
Actual: nil
To make this more Ruby-like yet avoid using the built-in String#reverse method you'd do this:
def reverse(string)
string.chars.reverse.join('')
end
Remember that in Ruby the result of the last operation is automatically the return value of the method. In your case the last operation is puts which always returns nil, eating your value. You want to pass it through.
Try to design methods with a simple mandate, that is, this function should focus on doing one job and one job only: reversing a string. Displaying it is beyond that mandate, so that's a job for another method, like perhaps the caller.
To avoid calling any sort of reverse method at all:
def reverse(string)
result = ''
length = string.length
length.times do |i|
result << string[length - 1 - i]
end
result
end
You can often avoid for almost completely and while frequently if you use things like times or ranges (0..n) to iterate over.
puts prints and returns nil, so the whole method returns nil. If, for debugging reasons , you want to inspect what your method is returning, use p which returns it's argument (reversedString in this case).
def reverse(string)
reversedString = ""
i = string.length - 1
while i >= 0
reversedString = reversedString + string[i]
i -= 1
end
p reversedString # !!!
end
And all 3 tests return true
If I was going to do this, I'd probably take advantage of an array:
ary = 'foo bar baz'.chars
reversed_ary = []
ary.size.times do
reversed_ary << ary.pop
end
reversed_ary.join # => "zab rab oof"
pop removes the last character from the array and returns it, so basically it's walking backwards through ary, nibbling at the end and pushing each character onto the end of reversed_ary, effectively reversing the array.
Alternately it could be done using a string:
ary = 'foo bar baz'.chars
reversed_str = ''
ary.size.times do
reversed_str << ary.pop
end
reversed_str # => "zab rab oof"
or:
reversed_str += ary.pop
I just saw that #tadman did a similar thing with the string. His would run more quickly but this is more readable, at least to my eyes.

How do you mock a break statement in RSpec?

I am having an issue with a LocalJumpError being raised when the example reaches the break statement.
Does anyone know how to stub out break, or if this is even the right approach?
The Method:
def foo(file_name, limit)
CSV.foreach(file_name, col_sep: "|") do |row|
row_count += 1
do something...
break if row_count >= limit
end
end
The Spec:
it 'does not exceed the limit' do
CSV.should_receive(:foreach).with(file_name, col_sep: "|") do |&block|
block.call(header_fields)
block.call(data)
end
foo(file_name, 2)
end
I might consider doing something like this as an integration-type test:
def foo(file_name, limit)
CSV.read(file_name, col_sep: "|").each_with_index do |row, index|
if index < limit
#do something...
end
end
end
end
it 'does not exceed the limit' do
filename = 'path/to/file.csv'
file = CSV.read(filename)
file_length = file.size
last_line = file.last
output = foo(filename, file_length - 1)
expect(output.last).to_not contain(last_line)
end
In this solution, you still iterate through every line of the CSV, but ignore the line if you're over the limit. It's not ideal, but it's one way to go.
The test would set the limit as one less than the file-length, then check if the last line got processed.
The assertion isn't quite right - it will depend on your # do something action.

Resources