This tripped me up (again) today

This tripped me up (again) today

Postby Jim » Sat Aug 07, 2010 12:08 am

Code: Select all
a = %w( one two three)
==> ["one", "two", "three"]

b = a.dup
==> ["one", "two", "three"]

b.each {|e| e.gsub!(/o/, 'z')}
==> ["zne", "twz", "three"]

p a
==> ["zne", "twz", "three"]


So even though I am operating on b which is a copy of the Array a, the elements of a are still modified. If you are not familiar with why, then this is worth spending a few minutes to figure out; or figuring out again, in my case.
0
Hi

Jim 
Global Moderator
 

Re: This tripped me up (again) today

Postby TIG » Sat Aug 07, 2010 10:26 am

If I set a = [1, 2 ,3]
Then b = a.dup (or b=[]+a)
Then b[0]=0
I get
a ==> [1, 2 ,3]
b ==> [0, 2 ,3]

If I set a=b
I get
a ==> [0, 2 ,3]
b ==> [0, 2 ,3]

Then b[0]=99 I get
a ==> [99, 2 ,3]
b ==> [99, 2 ,3]

because the variables a and b will then both refer to the same array?
Perhaps your a = %w( one two three)
is not quite the same as a = ["one", "two", "three"]?
:?
0
TIG
User avatar
TIG 
Global Moderator
 

Re: This tripped me up (again) today

Postby Dan Rathbun » Sat Aug 07, 2010 11:24 am

TIG wrote: because the variables a and b will then both refer to the same array?

The references (not variables!) do not refer to the same array after the b = a.dup call. (Check their object ids.)
They DO however point at the SAME objects in YOUR example because Integers are Immediate objects (there is ONLY 1 of each ordinal in the set of Ruby Integers.)
After you ref a to point at the object that b points at, then both refs point at the same Array object.

In Jim's example, he uses Strings, which are NOT Immediate objects. You can have more than 1 String "one", but in his case, the .dup method copies the Array object, but does not duplicate the individual element objects.

It seems "nutty" but this is one of the lessons rubyists should be taught early. In Ruby, everything is an object, every object has a reference. Literal arguments, are converted to objects and assigned an anonymous reference by RUBY. Since the element objects in the array have a 'parent' object that IS referenced, they will not be swept up by Ruby's GC, even though they don't have a explicit reference.

So.. an array is not a set of values (like it may be in BASIC,) in Ruby an array is a set of references, either explicit (managed by YOU,) or anonymous (managed by Ruby.)
When you dup or clone the array, you copy the references.

If you wish a completely different array:
b = [] # a new Array object
a.each {|e| b << e.dup}


TIP In Ruby = is NOT "equals" (which is why there is an .equal? method.)
It is the assignment operator, so your better off thinking and saying 'SPA' (Shall Point At.) Read:
a = b
as "The reference a shall point at the object that b references."

TIG wrote:Perhaps your a = %w( one two three)
is not quite the same as a = ["one", "two", "three"]?

It IS the same, it's just a Ruby "TimToady."
0
    I'm not here much anymore. But a PM will fire email notifications.
    User avatar
    Dan Rathbun 
    PluginStore Author
    PluginStore Author
     

    Re: This tripped me up (again) today

    Postby Dan Rathbun » Sat Aug 07, 2010 12:04 pm

    For further reading, see this topic on References:
    By value or by reference?
    0
      I'm not here much anymore. But a PM will fire email notifications.
      User avatar
      Dan Rathbun 
      PluginStore Author
      PluginStore Author
       

      Re: This tripped me up (again) today

      Postby Dan Rathbun » Sat Aug 07, 2010 12:18 pm

      This is interesting, in Ruby 1.9.x Core Docs, there is a method called:
      Array.dclone

      The source shows that it creates a totally new array, including new cloned elements, and handles nested arrays. (It's a recursive method.)

      Code: Select all
      # File lib/rexml/xpath_parser.rb, line 22
        def dclone
          klone = self.clone
          klone.clear
          self.each{|v| klone << v.dclone}
          klone
        end

      Looks like it's an Array class extension defined in the REXML extended Library.
      0
        I'm not here much anymore. But a PM will fire email notifications.
        User avatar
        Dan Rathbun 
        PluginStore Author
        PluginStore Author
         

        Re: This tripped me up (again) today

        Postby Jim » Sat Aug 07, 2010 12:21 pm

        It might be eaier to explain by looking at object id's:

        Code: Select all
        >> a = %w( one two three )
        => ["one", "two", "three"]
        >> b = a
        => ["one", "two", "three"]
        >> a.object_id
        => 23021928
        >> b.object_id
        => 23021928


        Here is an image what what this means in the computer. Both a and b now reference the same object, an Array. However, note that each array element is a reference to another object.

        ba.png


        When we .dup a, we get a new Array object, including it's elements. But remember the elements are just references to the original objects.

        Code: Select all
        >> a = %w( one two three )
        => ["one", "two", "three"]
        >> b = a.dup
        => ["one", "two", "three"]
        >> a.object_id
        => 23021928
        >> b.object_id
        => 22854408


        ba(3).png


        Which can be seen by:

        a[0].object_id == b[0].object_id
        => true


        So when you .dup an array, you get a new array. Because the elements of the original array are references to a non-primitive type (String in this case,) the new array's elements reference the original elements.

        As Dan says, there are no variables in Ruby, only references to object.

        Here's someone else's explaination

        When you copy an object, all you do is copy its set of instance variables, which are just references to other objects. For an array, the instance variables are its set of indexes, which again are just references. Copying an array just means making a new list of references, but the objects they point to remain unmodified and uncopied.
        0
        Last edited by Jim on Sat Aug 07, 2010 5:29 pm, edited 1 time in total.
        Hi

        Jim 
        Global Moderator
         

        Re: This tripped me up (again) today

        Postby thomthom » Sat Aug 07, 2010 2:56 pm

        Are .dup and .clone aliases?
        0
        Thomas Thomassen — SketchUp Monkey & Coding addict
        List of my plugins and link to the CookieWare fund
        User avatar
        thomthom 
        PluginStore Author
        PluginStore Author
         

        Re: This tripped me up (again) today

        Postby TIG » Sat Aug 07, 2010 3:36 pm

        I think so. :roll:
        0
        TIG
        User avatar
        TIG 
        Global Moderator
         

        Re: This tripped me up (again) today

        Postby Jim » Sat Aug 07, 2010 3:42 pm

        thomthom wrote:Are .dup and .clone aliases?


        No, but I don't know the details.
        0
        Hi

        Jim 
        Global Moderator
         

        Re: This tripped me up (again) today

        Postby AdamB » Sat Aug 07, 2010 4:48 pm

        FWIW This whole area of Comp Sci is pretty tricky to resolve in programming languages generally because ultimately these references are not 'semantic free'.
        Sometimes you want a 'deep' copy and sometimes you want a 'shallow' copy. The classic example is if you have a class Car which has an attribute which is a reference to the car manufacturer, when I copy the car, I generally wouldn't want to deep copy the entire Ford motor company (or whatever).

        So languages essentially cannot a priori "know" what the meaning and therefore the intent of these references are, hence we have to either manually descend a parts hierarchy choosing to copy or not copy, or keep a top-level reference.

        Adam
        0
        Developer of LightUp Click for website
        User avatar
        AdamB 
        LightUp Support
        LightUp Support
         

        Re: This tripped me up (again) today

        Postby Dan Rathbun » Sun Aug 08, 2010 2:20 am

        thomthom wrote:Are .dup and .clone aliases?

        NO, even though both create "shallow" copies:
        • .clone copies the frozen and tainted state of the receiver object.
        • .dup copies the tainted state of the receiver object.

        In addition:
        Programming Ruby in Object#dup wrote:In general, clone and dup may have different semantics in descendent classes. While clone is used to duplicate an object, including its internal state, dup typically uses the class of the descendent object to create the new instance.

        In general it is always safer to try using dup first (especially if you don't want to get a frozen object.)

        Dan Rathbun in Re: Face.clone wrote:
        kwalkerman wrote:So apparently, you can do face.clone, ... I if so, I might be able to determine what is happening with face.clone

        Karen,

        The .clone and .dup methods come from standard Ruby class Object, and create "shallow" copies. Ie, (from the book,) "the instance variables of the obj are copied, but not the objects they reference."

        FYI: I went on to say not to use these methods with Sketchup's C++ objects (... until they get overriden methods that work correctly.)
        0
          I'm not here much anymore. But a PM will fire email notifications.
          User avatar
          Dan Rathbun 
          PluginStore Author
          PluginStore Author
           

          Re: This tripped me up (again) today

          Postby thomthom » Sun Aug 08, 2010 10:34 am

          I use clone a lot for SketchUp's objects...
          What is the issue with SU's .clone?
          0
          Thomas Thomassen — SketchUp Monkey & Coding addict
          List of my plugins and link to the CookieWare fund
          User avatar
          thomthom 
          PluginStore Author
          PluginStore Author
           

          Re: This tripped me up (again) today

          Postby Dan Rathbun » Sun Aug 08, 2010 1:05 pm

          thomthom wrote:I use clone a lot for SketchUp's objects...
          What is the issue with SU's .clone?


          It WILL work for any classes that are subclasses of standard Ruby classes, such as Length, which is a subclass of Float.

          It WILL work for any custom Sketchup classes that provide an overidden version of clone, such as:
          Geom::Point3d
          Geom::Transformation
          Geom::Vector3d
          * note that these work like dup and not clone (in that they do not copy the frozen state of the receiver.)

          The Ruby inherited edition will not work for many C++ objects like Sketchup::Face.

          To get around this, TIG shows in the other thread, how to use Sketchup::Group.copy to "clone" Sketchup::Drawingelement subclass objects, like Faces, Edges, etc.

          Geom::BoundingBox objects can be copied by using their .add method (if clone or dup does not work):
          bb2 = Geom::BoundingBox.new.add( bb1 )
          0
            I'm not here much anymore. But a PM will fire email notifications.
            User avatar
            Dan Rathbun 
            PluginStore Author
            PluginStore Author
             

            Re: This tripped me up (again) today

            Postby thomthom » Sun Aug 08, 2010 2:09 pm

            So SU doesn't override .dup for Point3d and Vector3d?
            That would explain why I had problems before when I tried .dup for Point3d. I've had to use .clone.
            0
            Thomas Thomassen — SketchUp Monkey & Coding addict
            List of my plugins and link to the CookieWare fund
            User avatar
            thomthom 
            PluginStore Author
            PluginStore Author
             

            Re: This tripped me up (again) today

            Postby Dan Rathbun » Sun Aug 08, 2010 3:14 pm

            thomthom wrote:So SU doesn't override .dup for Point3d and Vector3d?
            That would explain why I had problems before when I tried .dup for Point3d. I've had to use .clone.

            Yepper... I remember having that discussion with you in another topic thread.

            Either most the Sketchup classes need to override Ruby's .dup and .clone with methods that work, or they should be removed for those classes.

            And... a point about .freeze, we would not want to freeze most of the Ruby objects that Sketchup needs to modify (data classes and any object class that is kept in the model.) So copying the frozen state doesn't mean much, so the overriden dup could likely be just an alias for the overriden clone.

            In most Sketchup classes the .freeze method should be also removed. It can be done for many of them, by removing it from Sketchup::Entity.
            0
              I'm not here much anymore. But a PM will fire email notifications.
              User avatar
              Dan Rathbun 
              PluginStore Author
              PluginStore Author
               

              Re: This tripped me up (again) today

              Postby TIG » Sun Aug 08, 2010 6:11 pm

              I passing... IF you want to make a completely separate array based on another array's 'reference' then us
              []+
              so
              a=[1,2]
              b=[]+a

              makes array a and array b separate arrays as
              a[0]=99
              gives
              a ==> [99,2]
              but b is not affected
              b ==> [1,2]
              ?
              0
              TIG
              User avatar
              TIG 
              Global Moderator
               

              An independent copy of a Ruby array

              Postby August » Mon Jul 27, 2015 11:05 pm

              I am SO glad that I found this 5-year-old thread. I, too, was having trouble copying an array and having the copy be independent of the original.

              In my case, I had an array of points that I had generated, and I wanted to create a closed curve from them. My strategy was to copy the point array
              curve = []
              curve = point
              and then add point[0] to the end,
              curve << point[0]
              so that a curve created from the new curve[] array would return to its beginning point.
              path = ents.add_curve curve

              But the above sequence kept adding the extra point to the point array too.

              What I finally kludged around to was
              curve = []
              curve = point[0..(point.length - 1)]
              curve << point[0]
              path = ents.add_curve curve

              Using the sample code from the above discussion, I have tested and confirmed that
              b = a.dup
              and
              b = a.clone
              both have the same problem as my initial
              curve = point

              And I have confirmed that
              b = a.dclone
              gives an error.
              I'm using SU 15, which is supposed to have Ruby 2.0 and Dan notes that dclone is in 1.9, so I'm confused.

              I do have a kludge that works. Maybe it's not so much of a kludge after all. I understand the issue a little more, and hopefully I will remember it in the future.

              FWIW, I hope this helps,
              August
              0
              “An idea, like a ghost, must be spoken to a little before it will explain itself.”
              -- Charles Dickens

              August 
               

              Re: This tripped me up (again) today

              Postby TIG » Tue Jul 28, 2015 11:36 am

              The assignment
              curve = point
              means the array 'curve' refers to the array 'point' - they are essentially referencing the same thing, whereas...
              curve = point.clone
              means the array 'curve' is a separate array, which has been made as a copy from the array 'point',
              Consider this...
              curve = point + [ point[0] ]
              which achieves you aim for a 'curve' array defining a 'loop', but done in the one step.
              It combines the array 'point' and a new array made from the first element of that array, all in a new array named 'curve'.

              Incidentally consider naming arrays and other collections in the plural - it is is easy to follow the code - so the array named 'points' consists of a collection of elements, each of which is a 'point'.
              The 'curve' array would also perhaps be better named 'curve_points'
              0
              TIG
              User avatar
              TIG 
              Global Moderator
               

              Re: This tripped me up (again) today

              Postby August » Tue Jul 28, 2015 6:00 pm

              Thanks TIG.

              I had not tried using that additional set of square brackets. It makes sense.

              As for naming, I thought about using plural in the first place, but most of my usages were as singular references, point[0], point[1], ... point[n-1], point[n] where the singular read better to me. I like "curve_points" -- that is always used as a group, never individually, so plural reads much better there.

              Thanks,
              August
              0
              “An idea, like a ghost, must be spoken to a little before it will explain itself.”
              -- Charles Dickens

              August 
               

              Re: This tripped me up (again) today

              Postby August » Tue Jul 28, 2015 8:14 pm

              TIG, I'm not sure that clone does what you say it does.

              This page, <http://lukaszwrobel.pl/blog/copy-object-in-ruby> suggests that a clone's elements still point to the original elements so some changes to one will indeed show up in the other. That's what I found with my initial testing.

              Above, Dan says
              It WILL work for any custom Sketchup classes that provide an overidden version of clone, such as:
              Geom::Point3d
              Geom::Transformation
              Geom::Vector3d
              * note that these work like dup and not clone (in that they do not copy the frozen state of the receiver.)

              The Ruby inherited edition will not work for many C++ objects like Sketchup::Face.
              So I didn't use .clone.

              And yet, I just tried in the SU Ruby console:
              Code: Select all
              > a = ["a","b", "c"]
              ["a", "b", "c"]
              > b = a
              ["a", "b", "c"]
              > c = a.clone
              ["a", "b", "c"]
              > d = a << "d"
              ["a", "b", "c", "d"]
              > a
              ["a", "b", "c", "d"]
              > b
              ["a", "b", "c", "d"]
              > c
              ["a", "b", "c"]
              > a[2] = "3"
              3
              > a
              ["a", "b", "3", "d"]
              > b
              ["a", "b", "3", "d"]
              > c
              ["a", "b", "c"]
              > d
              ["a", "b", "3", "d"]
              which shows that for these two kinds of changes, the clone is not affected.

              I'm still not clear on when I can use .clone and when not, nor do I really understand shallow vs. deep copies and frozen objects, so for now, unless I'm doing tens of thousands of copies, I may use a brute force method becuase the shortcuts seem so problematical.

              Thanks,
              =A=
              0
              “An idea, like a ghost, must be spoken to a little before it will explain itself.”
              -- Charles Dickens

              August 
               

              Re: An independent copy of a Ruby array

              Postby Dan Rathbun » Wed Jul 29, 2015 12:10 am

              August wrote:I'm using SU 15, which is supposed to have Ruby 2.0 and Dan notes that dclone is in 1.9, so I'm confused.

              What I actually said was that the REXML library (beginning in Ruby 1.9,) modified the Array class, by adding the dclone method.

              In order to use it, you must either precede it's use with:
              require "rexml/document"
              or use a refinement.

              (1) Using the REXML library:
              require "rexml/document"
              >> true
              a = []
              >> []
              a.respond_to?(:dclone)
              >> true


              (2) Refinement module:
              Code: Select all

              module August
                module DeepCopy

                  refine 
              Array do
                    
              def dclone
                      klone 
              self.clone
                      
              klone.clear
                      self
              .each{|vklone << v.dclone}
                      
              klone
                    end
                  end 
              # class Array

                
              end # refinement module DeepCopy
              end # Author's namespace


              using August::DeepCopy

              module August
                module SomePlugin
                  
                  a 
              = ["august","dan","tig"]
                  
              a.dclone
                  
                  puts 
              "a is: #{a.inspect}"
                  
              puts "b is: #{b.inspect}"
                  
              puts "changing a[1] to \"bob\""
                  
              a[1]= "bob"
                  
              puts "a is: #{a.inspect}"
                  
              puts "b is: #{b.inspect}"

                
              end # module SomePlugin
              end # Author's namespace
               


              Any call to using() must occur within the TOPLEVEL_BINDING.
              This restriction has been removed in later versions of Ruby 2.2+, and the experimental warning that is output on calls to refine has also been removed. (Ie, refinements are no longer experimental and calls to using can happen inside specific module and class scopes.)

              If you want to see that all the elements of the arrays are different objects, you can iterate them and compare their object id numbers.
              0
                I'm not here much anymore. But a PM will fire email notifications.
                User avatar
                Dan Rathbun 
                PluginStore Author
                PluginStore Author
                 

                Re: This tripped me up (again) today

                Postby August » Wed Jul 29, 2015 6:24 am

                Thanks Dan,

                So refine allows me to add my own operators to an existing class? That's a sweet (and dangerous) concept.

                I'll have to put that on the back burner for now. I'm still working on basic Ruby.
                0
                “An idea, like a ghost, must be spoken to a little before it will explain itself.”
                -- Charles Dickens

                August 
                 

                Re: This tripped me up (again) today

                Postby Dan Rathbun » Wed Jul 29, 2015 6:34 pm

                August wrote:So refine allows me to add my own operators to an existing class? That's a sweet (and dangerous) concept.

                Refinements do not affect other people's scripts that do not "use" the refinement module. (They are only valid within the file that has the using call.)
                0
                  I'm not here much anymore. But a PM will fire email notifications.
                  User avatar
                  Dan Rathbun 
                  PluginStore Author
                  PluginStore Author
                   

                  Re: This tripped me up (again) today

                  Postby Dan Rathbun » Wed Jul 29, 2015 6:42 pm

                  Another basic concept is the mixin module, which uses include() and extend().

                  In this case, in order to affect only the array you are using, you need to only "extend" the specific array instance object.

                  So, assume you have previously loaded a mixin module thus:
                  Code: Select all
                  module August
                    module DeepCopy

                      def dclone
                        klone 
                  = self.clone
                        klone.clear
                        self
                  .each{|v| klone << v.dclone}
                        klone
                      end

                    end 
                  # mixin module DeepCopy
                  end # Author's namespace 


                  Then you need to extend a specific array instance:
                  Code: Select all
                  = ["august","dan","tig"]

                  a.extend(August::DeepCopy)

                  = a.dclone
                  0
                    I'm not here much anymore. But a PM will fire email notifications.
                    User avatar
                    Dan Rathbun 
                    PluginStore Author
                    PluginStore Author
                     

                    Naming Arrays

                    Postby August » Sat Aug 01, 2015 12:55 am

                    Back to TIG's comment about plural vs. singular names for arrays, here is the way I'm naming things. In this context, the singular terms seem to work better for me, probably because I think of it as the mathematical notation P0, P1, ... PN.

                    Code: Select all
                    =begin

                              point[N]
                                      x-------------x point[0]  angle[0] = angle_between vector[N], vector[0]
                                         vector[N]   \
                      vector[N] = point[N], point[0]  \
                                                       \
                                              vector[0] \
                          vector[0] = point[0], point[1] \
                                                          x point[1]  angle[1] = angle_between vector[0], vector[1]
                                                         /
                                              vector[1] /
                        vector[1] = point[1], point[2] /
                                                      /
                                                     /
                                                    x point[2]  angle[2] = angle_between vector[1], vector[2]

                    =end
                    0
                    “An idea, like a ghost, must be spoken to a little before it will explain itself.”
                    -- Charles Dickens

                    August 
                     

                    SketchUcation One-Liner Adverts

                    by Ad Machine » 5 minutes ago



                    Ad Machine 
                    Robot
                     



                     

                    Return to Developers' Forum

                    Who is online

                    Users browsing this forum: Bing [Bot] and 4 guests

                    Visit our sponsors: