简体   繁体   中英

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.

Code:

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
    end
    array
  end
end

RSpec:

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]
      ])
    end

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

    it "should not modify the original array" do
      small_arr.my_transpose

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

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

      arr.my_transpose
    end
  end
end

Output:

  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)>'

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 .

The original array isn't being modified, but the arrays within it are, as dup is a shallow clone.

xs = [[1,2],[3,4]]
ids = xs.map(&:object_id)
xs.my_transpose
ids == xs.map(&:object_id)  #=> true

Since shift is a mutating operation (being performed on the nested array elements), you need to dup the elements within the array as well, eg

orig_arr = dup.map(&:dup)

With this modification, your test should pass.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM