Deep copy instead of shallow copy?

Post your Gambas programming questions here.
Post Reply
User avatar
Godzilla
Posts: 59
Joined: Wednesday 26th September 2018 11:20am

Deep copy instead of shallow copy?

Post by Godzilla » Wednesday 03rd February 2021 10:28am

Hello,

Let's say I have a class array called Pets. Let's say Pets is the end-result of a huge, CPU-intensive sort that I'd rather not have to perform again, when I can simply cherry-pick what I need from the already-sorted Pets array.

So I create another class array of the same type, called Pets_Reptiles. Certain elements from the Pets array will be copied to the Pets_Reptiles array.

Without including the details of array resizing or instantiating, the pseudo code for copying what I want to be in this new array might generally be something like:
For Counter1 = 0 to Pets.Max
    If Comp(Pets[Counter1].TypeOfAnimal,"reptile",gb.IgnoreCase) = 0 Then 'if the text contained in .TypeOfAnimal is 'reptile' then
        Pets_Reptiles[Counter2] = Pets[Counter1] 'copy that element to an element in Pets_Reptiles
        Counter2 = Counter2 + 1
    EndIf
Next TheCounter
This will make a "shallow copy" of the data in certain elements from Pets, and assign them to Pets_Reptiles.

The problem with this is any change made to an element in Pets_Reptiles is also made to the originating element from the Pets array. Because with a shallow copy, its not really a true copy. Its simply a pointer to each original element in the original array. If the program is continuing to use the Pets array for something completely different, changes having been made to Pets_Reptiles can create chaos/confusion

In Gambas 3.6.0, an option to do a "deep copy" .Copy was introduced. My understanding is that it creates a true, separate, independent copy in which changes to elements in the deep copy array have no effect on the originating array.

I can't find any examples of how to use the deep copy. I tried
Pets_Reptiles[Counter2] = Pets.Copy[Counter1]    
and I got the error "not an object". So I'm at a loss of how to use .Copy or if it will even work on a class array. Can anyone alter my pseudo code to demonstrate the proper syntax of a deep copy?

Thanks for reading.

User avatar
stevedee
Posts: 379
Joined: Monday 20th March 2017 6:06pm

Re: Deep copy instead of shallow copy?

Post by stevedee » Wednesday 03rd February 2021 12:49pm

Godzilla wrote:
Wednesday 03rd February 2021 10:28am
...In Gambas 3.6.0, an option to do a "deep copy" ...
There seems to have been some debate concerning the definition of "deep" and "shallow" here: http://lists.gambas-basic.org/pipermail ... 22664.html which doesn't help me at all!

So although Object[].Copy is stated as a deep copy, maybe its not.

With your code:-
Pets_Reptiles[Counter2] = Pets.Copy[Counter1]
...which part is generating "not an object"?
If you step to just before this point in your code then click on Pets_Reptiles the object observer should show the object, which you can then investigate.

Maybe try:-
Pets_Reptiles = Pets.Copy
...just to see if you still get an error.

User avatar
cogier
Site Admin
Posts: 648
Joined: Wednesday 21st September 2016 2:22pm
Location: Guernsey, Channel Islands

Re: Deep copy instead of shallow copy?

Post by cogier » Wednesday 03rd February 2021 4:30pm

In the attached program is an array of the class of 'Pets' which I have then searched for 'Reptiles' and added the 'Pet' to the new "Reptiles" array if appropriate.

Does this help?
Pets-0.0.1.tar.gz
(11.85 KiB) Downloaded 14 times

User avatar
Godzilla
Posts: 59
Joined: Wednesday 26th September 2018 11:20am

Re: Deep copy instead of shallow copy?

Post by Godzilla » Thursday 04th February 2021 9:34am

stevedee and cogier, thank you both for your replies.

Steve:

I understand that there is some debate as to what may constitute a true deep copy. But to me, whether or not the .Copy command is technically deep...its all fine and good as long as it allows me to make changes to a child array, without those changes also appearing without want or intention in the master array.

The "not an object" error referred to the first part, "Pets_Reptiles[Counter2]". I have no doubt that the error was the result of my trying to make a wild guess at the proper .Copy syntax, and failing miserably.

Trying the code you suggested, to see what happens, gives the error "Type mismatch, wanted ClassArrayName[], got Function instead." However, a good-enough deep copy isn't a lost cause. Read on...

cogier:

Thank you for creating the situation I described. Short, simple, and more advanced than my clunky VB6-influenced style. Impeccable coding!

I've modified your code to demonstrate to you and anyone interested the shortcoming of a shallow copy.

Changes to the "child" array Reptiles also appear without intention in the "parent" array Pets. This is demonstrated in the Pets GridView. Changes I've made to the Reptiles array are shown as expected in the Reptiles GridView,. However, those same changes also now appear in the Pets GridView. Even though no changes whatsoever were made to the Pets array after the original values were assigned. With the integrity of the master array now compromised, any other child array later derived from the master array Pets might also be compromised. With the problem compounding if/when changes are also made to other children.

From my research, this shortcoming of shallow copies is not a bug, per se. Its common practice in Java and Python, from which I believe Gambas uses at some low-level point.

HOWEVER, I've come up with a sort of a solution/workaround/hack to fix this shortcoming of shallow copies. By assigning each element of the master array Pets to intermediary strings, and THEN assigning those intermediary strings to the elements of the child array Reptiles, the master array Pets is completely unaffected by changes made to Reptiles, This is demonstrated in my modified version of your code by unchecking the CheckBox1 checkbox. Checking the box once again demonstrates once again the shallow copy "bug."

A "DeepCopy" function could be created, customized to work with all elements of a particular class. But it would be nice if this were a built-in Gambas function. I was hoping .Copy might be it. Or maybe it is, but I just don't know the proper syntax.

Anyways, thanks for reading my rambling!
Attachments
Pets_Godzilla.tar.gz
My modification to cogier's code, demonstrating the shortcoming to shallow copies & a possible workaround.
(14.12 KiB) Downloaded 14 times

User avatar
stevedee
Posts: 379
Joined: Monday 20th March 2017 6:06pm

Re: Deep copy instead of shallow copy?

Post by stevedee » Thursday 04th February 2021 11:19am

Godzilla wrote:
Thursday 04th February 2021 9:34am
...Anyways, thanks for reading my rambling!

EDIT: I can't find a strike-through font so I have just removed the hog-wash!


See post #7
Last edited by stevedee on Thursday 04th February 2021 2:07pm, edited 1 time in total.

User avatar
cogier
Site Admin
Posts: 648
Joined: Wednesday 21st September 2016 2:22pm
Location: Guernsey, Channel Islands

Re: Deep copy instead of shallow copy?

Post by cogier » Thursday 04th February 2021 11:51am

Thanks for the high praise.

I had not heard of 'shallow' and 'deep' copies but looking here helped me. It's interesting, but I am struggling to think of a use for 'shallow' copy.
Let's say Pets is the end-result of a huge, CPU-intensive sort
Have you considered using a 'Task' so you can do this in the background?

In case you are not aware the following all do the same: -
iLoop = iLoop + 1
iLoop += 1
Inc iLoop

User avatar
stevedee
Posts: 379
Joined: Monday 20th March 2017 6:06pm

Re: Deep copy instead of shallow copy?

Post by stevedee » Thursday 04th February 2021 2:04pm

Well I've had some fun this morning playing with String arrays, Collections & classes, but haven't solved your problem.

Your base code does indeed create a shallow copy as you can see from the memory addresses in the 2 Object Inspectors...
PetsSameMemAddr.png
PetsSameMemAddr.png (144.68 KiB) Viewed 276 times
...and of course when you destroy the Reptiles array then rebuild it, new addresses are used, so Pets and Reptiles each become unique...
PetsDiffMemAddr.png
PetsDiffMemAddr.png (158.06 KiB) Viewed 276 times
With the shallow copy, memory references are made to the original data for [presumably] any number of copies.

User avatar
Godzilla
Posts: 59
Joined: Wednesday 26th September 2018 11:20am

Re: Deep copy instead of shallow copy?

Post by Godzilla » Friday 05th February 2021 8:25am

Passing along a tidbit of information from my trial and error experiments with the Pets_Godzilla project (posted above)...

As we already know, either of these two lines of code produces a shallow copy:
If hPet.TypeOfAnimal = "Reptile" Then Reptiles.Add(hPet)
Reptiles[iLoop] = hPet
However, doing it varialbe-by-variable, to my surprise, appears to produce a deep copy, according to the results listed in the Pets GridView. And its done without any need for first using intermediary strings:
Reptiles[iLoop].Name = hPet.Name
Reptiles[iLoop].TypeOfAnimal = hPet.TypeOfAnimal
So this appears to be a good-enough workaround to the problem, and its certainly doable. However, using this method with super-complex classes with dozens of variables (my downfall) still makes me wish for a simple, elegant singular command.

But hey, it solves the issue for now and I'll take it!

User avatar
stevedee
Posts: 379
Joined: Monday 20th March 2017 6:06pm

Re: Deep copy instead of shallow copy?

Post by stevedee » Friday 05th February 2021 10:27am

Godzilla wrote:
Friday 05th February 2021 8:25am
...doing it variable-by-variable, to my surprise, appears to produce a deep copy...
Yes, that's what my second screen shot above shows; the system allocates a new memory area as soon as you resize and start adding new data.
{compare the memory addresses of Pets[2] with Reptiles[0]}

Shallow copy seems fairly common across other languages because you could tie up a lot of memory (RAM) if deep copy was the default. I think in VB.Net you have the options of .Copy (shallow copy) and .Clone (deep copy) ...but it may be a little more complicated than that.

Post Reply