Ruby, assert_equal of two arrays of objects - ruby

I have the following situation. I am trying to write a unit test for an array of objects. The object is defined something like this:
class Element
attr_reader :title, :season, :episode
def initialize ( name, number )
#name = name
#number = number
end
def to_s
number = "%02d" % #number
result = "Number " << number << " " << #name
result
end
end
During the test I assert two arrays which both contain three elements, the elements are identical and even the order is identical still I get an error that the assert isn't equal. I guess I am missing something really basic here, whats the catch?
If I compare each element by the to_s method, the assert is correct.. Is that the way it should be done in the first place?

Try declaring a method == for your class, with the following code.
def ==(other)
self.to_s == other.to_s
end
Sidenote, you might want to refactor your to_s method too, for some concise code.
def to_s
"Number %02d #{#name}" % #number
end
Edit:
Numbers already have an == method defined (https://github.com/evanphx/rubinius/blob/master/kernel/bootstrap/fixnum.rb#L117).
Ruby compares arrays by running an == compare on each element of an Array. Here's the implementation of == on Arrays, as done in Rubinius (a Ruby implementation written almost completely in Ruby itself) https://github.com/evanphx/rubinius/blob/master/kernel/common/array.rb#L474.
If you leave out various error detections, it basically runs an == on all the elements of the array, recursively, and returns true if all of them match.

Related

Ruby method doesn't get array parameter

describe Array do
describe "#sum" do
it "has a #sum method" do
expect([]).to respond_to(:sum)
end
it "should be 0 for an empty array" do
expect([].sum).to eq(0)
end
it "should add all of the elements" do
expect([1,2,4].sum).to eq(7)
end
end
end
the code above is the test code given to me. and I made the code below to be tested by the test code.
class Array
def initialize(arr)
#arr = arr
end
def sum
if #arr == nil
return 0
else
numArr = #arr
total = 0
numArr.each {|num|
total += num
}
return total
end
end
end
I thought it would return total 7. (1+2+4 = 7) but it returns 0... I guess it doesn't take [1,2,4] array as a parameter. what am I doing wrong?
As the existing array class already has an initializer, and self inside the instance of an array already is an array, you don't need to add your own initializer
class Array
def sum
self.inject(0){|sum,x| sum + x }
end
end
should do what you want. Lookup inject if it isn't clear, but it does essentially the same that you tried to do with your code, just that you were using a local variable to store the sum.
If this is not only an experiment however I recommend not do monkey patches on core classes if avoidable (and it usually is avoidable). Here's an article with some hints how to do it a bit cleaner, if not avoidable: http://www.justinweiss.com/articles/3-ways-to-monkey-patch-without-making-a-mess/

How can I rewrite this flatten method without using class level variables?

I am working on building the flatten method in Ruby. Here is my code:
require 'byebug'
class Array
##new_array = []
def new_flatten
self.each do |element|
if !element.is_a? Array
##new_array << element
elsif element.is_a? Array
element.new_flatten
end
end
##new_array
end
end
test_array = ([1,2,3,[4,5,6,[7]]])
puts test_array.new_flatten == test_array.flatten
My question is this. Do I need to use the class level variable? I would like to use an instance variable but it doesn't seem to get called when I do:
test_array = []
or
Array.new([])
So I had to create a class-level variable to hold the new flattened array.
The problem with using a class variable is that the same one is visible to all the instances, and new_flatten is an operation for an instance. You should refactor your new_flatten algorithm so it doesn't rely on what is, in effect, a "global" variable for the method calls.
For example, you can use a local variable and Ruby's facility to append arrays with +:
class Array
def new_flatten
a = []
self.each do |element|
if element.is_a? Array
a += element.new_flatten
else
a << element
end
end
a
end
end
Also, as some tweaks of style, when doing an if-else, it's often clearer to state the positive logic first. And, you don't need the elsif in this case since the else is the complement of the if. So rather than:
if not something
do_stuff
elsif opposite_of_not_something
do_other_stuff
end
It's clearer to say:
if something
do_stuff
else
do_other_stuff
end
Your question is really confusing. In your title, you talk about calling initialize, but there is no initialize in your code. In your question, you talk about calling an instance variable, but you can't call variables, only methods.
However, implementing flatten is just a simple fold, I don't see the need for storing any intermediate state:
class Array
def new_flatten
inject([]) {|acc, el| acc + Array(case el when Array then el.new_flatten else el end) }
end
end

ruby dynamic class instance creation

I'm programming a board game as part of an Ruby OOP learning exercise. I'm attempting to create a game board consisting of nine spaces arranged in three rows of three. Below is a snippet of the currently non-functioning code:
class Board
def initialize()
#dynamically creates instances of the Space class named #s1 through #s9 with #s 1-9 as the location parameter
9.times do |x|
x += 1
y = "#s" + x.to_s
instance_variable_set( y , x.to_s) = Space.new
end
end
def print_board
#prints a 3X3 grid containing each instance's location parameter
puts "\n\n#{#s1.location}|#{#s2.location}|#{#s3.location}\n-----\n#{#s4.location}|#{#s5.location}|#{#s6.location}\n-----\n#{#s7.location}|#{#s8.location}|#{#s9.location}\n\n\n"
end
end
class Space
attr_accessor :location
def initialize(location)
#location = location
end
end
board = Board.new
board.print_board
The desired output is:
X|X|X
-----
X|X|X
-----
X|X|X
...but I keep getting errors related to the creation of instances of the Space class. I've tried so many different things that I can't remember them all. I know there has to be a way to do it like this, but I'm new to Ruby, and it has me stumped. I know could just create 9 individual instances of the Space class, but that seems cheap and dirty.
Here's a slightly re-worked, more Ruby-style version of same:
class Board
def initialize
#board = Array.new(9) do
Space.new
end
end
def move(x,y,set)
#board[(x - 1) + (y - 1) * 3].location = set
end
def to_s
#board.each_slice(3).collect do |row|
row.join('|')
end.join("\n------\n") + "\n"
end
end
class Space
attr_accessor :location
def initialize(location = nil)
#location = location || ' '
end
def to_s
#location
end
end
board = Board.new
puts board.to_s
board.move(1,2,'x')
puts board.to_s
It's worth noting a few tricks:
Array.new takes an argument as to how many entries to pre-populate with.
An Array can be given a block to evaluate that supplies the value to be used for default entries.
each_slice pulls out groups of N entries to process.
collect transforms each row into a simple string.
join combines the rows with horizontal lines.
to_s is the default name for "show as string" and should be used unless it's otherwise confusing.
Space needed a default of nil for the initializer
Methods that do not take arguments do not need brackets, so initializer is preferable to initializer().
The approach you're pursuing with instance_variable_set is a lot harder to maintain and violates the Zero, One or Infinity Rule by having nine variables that represent the cells. What you need is one variable of theoretically infinite size, bounded only by constraints you impose if necessary. Having multiple variables makes things much more difficult to iterate, makes it inconvenient to pass through as an argument, and generally makes a mess of what should be simple.

How can I make the set difference insensitive to case?

I have a class in which the data is stored as a set and I want to be able to compare objects of that class such that the letter case of the elements is of no matter. For example if the set contains elements that are strings there should be no difference of "a" and "A".
To do this I have tried to define the eql? method of the set members to be insensitive to case but this has no effect on the method - (alias difference) in Set. So, how should I go about to make - insensitive to case?
The following code illustrates the problem:
require 'set'
class SomeSet
include Enumerable
def initialize; #elements = Set.new; end
def add(o)
#elements.add(o)
self
end
def each(&block) # To enable +Enumerable+
#elements.each(&block)
end
def difference(compared_list)
#elements - compared_list
end
end
class Element
attr_reader :element
def initialize(element); #element = element; end
# This seems to have no effect on +difference+
def eql?(other_element)
element.casecmp(other_element.element) == 0
end
end
set1 = SomeSet.new
set2 = SomeSet.new
set1.add("a")
set2.add("A")
# The following turns out false but I want it to turn out true as case
# should not matter.
puts set1.difference(set2).empty?
Ok, firstly, you're just storing strings from SomeSet#add, you need to store an instance of Element, like so:
def add(o)
#elements.add(Element.new(o))
self
end
And you need to implement a hash method in your Element class.
You can convert Element##element to lowercase, and pass on its hash.
def hash
element.downcase.hash
end
Full code and demo: http://codepad.org/PffThml2
Edit: For my O(n) insertion comment, above:
Insertions are O(1). From what I can see, eql? is only used with the hash of 2 elements is same. As we're doing hash on the downcased version of the element, it will be fairly well distributed, and eql? shouldn't be called much (if it is called at all).
From the docs:
The equality of each couple of elements is determined according to Object#eql? and Object#hash, since Set uses Hash as storage.
Perhaps you need to implement Object#hash as well.
require 'set'
class String2
attr_reader :value
def initialize v
#value = v
end
def eql? v
value.casecmp(v.value) == 0
end
def hash
value.downcase.hash
end
end
set1 = Set.new
set2 = Set.new
set1.add(String2.new "a")
set2.add(String2.new "A")
puts set1.difference(set2).empty?

How do I add 'each' method to Ruby object (or should I extend Array)?

I have an object Results that contains an array of result objects along with some cached statistics about the objects in the array. I'd like the Results object to be able to behave like an array. My first cut at this was to add methods like this
def <<(val)
#result_array << val
end
This feels very c-like and I know Ruby has better way.
I'd also like to be able to do this
Results.each do |result|
result.do_stuff
end
but am not sure what the each method is really doing under the hood.
Currently I simply return the underlying array via a method and call each on it which doesn't seem like the most-elegant solution.
Any help would be appreciated.
For the general case of implementing array-like methods, yes, you have to implement them yourself. Vava's answer shows one example of this. In the case you gave, though, what you really want to do is delegate the task of handling each (and maybe some other methods) to the contained array, and that can be automated.
require 'forwardable'
class Results
include Enumerable
extend Forwardable
def_delegators :#result_array, :each, :<<
end
This class will get all of Array's Enumerable behavior as well as the Array << operator and it will all go through the inner array.
Note, that when you switch your code from Array inheritance to this trick, your << methods would start to return not the object intself, like real Array's << did -- this can cost you declaring another variable everytime you use <<.
each just goes through array and call given block with each element, that is simple. Since inside the class you are using array as well, you can just redirect your each method to one from array, that is fast and easy to read/maintain.
class Result
include Enumerable
def initialize
#results_array = []
end
def <<(val)
#results_array << val
end
def each(&block)
#results_array.each(&block)
end
end
r = Result.new
r << 1
r << 2
r.each { |v|
p v
}
#print:
# 1
# 2
Note that I have mixed in Enumerable. That will give you a bunch of array methods like all?, map, etc. for free.
BTW with Ruby you can forget about inheritance. You don't need interface inheritance because duck-typing doesn't really care about actual type, and you don't need code inheritance because mixins are just better for that sort of things.
Your << method is perfectly fine and very Ruby like.
To make a class act like an array, without actually inheriting directly from Array, you can mix-in the Enumerable module and add a few methods.
Here's an example (including Chuck's excellent suggestion to use Forwardable):
# You have to require forwardable to use it
require "forwardable"
class MyArray
include Enumerable
extend Forwardable
def initialize
#values = []
end
# Map some of the common array methods to our internal array
def_delegators :#values, :<<, :[], :[]=, :last
# I want a custom method "add" available for adding values to our internal array
def_delegator :#values, :<<, :add
# You don't need to specify the block variable, yield knows to use a block if passed one
def each
# "each" is the base method called by all the iterators so you only have to define it
#values.each do |value|
# change or manipulate the values in your value array inside this block
yield value
end
end
end
m = MyArray.new
m << "fudge"
m << "icecream"
m.add("cake")
# Notice I didn't create an each_with_index method but since
# I included Enumerable it knows how and uses the proper data.
m.each_with_index{|value, index| puts "m[#{index}] = #{value}"}
puts "What about some nice cabbage?"
m[0] = "cabbage"
puts "m[0] = #{m[0]}"
puts "No! I meant in addition to fudge"
m[0] = "fudge"
m << "cabbage"
puts "m.first = #{m.first}"
puts "m.last = #{m.last}"
Which outputs:
m[0] = fudge
m[1] = icecream
m[2] = cake
What about some nice cabbage?
m[0] = cabbage
No! I meant in addition to fudge
m.first = fudge
m.last = cabbage
This feels very c-like and I know Ruby
has better way.
If you want an object to 'feel' like an array, than overriding << is a good idea and very 'Ruby'-ish.
but am not sure what the each method
is really doing under the hood.
The each method for Array just loops through all the elements (using a for loop, I think). If you want to add your own each method (which is also very 'Ruby'-ish), you could do something like this:
def each
0.upto(#result_array.length - 1) do |x|
yield #result_array[x]
end
end
If you create a class Results that inherit from Array, you will inherit all the functionality.
You can then supplement the methods that need change by redefining them, and you can call super for the old functionality.
For example:
class Results < Array
# Additional functionality
def best
find {|result| result.is_really_good? }
end
# Array functionality that needs change
def compact
delete(ininteresting_result)
super
end
end
Alternatively, you can use the builtin library forwardable. This is particularly useful if you can't inherit from Array because you need to inherit from another class:
require 'forwardable'
class Results
extend Forwardable
def_delegator :#result_array, :<<, :each, :concat # etc...
def best
#result_array.find {|result| result.is_really_good? }
end
# Array functionality that needs change
def compact
#result_array.delete(ininteresting_result)
#result_array.compact
self
end
end
In both of these forms, you can use it as you want:
r = Results.new
r << some_result
r.each do |result|
# ...
end
r.compact
puts "Best result: #{r.best}"
Not sure I'm adding anything new, but decided to show a very short code that I wish I could have found in the answers to quickly show available options. Here it is without the enumerator that #shelvacu talks about.
class Test
def initialize
#data = [1,2,3,4,5,6,7,8,9,0,11,12,12,13,14,15,16,172,28,38]
end
# approach 1
def each_y
#data.each{ |x| yield(x) }
end
#approach 2
def each_b(&block)
#data.each(&block)
end
end
Lets check performance:
require 'benchmark'
test = Test.new
n=1000*1000*100
Benchmark.bm do |b|
b.report { 1000000.times{ test.each_y{|x| #foo=x} } }
b.report { 1000000.times{ test.each_b{|x| #foo=x} } }
end
Here's the result:
user system total real
1.660000 0.000000 1.660000 ( 1.669462)
1.830000 0.000000 1.830000 ( 1.831754)
This means yield is marginally faster than &block what we already know btw.
UPDATE: This is IMO the best way to create an each method which also takes care of returning an enumerator
class Test
def each
if block_given?
#data.each{|x| yield(x)}
else
return #data.each
end
end
end
If you really do want to make your own #each method, and assuming you don't want to forward, you should return an Enumerator if no block is given
class MyArrayLikeClass
include Enumerable
def each(&block)
return enum_for(__method__) if block.nil?
#arr.each do |ob|
block.call(ob)
end
end
end
This will return an Enumerable object if no block is given, allowing Enumerable method chaining

Resources