Non destructive methods in Ruby - ruby

I have a class which inherits from Array (in practice, it's just a mask for a multidimensional array).
I want to override its to_a method:
def to_a
self.each.with_index { |el, i| el.map {|j| j} }
end
but this messes things up: when I try to test my function:
it 'should be non destructive' do
a_board = Representation.new(#a_size)
a_clean_board = Representation.new(#a_size)
expect(a_board).to eq(a_clean_board)
# Try to modify a_board
arr = a_board.to_a
arr.pop
a_board.to_a.pop
# Check that it stayed equal to a_clean_board
expect(a_board).to eq(a_clean_board)
end
both calls to pop have side effects on the original board.
How can I avoid this?

Probably because it returns reference to the same object. To avoid this use map instead of each or use .dup at the end.
UPD
As I said, just use map. Like it's in functional programming where intentionally are no side-effects. Example:
class WrappedArr < Array
def to_a
map { |el| el.map {|el2| el2 } }
end
end
w_arr = WrappedArr.new([[1,2], [2,3]])
# => [[1, 2], [2, 3]]
2.0.0p247 :012 > w_arr.to_a.object_id # always different as it is different object
#=> 70318090081080
2.0.0p247 :013 > w_arr.to_a.object_id
# => 70318095088040
2.0.0p247 :014 > w_arr.to_a.object_id
# => 70318095081540
# final test
2.0.0p247 :015 > w_arr.to_a.pop
# => [2, 3]
2.0.0p247 :016 > w_arr
# => [[1, 2], [2, 3]]

Related

Ruby - explain code snippet hash sorter

I use this code for sorting Hash;
I've got no idea how its works.
please explain to me:
def foo(hash)
Hash[hash.sort]
end
irb(main):001:0> h = {1=>'z', 3=>'x', 2=>'y'}
=> {1=>"z", 3=>"x", 2=>"y"}
irb(main):002:0> Hash[h.sort]
=> {1=>"z", 2=>"y", 3=>"x"}
irb(main):003:0>
Enumerable#sort reutrns an sorted array of key-value pairs:
h = {b: 1, a: 2}
h.sort
# => [[:a, 2], [:b, 1]]
Hash::[] create a new hash base on the argument:
Hash[h.sort]
# => {:a=>2, :b=>1}
BTW, if you use Ruby 2.1+, you can use Array#to_h instead:
h.sort.to_h
# => {:a=>2, :b=>1}

Changing state of Ruby objects changes other class members?

I have a class that primarily implements some logic around a multi-dimensional array, which is essentially a grid of numbers. These numbers can swap positions, etc.. However, when they swap, other objects of the same class also appear to be modified. I'm not sure why.
I'm using instance variables to store the grid, so I don't understand why changes are apparently affecting other class members.
Here's a simplified example;
class TestGrid
attr_accessor :grid
#grid = []
def initialize(newgrid)
#grid = newgrid
end
def to_s
out = ""
#grid.each{|row|
out += row.join("|") + "\n"
}
out
end
def swap(x, y)
#grid[x[0]][x[1]], #grid[y[0]][y[1]] = #grid[y[0]][y[1]], #grid[x[0]][x[1]]
end
end
When we interact with a single instance in IRB, things look fine;
1.9.3-p385 :001 > require './TestGrid.rb'
=> true
1.9.3-p385 :002 > x = TestGrid.new([[1,2],[3,4]])
=> 1|2
3|4
1.9.3-p385 :003 > x.swap([0,1],[1,1])
=> [4, 2]
1.9.3-p385 :004 > puts x
1|4
3|2
=> nil
However, if I create a second instance by cloning or duping;
1.9.3-p385 :006 > x = TestGrid.new([[1,2],[3,4]])
=> 1|2
3|4
1.9.3-p385 :007 > y = x.clone
=> 1|2
3|4
1.9.3-p385 :008 > x.swap([0,1],[1,1])
=> [4, 2]
1.9.3-p385 :009 > puts x
1|4
3|2
=> nil
1.9.3-p385 :010 > puts y
1|4
3|2
=> nil
Why are my changes to x also being applied to y? From my understanding of Object#Clone, theses are supposed to be distinct instance, unrelated to each other. Their object ID's would seem to support that expectation;
1.9.3-p385 :012 > puts "#{x.object_id} #{y.object_id}"
70124426240320 70124426232820
For reference, I ended up creating an initialize_copy method which ensures the affected parameter is deep copied. I didn't really like the idea of Marshalling objects around just to copy an array deeply, so I decided on this instead.
def initialize_copy(original)
super
#grid = []
original.grid.each{|inner|
#grid << inner.dup
}
end
By default, dup and clone produce shallow copies of the objects they are invoked on. Meaning that x and y in your example still reference the same area of memory.
http://ruby-doc.org/core-2.0/Object.html#method-i-dup
http://ruby-doc.org/core-2.0/Object.html#method-i-clone
You can override them inside of your customized class to produce a deep copy in a different area of memory.
A common idiom in Ruby is to use the Marshal#load and Marshal#dump methods of the Object superclass to produce deep copies. (Note: these methods are normally used to serialize/deserialze objects).
def dup
new_grid = Marshal.load( Marshal.dump(#grid) )
new_grid
end
irb(main):007:0> x = TestGrid.new([[1,2],[3,4]])
=> 1|2
3|4
irb(main):008:0> y = x.dup
=> [[1, 2], [3, 4]]
irb(main):009:0> x.swap([0,1],[1,1])
=> [4, 2]
irb(main):010:0> puts x
1|4
3|2
=> nil
irb(main):011:0> y
=> [[1, 2], [3, 4]]
irb(main):012:0> puts y
1
2
3
4
=> nil
irb(main):013:0>
y remains the same after the swap.
Alternatively, create a new array, iterate through #grid and push its subarrays into the array.
def dup
new_grid = []
#grid.each do |g|
new_grid << g
end
new_grid
end

correct way of using hash sort in ruby

I'm new to ruby and I'm trying to write a dijkstra function but my hash sort seems doesn't work at all
def distance(start_code, end_code, map)
#initialize hash for distance
#distance are initialized to -1
dist_hash=Hash.new()
start_hash=Hash.new()
parent_hash=Hash.new()
close_list=Array.new()
find=-1
map.citylist.each do |e|
dist_hash[e]=[+1.0/0.0]
end
start_hash[start_code]=0
parent_hash[start_code]=start_code
while (start_hash.empty?)==false
#sort the hash
start_hash.sort_by {|k,v| v}
puts 'value'
puts start_hash.values()
#pop the first item in the hash
h=start_hash.shift()
curr_key=h[0]
curr_val=h[1]
curr_city=map.findcity(curr_key)
close_list<<curr_city.code
#for every one in adjacent list
curr_city.get_adj_city().each do |e|
#if it in the close list then igonore
if close_list.include?(e)==false
#if it is not in the start_hash then add to start hash
if start_hash.has_key?(e)==false
dist=map.adj_dist(curr_city.code, e)
dist=dist+curr_val
start_hash[e]=dist
parent_hash[e]=curr_city.code
#if it is in the start_hash check if we have better distance
else
dist=map.adj_dist(curr_city.code, e)
if (dist+curr_val)<start_hash[e]
parent_hash[e]=curr_city.code
start_hash[e]=dist
end
end
#end pf checking single adj city
end
#end of check if include in close
end
#end of check whole list
if curr_city.code==end_code
find=0
break
end
end
#end of check node
#result
if find==0
ptr=end_code
puts ptr
puts "final list"
while ptr!=start_code
ptr=parent_hash[ptr]
puts ptr
end
return 0
else
return -1
end
end
When I'm trying to call d.distance("BUE", "LOS", map)
the output looks like
value
0
value
1680
4651
value
10053
8047
4651
value
11094
15839
15839
8047
4651
10779
....
the values are printed out right after hash.sort_by but not sorted. Am I using the method correctly?
Ruby 1.9 actually has ordered hashes, so if you do want to continue to work on the sorted result as a Hash, you can simply turn the array into Hash again:
h = {:a=>1, :c=>3, :b=>5, :d=>2} # => {:a=>1, :c=>3, :b=>5, :d=>2}
h_sorted = Hash[h.sort_by{|k,v| v}] # => {:a=>1, :d=>2, :c=>3, :b=>5}
the values are printed out right after hash.sort_by but not sorted. Am I using the method correctly?
No. When I'm not sure how something works, I open up IRB and try a few things with it:
hash = {a:1, b:2, c:4, d: 3}
=> {:a=>1, :b=>2, :c=>4, :d=>3}
hash.sort
=> [[:a, 1], [:b, 2], [:c, 4], [:d, 3]]
hash
=> {:a=>1, :b=>2, :c=>4, :d=>3}
hash.sort_by{|k,v| v }
=> [[:a, 1], [:b, 2], [:d, 3], [:c, 4]]
hash
=> {:a=>1, :b=>2, :c=>4, :d=>3}
sort_by does not alter the hash, it returns a result. Try:
hash = hash.sort_by{|k,v| v } # <- don't use this, it's an array and you'll mislead anyone reading this code.
sorted_tuples = hash.sort_by{|k,v| v }
or something like it.
Try this
hash = {
"fred" => 23,
"joan" => 18,
"pete" => 54
}
hash.values.sort # => [18, 23, 54]
hash.sort_by { |name, age| age } # => [["joan", 18], ["fred", 23], ["pete", 54]]
hash.sort_by { |name, age| name } # => [["fred", 23], ["joan", 18], ["pete", 54]]

Searching for range overlaps in Ruby hashes

Say you have the following Ruby hash,
hash = {:a => [[1, 100..300],
[2, 200..300]],
:b => [[1, 100..300],
[2, 301..400]]
}
and the following functions,
def overlaps?(range, range2)
range.include?(range2.begin) || range2.include?(range.begin)
end
def any_overlaps?(ranges)
# This calls to_proc on the symbol object; it's syntactically equivalent to
# ranges.sort_by {|r| r.begin}
ranges.sort_by(&:begin).each_cons(2).any? do |r1, r2|
overlaps?(r1, r2)
end
end
and it's your desire to, for each key in hash, test whether any range overlaps with any other. In hash above, I would expect hash[:a] to make me mad and hash[:b] to not.
How is this best implemented syntactically?
hash.each{|k, v| puts "#{k} #{any_overlaps?( v.map( &:last )) ? 'overlaps' : 'is ok'}."}
output:
a overlaps.
b is ok.
Here's another way to write any_overlaps:
def any_overlaps?(ranges)
(a = ranges.map { |r| [r.first, r.last] }.sort_by(&:first).flatten) != a.sort
end
any_overlaps? [(51..60),(11..20),(18..30),(0..10),(31..40)] # => true
any_overlaps? [(51..60),(11..20),(21..30),(0..10),(31..40)] # => false

Ruby: provide an argument while turning proc to a block

We can easily define a method and turn it into block with unary ampersand.
def my_method(arg)
puts arg*2
end
['foo', 'bar'].each(&method(:my_method))
# foofoo
# barbar
# or
my_method = ->(arg) { puts arg*2 }
['foo', 'bar'].each(&my_method)
# same output
As we see the first argument is passed automatically when we work with aggregates. But what if we need to pass 2 or even more arguments?
my_method = ->(arg,num) { puts arg*num }
['foo', 'bar'].each(&my_method)
# ArgumentError: wrong number of arguments (1 for 2)
['foo', 'bar'].each(&my_method(3))
# NoMethodError: undefined method `foo' for main:Object
['foo','bar'].each do |i, &my_method|
yield i, 3
end
# LocalJumpError: no block given (yield)
Is that possible to pass additional arguments while turning proc to a block?
#sawa is right. You can do that with curry.
Proc version:
mult = proc {|a, b| a * b} # => #<Proc:0x00000002af1098#(irb):32>
[1, 2].map(&mult.curry[2]) # => [2, 4]
Method version:
def mult(a, b)
a*b
end
[1, 2].map(&method(:mult).to_proc.curry[2]) # => [2, 4]
Regarding your comment:
Strange, but it swaps arguments during the performance
Actually, the argument order is preserved.
curry returns a new proc that effectively collects arguments until there are enough arguments to invoke the original method / proc (based on its arity). This is achieved by returning intermediate procs:
def foo(a, b, c)
{ a: a, b: b, c: c }
end
curried_proc = foo.curry #=> #<Proc:0x007fd09b84e018 (lambda)>
curried_proc[1] #=> #<Proc:0x007fd09b83e320 (lambda)>
curried_proc[1][2] #=> #<Proc:0x007fd09b82cfd0 (lambda)>
curried_proc[1][2][3] #=> {:a=>1, :b=>2, :c=>3}
You can pass any number of arguments at once to a curried proc:
curried_proc[1][2][3] #=> {:a=>1, :b=>2, :c=>3}
curried_proc[1, 2][3] #=> {:a=>1, :b=>2, :c=>3}
curried_proc[1][2, 3] #=> {:a=>1, :b=>2, :c=>3}
curried_proc[1, 2, 3] #=> {:a=>1, :b=>2, :c=>3}
Empty arguments are ignored:
curried_proc[1][][2][][3] #=> {:a=>1, :b=>2, :c=>3}
However, you obviously can't alter the argument order.
An alternative to currying is partial application which returns a new proc with lower arity by fixing one or more arguments. Unlike curry, there's no built-in method for partial application, but you can easily write your own:
my_proc = -> (arg, num) { arg * num }
def fix_first(proc, arg)
-> (*args) { proc[arg, *args] }
end
fixed_proc = fix_first(my_proc, 'foo') #=> #<Proc:0x007fa31c2070d0 (lambda)>
fixed_proc[2] #=> "foofoo"
fixed_proc[3] #=> "foofoofoo"
[2, 3].map(&fixed_proc) #=> ["foofoo", "foofoofoo"]
Or fixing the last argument:
def fix_last(proc, arg)
-> (*args) { proc[*args, arg] }
end
fixed_proc = fix_last(my_proc, 2) #=> #<Proc:0x007fa31c2070d0 (lambda)>
fixed_proc['foo'] #=> "foofoo"
fixed_proc['bar'] #=> "barbar"
['foo', 'bar'].map(&fixed_proc) #=> ["foofoo", "barbar"]
Of course, you are not limited to fixing single arguments. You could for example return a proc that takes an array and converts it to an argument list:
def splat_args(proc)
-> (array) { proc[*array] }
end
splatting_proc = splat_args(my_proc)
[['foo', 1], ['bar', 2], ['baz', 3]].map(&splatting_proc)
#=> ["foo", "barbar", "bazbazbaz"]

Resources