Cloning arrays

Discussion in 'Ruby' started by Len Lawrence, Mar 22, 2008.

  1. Len Lawrence

    Len Lawrence Guest

    Another newbie question. I have started recoding some of my homegrown
    Tcl/Tk applications in Ruby/rubytk and have hit an unexpected snag when
    attempting to clone arrays. The following code is snipped verbatim from
    the program:

    # Calculate coordinates for the Postscript page canvas and the
    # label icons canvas in the setupInkjet window.

    def User.calcxy ( disposition, f )

    xygrid = { }

    # Split the disposition string into x and y counts.
    xy = Typefaces::Disposition[disposition]
    # Obtain the label dimensions.
    labelsize = Typefaces::Labelsizes[disposition]
    # Populate the coordinate data structure.
    xygrid['x'] = nx = xy[0]
    xygrid['y'] = ny = xy[1]
    xygrid['dx'] = dx = labelsize[0] * f
    xygrid['dy'] = dy = labelsize[1] * f
    # Obtain an array of 4 element arrays from the Typefaces module.
    # These represent opposing corners of the address spaces on the
    # Postscript canvas.
    xygrid['page'] = Typefaces.labelgrid( disposition )
    puts 'page coordinates'
    puts xygrid['page'][0]
    puts '______________'
    # Copy the page label coordinates.
    e = xygrid['page'].clone
    # Shrink the label icon coordinates.
    e.each { |z| z.collect! { |d| d *= f } }
    # Shrink the icons further to make separations visible.
    e.each { |z| z[2] -= 2.0; z[3] -= 2.0 }
    # Save the label icon coordinates.
    xygrid['boxes'] = e
    puts 'page again'
    puts xygrid['page'][0]
    puts 'boxes'
    puts e[0]
    puts '=============='
    xygrid['max'] = nx * ny
    return xygrid

    end

    The diagnostics at 'page again' and 'boxes' show that the copy of the
    original array has been successfully processed but the original array now
    reflects the same values. The 'page' and 'boxes' arrays are to be used in
    different circumstances.

    What am I missing? It is bound to be something simple or something
    fundamental. I tried Object#dup also - same result.

    Len
     
    Len Lawrence, Mar 22, 2008
    #1
    1. Advertising

  2. On 22.03.2008 14:10, Len Lawrence wrote:
    > Another newbie question. I have started recoding some of my homegrown
    > Tcl/Tk applications in Ruby/rubytk and have hit an unexpected snag when
    > attempting to clone arrays. The following code is snipped verbatim from
    > the program:
    >
    > # Calculate coordinates for the Postscript page canvas and the
    > # label icons canvas in the setupInkjet window.
    >
    > def User.calcxy ( disposition, f )
    >
    > xygrid = { }
    >
    > # Split the disposition string into x and y counts.
    > xy = Typefaces::Disposition[disposition]
    > # Obtain the label dimensions.
    > labelsize = Typefaces::Labelsizes[disposition]
    > # Populate the coordinate data structure.
    > xygrid['x'] = nx = xy[0]
    > xygrid['y'] = ny = xy[1]
    > xygrid['dx'] = dx = labelsize[0] * f
    > xygrid['dy'] = dy = labelsize[1] * f
    > # Obtain an array of 4 element arrays from the Typefaces module.
    > # These represent opposing corners of the address spaces on the
    > # Postscript canvas.
    > xygrid['page'] = Typefaces.labelgrid( disposition )
    > puts 'page coordinates'
    > puts xygrid['page'][0]
    > puts '______________'
    > # Copy the page label coordinates.
    > e = xygrid['page'].clone
    > # Shrink the label icon coordinates.
    > e.each { |z| z.collect! { |d| d *= f } }
    > # Shrink the icons further to make separations visible.
    > e.each { |z| z[2] -= 2.0; z[3] -= 2.0 }
    > # Save the label icon coordinates.
    > xygrid['boxes'] = e
    > puts 'page again'
    > puts xygrid['page'][0]
    > puts 'boxes'
    > puts e[0]
    > puts '=============='
    > xygrid['max'] = nx * ny
    > return xygrid
    >
    > end
    >
    > The diagnostics at 'page again' and 'boxes' show that the copy of the
    > original array has been successfully processed but the original array now
    > reflects the same values. The 'page' and 'boxes' arrays are to be used in
    > different circumstances.
    >
    > What am I missing? It is bound to be something simple or something
    > fundamental. I tried Object#dup also - same result:


    Your issue is caused by the fact that dup and clone to shallow copies
    only, i.e. both copies of a clone Array contain the exact same
    instances. Now you modify those instances and of course changes are
    seen via both arrays:

    # Shrink the label icon coordinates.
    e.each { |z| z.collect! { |d| d *= f } }
    # Shrink the icons further to make separations visible.
    e.each { |z| z[2] -= 2.0; z[3] -= 2.0 }

    A solution would be to do

    e = xygrid['page'].map do |z|
    z = z.map { |d| d *= f }
    z[2] -= 2.0; z[3] -= 2.0
    z
    end

    Or you can do a deep copy via the usual Marshal idiom

    e = Marshal.load(Marshal.dump(xygrid['page']))

    Kind regards

    robert
     
    Robert Klemme, Mar 22, 2008
    #2
    1. Advertising

  3. On Saturday 22 March 2008, Len Lawrence wrote:
    > Another newbie question. I have started recoding some of my homegrown
    > Tcl/Tk applications in Ruby/rubytk and have hit an unexpected snag when
    > attempting to clone arrays. The following code is snipped verbatim from
    > the program:
    >
    > # Calculate coordinates for the Postscript page canvas and the
    > # label icons canvas in the setupInkjet window.
    >
    > def User.calcxy ( disposition, f )
    >
    > xygrid = { }
    >
    > # Split the disposition string into x and y counts.
    > xy = Typefaces::Disposition[disposition]
    > # Obtain the label dimensions.
    > labelsize = Typefaces::Labelsizes[disposition]
    > # Populate the coordinate data structure.
    > xygrid['x'] = nx = xy[0]
    > xygrid['y'] = ny = xy[1]
    > xygrid['dx'] = dx = labelsize[0] * f
    > xygrid['dy'] = dy = labelsize[1] * f
    > # Obtain an array of 4 element arrays from the Typefaces module.
    > # These represent opposing corners of the address spaces on the
    > # Postscript canvas.
    > xygrid['page'] = Typefaces.labelgrid( disposition )
    > puts 'page coordinates'
    > puts xygrid['page'][0]
    > puts '______________'
    > # Copy the page label coordinates.
    > e = xygrid['page'].clone
    > # Shrink the label icon coordinates.
    > e.each { |z| z.collect! { |d| d *= f } }
    > # Shrink the icons further to make separations visible.
    > e.each { |z| z[2] -= 2.0; z[3] -= 2.0 }
    > # Save the label icon coordinates.
    > xygrid['boxes'] = e
    > puts 'page again'
    > puts xygrid['page'][0]
    > puts 'boxes'
    > puts e[0]
    > puts '=============='
    > xygrid['max'] = nx * ny
    > return xygrid
    >
    > end
    >
    > The diagnostics at 'page again' and 'boxes' show that the copy of the
    > original array has been successfully processed but the original array now
    > reflects the same values. The 'page' and 'boxes' arrays are to be used in
    > different circumstances.
    >
    > What am I missing? It is bound to be something simple or something
    > fundamental. I tried Object#dup also - same result.
    >
    > Len


    I don't know rubytk, so I couldn't completely follow your code, but I think
    your problem is related to the fact that clone and dup both perform shallow
    copies of the array. This means that, while the array itself is duplicated,
    its contents are not. For example, running this code

    a = %w[a b c]
    b = a.dup
    puts "The ID of a is #{a.object_id}"
    puts "The ID of b is #{b.object_id}"
    puts "a is the same object as b? #{a.equal? b}"

    puts "The ID of the contents of a: #{a.map{|i| i.object_id}.join(', ')}"
    puts "The ID of the contents of b: #{b.map{|i| i.object_id}.join(', ')}"
    a.each_index do |i|
    puts "a[#{i}] is the same object as b[#{i}]? #{a.equal? b}"
    end

    produces:
    The ID of a is -605837488
    The ID of b is -605837528
    a is the same object as b? false
    The ID of the contents of a: -605837498, -605837508, -605837518
    The ID of the contents of b: -605837498, -605837508, -605837518
    a[0] is the same object as b[0]? true
    a[1] is the same object as b[1]? true
    a[2] is the same object as b[2]? true

    This means that while a and b are not the same object (and so, you can modify
    one, for example adding or removing an item, leaving the other as is), they
    contain the same objects. So, calling 'destructive' methods (that is, methods
    which change the receiver) on the elements of one of the array will also
    change the corresponding element of the other array (because they contain the
    same object).

    To achieve what you want, you need deep copy. As someone on this list recently
    pointed out, you can usually achieve this using marshal:

    b = Marshal.load(Marshal.dump(a))

    The problem is that with rubytk you're dealing with a C extension, and with
    objects created in C, which may, or may not, work correctly with marshal. If
    you find out they don't, you can try to manually deep-duplicate the array. In
    the simple case of my example above, you can do:

    b = a.map{|i| a.dup}

    If your array is a nested array, things become more complicated, as you'd need
    nested loops. You also need to keep in mind that object created from C
    extensions may not be clonable. In this case, there's nothing to do.

    I hope this helps

    Stefano
     
    Stefano Crocco, Mar 22, 2008
    #3
  4. Len Lawrence

    Len Lawrence Guest

    On Sat, 22 Mar 2008 14:31:51 +0100, Robert Klemme wrote:

    > On 22.03.2008 14:10, Len Lawrence wrote:
    >>

    > A solution would be to do
    >
    > e = xygrid['page'].map do |z|
    > z = z.map { |d| d *= f }
    > z[2] -= 2.0; z[3] -= 2.0
    > z
    > end
    >
    > Or you can do a deep copy via the usual Marshal idiom
    >
    > e = Marshal.load(Marshal.dump(xygrid['page']))
    >
    > Kind regards
    >
    > robert


    Thanks Robert. So the issue was fundamental. I had not yet come to
    appreciate the meaning of 'shallow copy' or come to terms with map or
    Marshal. The Tk connection here is actually irrelevant.

    That has been a great help.

    Len
     
    Len Lawrence, Mar 22, 2008
    #4
  5. Len Lawrence

    Len Lawrence Guest

    On Sat, 22 Mar 2008 08:37:46 -0500, Stefano Crocco wrote:

    > On Saturday 22 March 2008, Len Lawrence wrote:

    0
    > a = %w[a b c]
    > b = a.dup
    > puts "The ID of a is #{a.object_id}"
    > puts "The ID of b is #{b.object_id}"
    > puts "a is the same object as b? #{a.equal? b}"
    >
    > puts "The ID of the contents of a: #{a.map{|i| i.object_id}.join(', ')}"
    > puts "The ID of the contents of b: #{b.map{|i| i.object_id}.join(', ')}"
    > a.each_index do |i|
    > puts "a[#{i}] is the same object as b[#{i}]? #{a.equal? b}"
    > end
    >
    > produces:
    > The ID of a is -605837488
    > The ID of b is -605837528
    > a is the same object as b? false
    > The ID of the contents of a: -605837498, -605837508, -605837518
    > The ID of the contents of b: -605837498, -605837508, -605837518
    > a[0] is the same object as b[0]? true
    > a[1] is the same object as b[1]? true
    > a[2] is the same object as b[2]? true
    >
    > This means that while a and b are not the same object (and so, you can modify
    > one, for example adding or removing an item, leaving the other as is), they
    > contain the same objects. So, calling 'destructive' methods (that is, methods
    > which change the receiver) on the elements of one of the array will also
    > change the corresponding element of the other array (because they contain the
    > same object).
    >
    > To achieve what you want, you need deep copy. As someone on this list


    > recently pointed out, you can usually achieve this using marshal:
    >
    > b = Marshal.load(Marshal.dump(a))
    >
    > The problem is that with rubytk you're dealing with a C extension, and
    > with objects created in C, which may, or may not, work correctly with
    > marshal. If you find out they don't, you can try to manually
    > deep-duplicate the array. In the simple case of my example above, you
    > can do:
    >
    > b = a.map{|i| a.dup}
    >
    > If your array is a nested array, things become more complicated, as


    > you'd need nested loops. You also need to keep in mind that object
    > created from C extensions may not be clonable. In this case, there's
    > nothing to do.
    >
    > I hope this helps


    Thanks Stefano; it sure does. I missed the earlier references to
    Marshalling - not enough time to keep up with the newsgroup. I can cope
    with nested arrays. Anyhow, many thanks for the education.

    Len
     
    Len Lawrence, Mar 22, 2008
    #5
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Guest

    Cloning Possible?

    Guest, Nov 10, 2003, in forum: ASP .Net
    Replies:
    1
    Views:
    409
    Steve C. Orr [MVP, MCSD]
    Nov 11, 2003
  2. =?Utf-8?B?QVZM?=

    cloning

    =?Utf-8?B?QVZM?=, Dec 14, 2004, in forum: ASP .Net
    Replies:
    3
    Views:
    504
    Eliyahu Goldin
    Dec 14, 2004
  3. Andreas Leitgeb

    arrays and cloning, where is it described?

    Andreas Leitgeb, Mar 5, 2008, in forum: Java
    Replies:
    26
    Views:
    625
    Andreas Leitgeb
    Mar 11, 2008
  4. Philipp
    Replies:
    21
    Views:
    1,186
    Philipp
    Jan 20, 2009
  5. Len Lawrence

    Cloning arrays

    Len Lawrence, Mar 22, 2008, in forum: Ruby
    Replies:
    1
    Views:
    104
    ara howard
    Mar 22, 2008
Loading...

Share This Page