How to make an array of class objects in ruby - arrays

I'm trying to make an array of class objects, but my code doesn't work. When I make a Solution.new it returns nil, and I want it returns an array of arrays from words in each line of test.txt.
I'm using Ruby 2.1.5
class Line
def initialize (content)
#content = content
self.line_arr
end
def line_arr
#content.split
end
end
class Solution
def read_file
array = []
File.foreach('test.txt') do |line|
array << Line.new(line)
end
end
end
And now when I make a
foo = Solution.new
foo.read_file
it returns nil.

I don't think Solution.new is returning nil in your example, it's returning a new instance of solution (foo in your example)
Your main issue is that read_file is returning the value of File.foreach, which is always nil.
For starters, update your read_file method to return the array itself:
class Solution
def read_file
array = []
lines = []
File.foreach('test.txt') do |line|
lines << Line.new(line)
end
array << lines
array
end
end
solution = Solution.new
solution.read_file
# outputs:
# [#<Line:0x007fab92163b50 #content="This Is A Line\n">, #<Line:0x007fab92161be8 #content="Line 2\n">, #<Line:0x007fab92160d88 #content="Line3">]
If you want to return an array of arrays split each line by whitespace:
class Solution
def read_file
lines = []
File.foreach('test.txt') do |line|
words = []
line.strip.split(/\s+/).each do |word|
words << word
end
lines << Line.new(words)
end
lines
end
end
The key line of code here is: line.strip.split(/\s+/) which first strips leading and trailing whitespace from the string, then converts it to an array by splitting the string based on whitespace (the /s+/ regex matches one or more blank characters).
Some other suggestions:
Pass the filename as an argument to read_file you can set a default argument if you want to:
class Solution
def read_file(filename = 'test.txt')
array = []
File.foreach(filename) do |line|
array << Line.new(line)
end
array
end
end
Finally, for a much more elegant solution, you can use map, and simply call .split to return a nested array. The Line class isn't really doing much in this case.
class Solution
def read_file
File.foreach('test.txt').map do |line|
line.strip.split(/\s+/)
end
end
end
This will simply return an array of arrays, where the inner array contains the words for each line.

class Line
attr_reader :content
def initialize (content)
#content = content.split(' ')
end
end
class Solution
def read_file
array = []
File.foreach('test.txt') do |line|
array << Line.new(line).content
end
array
end
end
You need to add this 'array' row, because you need to return it from the method call. Also I simplified a bit the Line class here. Basically, this code can solve your problem, but consider using the regular expression for parsing rows.

Try this:
class Line
def initialize (content)
#content = content
self.line_arr
end
def line_arr
#content.split
end
end
class Solution
def initialize
self.read_file
end
def read_file
array = []
File.foreach('test.txt') do |line|
array << Line.new(line)
end
array
end
end

Consider to use Enumerable#inject instead of creating unnecessary variables:
class Solution
def read_file
File.foreach('test.txt').inject([]) do |memo, line|
memo << Line.new(line)
end
end
end
or, in this particular case, map will do the trick:
class Solution
def read_file
File.foreach('test.txt').map &Line.method(:new)
end
end

If all you need to do is get arrays of words, and you don't mind loading the entire file into memory at once, then it can be done very simply with the code below (the 3 lines beginning with word_arrays = ..., the rest are setup and output):
#!/usr/bin/env ruby
File.write('woods.txt',
"The woods are lovely, dark, and deep
But I have promises to keep
And miles to go before I sleep
And miles to go before I sleep")
word_arrays = File.readlines('woods.txt').each_with_object([]) do |line, word_arrays|
word_arrays << line.split
end
word_arrays.each.with_index do |words, index|
puts "#{index}: #{words} "
end
=begin
Prints:
0: ["The", "woods", "are", "lovely,", "dark,", "and", "deep"]
1: ["But", "I", "have", "promises", "to", "keep"]
2: ["And", "miles", "to", "go", "before", "I", "sleep"]
3: ["And", "miles", "to", "go", "before", "I", "sleep"]
=end

Related

Can't shovel string into new array in Ruby

I am trying to search an array for a substring and if that substring exists, shovel it into a new array. The problem I am having is that it keeps coming back with this error message:
`block in substrings': undefined method `<<' for nil:NilClass
I have verified that the index in the method is not nil by printing it. I have also done index == nil to double check.
What am I missing here?
Thanks in advance for your help!
new_array = []
def substrings(word, array)
new_array = array.each do |index|
if index.include? (word)
p index
p index == nil
new_array << index
end
end
end
dictionary = ["below", "down", "go", "going", "horn", "how", "howdy", "it", "i", "low", "own", "part", "partner", "sit"]
substrings("i", dictionary)
You basically combine two different ways of solving this problem. The first is to assign the new_array the result of looping though the array, but in that case, the new_array variable is not available to use inside the block.
So you could either choose to create the variable first, like this
new_array = []
array.each do |index|
if index.include?(word)
new_array << index
end
end
Alternatively you could use a method called reduce which takes a more functional programming approach. That could look like this
new_array = array.reduce([]) do |arr, index|
if index.include?(word)
arr << index
else
arr
end
end
What reduce does is that the block argument arr is always set to the return value of the previous block execution. that can make the syntax a little longer than it has to be, so Ruby also has an alternate approach to reduce, called each_with_object, that does the same, but by mutating the same variable, instead of requiring a return value. I actually prefer this way and would solve it like this.
new_array = array.each_with_object([]) do |index, arr|
arr << index if index.include?(word)
end
I would like to extend a bit the (correct) answer given by #DanneManne: While it is correct that you can access local variables from outer blocks from within an inner block, you can't do it within a def, and it is unnecessary in your example, because you can initialize new_array inside the body of your method and return it as result. But in case you ever really need this kind of construct, there indeed is a workaround:
So, this does NOT work:
a=5
def f
puts a; # WRONG. a is not known here
end
and this works different than you seem to expect:
a=5
def f
a=6
end
puts a # prints 5
f
puts a # prints 5 again
But if you define your method like this, it works:
a=5
define_method(:f) do
puts a; # OK, refers to outer variable a
end
By using a block, you create a closure with this, so if you do now a
f;
a=6;
f
5 and 6 is printed, in this order.
I have verified that the index in the method is not nil by printing it. I have also done index == nil to double check.
What am I missing here?
Not index is nil, but new_array. The error message says:
undefined method `<<' for nil:NilClass
and refers to the line new_array << index. Here, << is the method and new_array is the receiver. And for some reason, new_array is nil.
You probably expected new_array to be [] because you explicitly said new_array = []. But methods have their own local variable scope. If you define a local variable outside of a method, it won't be available inside, or vice-versa.
Typically when referring to an undefined variable you'd get:
undefined local variable or method `new_array'
but here, the 2nd assignment conceals the actual problem:
new_array = array.each do |index|
^^^^^^^^^^^
When Ruby encounters this line, it immediately creates a local variable new_array with an initial value of nil. (local variables are created when the line is parsed, not when the assignment occurs)
To get the expected result, you have to move new_array = [] into the method, get rid of the new_array = array.each { ...} assignment and return new_array at the end:
def substrings(word, array)
new_array = []
array.each do |index|
if index.include?(word)
new_array << index
end
end
new_array
end
The variable names are a bit arbitrary, maybe even misleading. Having index.include?(word) looks like you're comparing a numerical index to a string. I'd use something like this:
def substrings(substring, words)
result = []
words.each do |word|
if word.include?(substring)
result << word
end
end
result
end
Code-wise, you can incorporate the array into the loop via each_with_object which will also return the array:
def substrings(substring, words)
words.each_with_object([]) do |word, result|
if word.include?(substring)
result << word
end
end
end
However, selecting elements based on a condition is such a common task that Ruby provides a dedicated method select – you merely have to return true or false from the block to indicate whether the element should be selected:
def substrings(substring, words)
words.select do |word|
word.include?(substring)
end
end

Return unique values of an array without using `uniq`

For a challenge, I'm trying to return the unique values of an array without using uniq. This is what I have so far, which doesn't work:
def unique
unique_arr = []
input_arr.each do |word|
if word != unique_arr.last
unique_arr.push word
end
end
puts unique_arr
end
input = gets.chomp
input_arr = input.split.sort
input_arr.unique
My reasoning here was that if I sorted the array first before I iterated through it with each, I could push it to unique_arr without repetition being a possibility considering if it's a duplicate, the last value pushed would match it.
Am I tackling this the wrong way?
Yes, you are making at least two mistakes.
If you want to call it as input_arr.unique with input_arr being an array, then you have to define the method on Array. You have input_arr within your method body, which comes from nowhere.
puts in the last line of your code outputs to the terminal, but makes the method return nil, which makes it behave differently from uniq.
It can be fixed as:
class Array
def unique
unique_arr = []
each do |word|
unique_arr.push(word) unless unique_arr.last == word
end
unique_arr
end
end
A unique array? That sounds like a Set to me:
require 'set'
Set.new([1,2,3,2,3,4]).to_a
#=> [1,2,3,4]
Here's a concise way to do it that doesn't explicitly use functionality from another class but probably otherwise misses the point of the challenge:
class Array
def unique
group_by(&:itself).keys
end
end
I try this three options. Just for challenge
class Array
def unique
self.each_with_object({}) { |k, h| h[k] = k }.keys
end
def unique2
self.each_with_object([]) { |k, a| a << k unless a.include?(k) }
end
def unique3
arr = []
self.map { |k| arr << k unless arr.include?(k) }
arr
end
end
Here is one more way to do this:
uniques = a.each.with_object([]) {|el, arr| arr << el if not arr.include?(el)}
That's so easy if you see it this way:
a = [1,1,2,3,4]
h = Hash.new
a.each{|q| h[q] = q}
h.values
and this will return:
[1, 2, 3, 4]

Define a trim method on Array that removes the first and last element

class Array
define_method(:trim) do
new_array = self.pop()
new_array = self.shift()
end
end
EDIT: What I tried (among other things)
["hi", "ho", "he"].trim()
This returns "hi".
Remove the last element. Remove the first element. But how do I get the method to return the remaining array instead of what's returned by .shift (or whatever happens to be the last instruction of the method)? Do I need another variable?
Thank you.
pop() and shift() will modify the array directly. You just need to tell the method to return self
class Array
define_method(:trim) do
self.pop()
self.shift()
self
end
end
EDIT : as this method can be dangerous, I suggest you define both trim! and trim. Where trim! will modify the array directly and trim return a copy of the array, trimmed
class Array
def trim!
self.pop()
self.shift()
self
end
def trim
self.dup.trim!
end
end
You can use range when accessing array elements, like that
ary = [1, 2, 3, 4]; ary[1..-2] #=> [2, 3]
So going back to the method, it can be:
class Array
def trim
self[1..-2]
end
end
[EDIT]: to avoid returning nil for empty arrays:
class Array
def trim
self[1..-2] || []
end
end

Adding items to a new array with index

Trying to make a method skip_animals that takes an animals array and a skip integer and returns an array of all elements except first skip number of items.
input: skip_animals(['leopard', 'bear', 'fox', 'wolf'], 2)
expected output: ["2:fox", "3:wolf"]
def skip_animals(animals, skip)
arr = Array.new
animals.each_with_index{|animal, index| arr.push("#{animal}:#{index}") }
puts arr.drop(skip)
end
This instead puts each output on a separate line and doesn't add them to the array arr. I thought the arr.push would add them correctly. What do I have to do to get the elements added to the array?
I want to use these methods, not map or something more advanced. I need to tinker with this each_with_index line, not overhaul it.
(This is a challenge on Hackerrank, so it uses STDIN and STDOUT)
EDIT
Here is my updated code with p instead of puts. It's giving me a weird output of two different arrays, not sure why.
def skip_animals(animals, skip)
arr = Array.new
animals.each_with_index{|animal, index| arr.push("#{index}:#{animal}") }
p arr.drop(skip)
end
This gives me two lines of output:
["3:panda", "4:tiger", "5:deer"]
["0:leopard", "1:bear", "2:fox", "3:wolf", "4:dog", "5:cat"]
I'm assuming the top is the correct array, but I don't get why the second is printing also, or why it has a different set of animals.
Use p instead of puts.
irb(main):001:0> puts ['1', '2']
1
2
=> nil
irb(main):002:0> p ['1', '2']
["1", "2"]
According to the documentation, puts:
Writes the given objects to ios as with IO#print. Writes a record
separator (typically a newline) after any that do not already end with
a newline sequence. If called with an array argument, writes each
element on a new line. If called without arguments, outputs a single
record separator.
BTW, I would code like this (using Enumerable#map + returning result instead of printing inside the function):
def skip_animals(animals, skip)
animals.drop(skip).each_with_index.map { |animal, index|
("#{index + skip}:#{animal}")
}
end
p skip_animals(['leopard', 'bear', 'fox', 'wolf'], 2)
just remove puts remove form this line puts arr.drop(skip)
def skip_animals(animals, skip)
arr = Array.new
animals.each_with_index{|animal, index| arr.push("#{animal}:#{index}") }
arr.drop(skip)
end

Ruby .each method on Array from split string

I'm having trouble using the .each method on an array that results of a string.split
def my_function(str)
words = str.split
return words #=> good, morning
return words[words.count-1] #=> morning
words.each do |word|
return word
end
end
puts my_function("good morning") #=> good
With any multi-word string, I only get the 1st word, not each of the words. With this example, I don't understand why I don't get "good" and "morning" when the 2nd item clearly exists in the array.
Similarly, using a while loop gave me the same result.
def my_function(str)
words = str.split
i = 0
while i < words.count
return word[i]
i += 1
end
puts my_function("good morning") # => good
Any help is appreciated. Thanks in advance!
The return statement in ruby is used to return one or more values from a Ruby Method. So your method will exit from return words.
def my_function(str)
words = str.split
return words # method will exit from here, and not continue, but return value is an array(["good", "morning"]).
return words[words.count-1] #=> morning
....
end
puts my_function("good morning")
output:
good
morning
if you want to use each method to output words, you can do like this:
def my_function(str)
str.split.each do |word|
puts word
end
end
or
def my_function(str)
str.split.each { |word| puts word }
end
my_function("good morning")
output:
good
morning
You are assuming that return words returns the array to your outer puts function which is true. However once you return, you leave the function and never go back unless you explicitly call my_function() again (which you aren't) in which case you would start from the beginning of the function again.
If you want to print the value while staying in the function you will need to use
def my_function(str)
words = str.split
puts words #=> good, morning
puts words[words.count-1] #=> morning
words.each do |word|
puts word # print "good" on 1st iteration, "morning" on 2nd
end
end
my_function("good morning")

Resources