Christopher Byrd Christopher Byrd - 1 year ago 62
Ruby Question

Using self.dup, but failing rspec test to not modify original array

I'm creating a method to transpose square 2-d arrays. My method passes every test, except the "does not modify original array" one. I'm only working on the duped array, so I'm confused on why the test is failing.


class Array
def my_transpose
orig_arr = self.dup; array = []
orig_arr[0].length.times do
temp_arr = []
orig_arr.each { |arr| temp_arr << arr.shift }
array << temp_arr


describe Array do

describe "#my_transpose" do
let(:arr) { [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
] }

let(:small_arr) { [
[1, 2],
[3, 4]
] }

it "transposes a small matrix" do
expect(small_arr.my_transpose).to eq([
[1, 3],
[2, 4]

it "transposes a larger matrix" do
expect(arr.my_transpose).to eq([
[1, 4, 7],
[2, 5, 8],
[3, 6, 9]

it "should not modify the original array" do

expect(small_arr).to eq([
[1, 2],
[3, 4]

it "should not call the built-in #transpose method" do
expect(arr).not_to receive(:transpose)



7) Array#my_transpose should not modify the original array
Failure/Error: expect(small_arr).to eq([

expected: [[1, 2], [3, 4]]
got: [[], []]

(compared using ==)
# ./spec/00_array_extensions_spec.rb:123:in `block (3 levels) in <top (required)>'

Answer Source

When you call dup on an array, it only duplicates the array itself; the array's contents are not also duplicated. So, for example:

a = [[1,2],[3,4]]
b = a.dup
a.object_id == b.object_id        # => false
a[0].object_id == b[0].object_id  # => true

Thus, modifications to a itself are not reflected in b (and vice versa), but modifications in the elements of a are reflected in b, because those elements are the same objects.

That being the case, the problem crops up here:

orig_arr.each { |arr| temp_arr << arr.shift }

arr is an element of orig_arr, but it is also an element of self. If you did something like remove it from orig_arr, you would not also remove it from self, but if you change it, it's changed, no matter how you are accessing it, and as it turns out, Array#shift is a destructive operation.

Probably the smallest change you could make to your code to make it work as you expect would be to use each_with_index, and then use the index into arr, rather than calling arr.shift, so:

orig_arr.each_with_index { |arr,i| temp_arr << arr[i] }

In fact, though, once you're doing that, you're not doing any destructive operations at all and you don't need orig_arr, you can just use self.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download