Clone element of array with typescript - arrays

I am having the car object in this screenshot(I couldn't find a way to pretty print it here)
I would like to do a function that clones one of the two elements in the factory.
I tried it like this:
public cloneFactory(modelIndex: number, factoryIndex: number): void {
const newFactory = this.car.model[modelIndex].factory.slice(factoryIndex, factoryIndex + 1);
this.car.model[modelIndex].factory.push(newFactory[0]);
}
I also tried it the classic way:
public cloneFactory(modelIndex: number, factoryIndex: number): void {
const newFactory = this.car.model[modelIndex].factory[factoryIndex];
this.car.model[modelIndex].factory.push(newFactory);
}
The issue I am having is that if I, afterwards, change one of the values in the cloned object it also changes it in the original object. I cannot understand why and how / why are the original and the clone related after one of the methods above are executed.
What is the proper way of cloning an element of the array so that it can be, later, edited without the original object to be affected as well?

You are not actually cloning the object. You are pushing the reference to the same object again. To clone it you can use Object.assign:
const newFactory = Object.assign({}, this.car.model[modelIndex].factory[factoryIndex]);
Here's an example of how Object.assign works compared to just assigning a reference:
var obj = { a: 1 };
var objref2 = obj; // Just assigning reference; not a clone
var clone = Object.assign({}, obj); // Actual cloned copy of `obj`
obj.a = 100;
console.log(obj.a)
-> 100 // Value changed
console.log(objref2.a)
-> 100 // Value changed
console.log(clone.a)
-> 1 // Value unchanged

Related

How to update an array passed as a parameter on init when new items are added on the original array

Say for example I have an array that needs to be used on multiple classes, if I need a worker to update the values of this array how do I make it so that when I add new values to the said array the object worker's value also changes.
Example:
class Object {
var id: Int
var foo: String
var bar: Int
init(id: Int, foo: String, bar: Int) {
self.id = id
self.foo = foo
self.bar = bar
}
}
class ObjectWorker {
var objects: [Object]
init(objects: [Object]) {
self.objects = objects
}
func updateObjects(withId id: Int) {
self.objects.forEach { $0.foo = "a different value" }
}
}
class SomeClass {
// this declaration will happen on more than one class
var objects: [Object] = ... // let's just say there are 10 objects here
lazy var worker = ObjectWorker(objects: self.objects)
init() {
// to initialize the workers
_ = worker
print(objects.count) // 10
print(worker.objects.count) // 10
let newObjects: [Object] = ... // let's say this has 5 new values
objects.append(contentsOf: newObjects)
print(objects.count) // 15
print(worker.objects.count) // 10
}
}
I have tried making the ObjectWorker's init be an inout parameter like this init(objects: inout [Object] but even then the result is still the same. The updateObjects works though even if the init is not an inout parameter.
Note:
I know I can do this by using the ObjectWorker as the container of the objects instead of what is currently going on in here, but is there a way to do this without doing that?
I can also use static functions instead, but let's not go there
As already mentioned, Swift Arrays are value types so ObjectWorker gets a copy of the array. If you don't want to use ObjectWorker as a container, you could use an NSArray instead (which is a reference type).

sortOn(): Trying to sort the copy of an array, but the original is also being sorted

I am trying to sort a copy of an array, but for some reason, both arrays are being sorted. What could be causing this?
var myArray = [["stuff," "stuff"], ["stuff", "stuff"]]
var myArrayCopy = myArray as Array
trace(myArray) //Gives unsorted array
trace(myArrayCopy) //Gives unsorted array
arrayCopy.sortOn(1, Array.CASEINSENSITIVE)
trace(myArray) //Gives sorted array
trace(myArrayCopy) //Gives sorted array
In AS3 there are, simply put, two types of data: primitive (int, uint, Number, Boolean, String) and objects (everything else, even basic Object and Array types). The primitive data are copied by their values:
var a:int = 1;
var b:int;
// Now we pass the value of a.
b = a;
a = 2;
trace(a); // output: 2
trace(b); // output: 1
Then, all objects are passed by their reference (like pointers in C/C++ language), so there's only one original instance of the object and multiple references to it:
var A:Array = [1, 2];
var B:Array;
// Now we pass the reference to the A.
B = A;
A[0] = 2;
trace(A); // output: 2,2
trace(B); // output: 2,2
In order to copy object data you need a bit of understanding. There are, as DodgerThud mentioned, deep copy and shallow copy terms. Simply, shallow copy creates a clone of top-level container while copying the deeper levels as is. The deep copy clones everything to the bottom so one of copies won't be affected in any way no matter what you do to another.
To shallow copy an Array:
var A:Array = [1,2,[3]];
var B:Array;
// Make a shallow copy.
B = A.slice();
// Lets change the original and see.
A[0] = 2;
A[1] = 3;
A[2][0] = 4;
trace(A); // output: 2,3,4
trace(B); // output: 1,2,4
So, A and B are distinct Arrays, but their last element refers to the same Array.
To shallow copy an Object:
var A:Object = {a:1,b:2,c:[3]};
var B:Object;
// Make a shallow copy.
B = new Object;
// Iterate over keys in A.
for (var aKey:String in A)
{
// Copy members of A one by one.
B[aKey] = A[aKey];
}
// Let's change the original and see.
A.a = 2;
A.b = 3;
A.c[0] = 4;
trace(A.a, A.b, A.c); // output: 2 3 4
trace(B.a, B.b, B.c); // output: 1 2 4
Then, deep copy. Normally, you recursively iterate over top-level object and all its descendants to make sure each and any piece of data goes as a copy and not as a reference to the original data. In the same time you watch for the duplicate entries and circular references.
However (and luckily) there are some shortcuts in AS3 for that. You can deep copy via ByteArray. This will (tested and confirmed that) handle duplicates and cyclic references just fine:
function deepCopy(source:*):*
{
var BA:ByteArray = new ByteArray;
BA.writeObject(source);
BA.position = 0;
var result:* = BA.readObject();
BA.clear();
return result;
}
var A:Array = [1, 2, [3]];
var B:Array = deepCopy(A);
// Let's change the original and see.
A[0] = 2;
A[1] = 3;
A[2][0] = 4;
trace(A); // output: 2,3,4
trace(B); // output: 1,2,3
Not sure, if it would be in any way faster, or better, or more optimal, but still, another way:
function deepCopy(source:*):*
{
return JSON.parse(JSON.stringify(source));
}
There are some insights about both of them.
JSON deep copy:
duplicates: no (clones them as distinct objects)
circular references: no (runtime error)
custom classes: no
supported data types: int, uint, Number, Boolean, String, Object, Array
ByteArray deep copy:
duplicates: yes
circular references: yes
custom classes: via registerClassAlias method
supported data types: int, uint, Number, Boolean, String, Object, Array, ByteArray
Here's my own version of deepCopy:
private const SIMPLE:Object =
{
"number":true,
"string":true,
"boolean":true,
"undefined":true
};
private const XNODE:String = getQualifiedClassName(XML);
private const XLIST:String = getQualifiedClassName(XMLList);
private const ARRAY:String = getQualifiedClassName(Array);
private const OBJECT:String = getQualifiedClassName(Object);
private const BYTES:String = getQualifiedClassName(ByteArray);
private var lock:Dictionary;
private function deepCopy(source:*):*
{
// Handle primitive data.
if (source == null) return source;
if (source is Class) return source;
if (SIMPLE[typeof(source)]) return source;
var result:*;
var aLock:Boolean;
var aQname:String;
if (!lock)
{
// If we're here, then we're at the top level
// so we should devise cache for handling
// duplicates and circular references.
lock = new Dictionary;
aLock = true;
}
// If it is cached, then it is either
// duplicate or circular reference.
if (lock[source]) return lock[source];
aQname = getQualifiedClassName(source);
if (aQname == BYTES)
{
var aBytes:ByteArray;
aBytes = new ByteArray;
aBytes.writeBytes(source, 0, source.length);
result = aBytes;
lock[source] = result;
}
else if (aQname == ARRAY)
{
var aRay:Array;
aRay = new Array;
aRay.length = source.length;
result = aRay;
lock[source] = result;
// Copy the elements of the source Array one by one.
for (var i:int = 0; i < aRay.length; i++)
{
aRay[i] = deepCopy(source[i]);
}
}
else if (aQname == OBJECT)
{
var aRes:Object;
aRes = new Object;
result = aRes;
lock[source] = result;
// Copy the members of the source Object one by one.
for (var aKey:String in source)
{
aRes[aKey] = deepCopy(source[aKey]);
}
}
else if ((aQname == XNODE) || (aQname == XLIST))
{
// This one is tricky. The object to clone might
// have a reference to some descendant node of some
// big XML. There could be several references to
// different sub-nodes either. Probably you should
// not rely on this method to clone XML data unless
// you are totally aware of what you are doing.
result = source.copy();
lock[source] = result;
}
else
{
// If we're here, that means that source holds
// a reference to some class instance. You should
// define here your own ways to handle them.
result = source;
}
if (aLock)
{
// If we're here, then we're at the top level
// so we should do some clean-up before we leave.
for (var aRef:* in lock)
{
delete lock[aRef];
}
lock = null;
}
return result;
}
Custom deep copy:
duplicates: yes
circular references: yes
custom classes: you should define your own rules of handling them
supported data types: int, uint, Number, Boolean, String, Object, Array, ByteArray, XML (limited), class constructors

Address of Array & Remove(at :) IOS

I'm passing an array of a specific model by reference between ViewControllers.
If I change any value of a specific element in the array it reflects well in all ViewControllers but when I remove an element from that array it doesn't reflect to the other controllers.
Does the remove(at: ) function create new array and refer to another address?
And if so how to delete an element without changing the address of array so it can reflect this change on the other view controllers?
Swift Arrays are value types (specifically, an array is a struct), not reference types, so you are mistaken when you say that you are "passing an array of a specific model by reference between view controllers". You can only ever pass a Swift array as a value.
Arrays, like other structs, have copy-on-modify semantics. As soon as you change the array itself a copy is made and the change is made to the copy.
Now, in your case the array contains references to model objects; When you update the model object you change the object itself, not the reference held in the array, so you see the change reflected in all of your view controllers.
An analogy might be the difference between adding a house to a street (which changes the street itself) versus changing the occupants of an existing house on the street.
I would suggest you implement a model object that provides abstraction from the underlying array so that you have better code and avoid the issue with array references.
One approach could be something like:
struct MyModel {
let name: String
let size: Int
}
class MyData {
private var _models = [MyModel]()
var models: [MyModel] {
return _models
}
func insert(model: MyModel) {
self._models.append(model)
}
func removeModel(at: Int) {
guard at >= 0 && at < _models.count else {
return
}
self._models.remove(at: at)
}
}
Although this isn't ideal as it still requires model consumers to know indices in the underlying array. I would prefer something like this:
struct MyModel: Hashable {
let name: String
let size: Int
}
class MyData {
private var _models = [MyModel]()
var models: [MyModel] {
return _models
}
func insert(model: MyModel) {
self._models.append(model)
}
func remove(model: MyModel) -> Bool {
if let index = self._models.index(of: model) {
_models.remove(at: index)
return true
} else {
return false
}
}
}
Now I don't need to know what internal collection MyData uses to store the models.
If you need to pass an array (or any other value type) by reference, you could go through an intermediate structure that manages the indirection for you.
[EDIT] changed to use KeyPaths available in Swift 4.
// Generic class to hold a "weak" reference to a property from an object
// including properties that are valued types such as arrays, structs, etc.
// This is merely an encapsulation of Swift's native KeyPath feature
// to make the code a bit more readable and simpler to use
//
class ReferenceTo<ValueType> { var value:ValueType! { get { return nil} set {} } }
class Reference<OwnerType:AnyObject,ValueType>:ReferenceTo<ValueType>
{
internal weak var owner:OwnerType!
internal var property:ReferenceWritableKeyPath<OwnerType,ValueType>! = nil
internal var valueRef:KeyPath<OwnerType,ValueType>! = nil
init(_ owner:OwnerType, _ property:ReferenceWritableKeyPath<OwnerType,ValueType>)
{ (self.owner,self.property) = (owner,property) }
init(_ owner:OwnerType, get valueRef:KeyPath<OwnerType,ValueType>)
{ (self.owner,self.valueRef) = (owner,valueRef) }
override var value:ValueType!
{
get { return valueRef != nil ? owner?[keyPath:valueRef] : owner?[keyPath:property] }
set { owner?[keyPath:property] = newValue }
}
}
With this generic class you can create references to valued type properties of object instances and manipulate them anywhere in your code as if the valued type property was a reference type.
// Example class with a read/write and a read-only property:
class MyObject
{
var myArray = [1,2,3,4]
var total:Int { return myArray.reduce(0,+) }
}
var instance:MyObject! = MyObject()
// create a reference to the array (valued type)
// that can be used anywhere and passed around as a parameter
let arrayRef = Reference(instance, \.myArray)
// the value is accessed and manipulated using the
// "value" property of the reference
arrayRef.value.remove(at:2)
arrayRef.value.append(5)
print(instance.myArray) // [1,2,4,5]
// Read-only properties can also be manipulated as
// references
let valueRef = Reference(instance, get:\.total)
print(valueRef.value) // 12
The Reference class allows passing the value as a reference to function parameters
// a function that expects a reference to an array
// would be declared as follows
func changeArray(_ array:ReferenceTo<[Int]>)
{ array.value.insert(9, at: 1) }
// the reference can also be used as an inout parameter
func shift(_ array:inout [Int])
{ array = Array(array.dropFirst()) + Array(array.prefix(1)) }
changeArray(arrayRef)
shift(&arrayRef.value!)
print(instance.myArray) // [9,2,4,5,1]
...
// the reference uses a weak link to the owner
// of the referenced property or value
// so there will be no strong reference cycle issues even
// if the reference is used in an object held strongly
// by the owner itself
instance = nil
print(arrayRef.value) // none ... no more value after the owner is gone

why are key value pairs being added twice to state?

I have the following function:
handleAddItem(s) {
var key = Object.keys(s)[0];
var value = s[key];
var allItems = {...this.state.items};
allItems[key] = allItems[key];
allItems[key].push({name: value});
var ourItems = {};
ourItems = allItems[key];
ourItems.push({name: value });
this.setState({items: allItems});
}
I realize that ourItems isn't really suppose to be doing anything but why when those three lines are added are the key value pairs being added twice to state?
Only primitives in JavaScript are passed by value. Everything else is passed by reference.
By setting ourItems = allItems[key] you make the ourItems variable point to the allItems[key] array. It does not copy the array. Because of that you actually push the object twice into the same array.

Cloning an array in Javascript/Typescript

I have array of two objects:
genericItems: Item[] = [];
backupData: Item[] = [];
I am populating my HTML table with genericItemsdata. The table is modifiable. There is a reset button to undo all changes done with backUpData. This array is populated by a service:
getGenericItems(selected: Item) {
this.itemService.getGenericItems(selected).subscribe(
result => {
this.genericItems = result;
});
this.backupData = this.genericItems.slice();
}
My idea was that, the user changes will get reflected in first array and second array can be used as backup for reset operation. The issue I am facing here is when the user modifies the table (genericItems[]) the second array backupData also gets modified.
How is this happening and how to prevent this?
Clone an object:
const myClonedObject = Object.assign({}, myObject);
Clone an Array:
Option 1 if you have an array of primitive types:
const myClonedArray = Object.assign([], myArray);
Option 2 - if you have an array of objects:
const myArray= [{ a: 'a', b: 'b' }, { a: 'c', b: 'd' }];
const myClonedArray = [];
myArray.forEach(val => myClonedArray.push(Object.assign({}, val)));
Cloning Arrays and Objects in javascript have a different syntax. Sooner or later everyone learns the difference the hard way and end up here.
In Typescript and ES6 you can use the spread operator for array and object:
const myClonedArray = [...myArray]; // This is ok for [1,2,'test','bla']
// But wont work for [{a:1}, {b:2}].
// A bug will occur when you
// modify the clone and you expect the
// original not to be modified.
// The solution is to do a deep copy
// when you are cloning an array of objects.
To do a deep copy of an object you need an external library:
import {cloneDeep} from 'lodash';
const myClonedArray = cloneDeep(myArray); // This works for [{a:1}, {b:2}]
The spread operator works on object as well but it will only do a shallow copy (first layer of children)
const myShallowClonedObject = {...myObject}; // Will do a shallow copy
// and cause you an un expected bug.
To do a deep copy of an object you need an external library:
import {cloneDeep} from 'lodash';
const deeplyClonedObject = cloneDeep(myObject); // This works for [{a:{b:2}}]
Using map or other similar solution do not help to clone deeply an array of object.
An easier way to do this without adding a new library is using JSON.stringfy and then JSON.parse.
In your case this should work :
this.backupData = JSON.parse(JSON.stringify(genericItems));
When using the last version of JS/TS, installing a large library like lodash for just one/two function is a bad move. You will heart your app performance and in the long run you will have to maintain the library upgrades! check https://bundlephobia.com/result?p=lodash#4.17.15
For small objet lodash cloneDeep can be faster but for larger/deeper object json clone become faster. So in this cases you should not hesitate to use it. check https://www.measurethat.net/Benchmarks/Show/6039/0/lodash-clonedeep-vs-json-clone-larger-object and for infos https://v8.dev/blog/cost-of-javascript-2019#json
The inconvenient is that your source object must be convertible to JSON.
try the following code:
this.cloneArray= [...this.OriginalArray]
The following line in your code creates a new array, copies all object references from genericItems into that new array, and assigns it to backupData:
this.backupData = this.genericItems.slice();
So while backupData and genericItems are different arrays, they contain the same exact object references.
You could bring in a library to do deep copying for you (as #LatinWarrior mentioned).
But if Item is not too complex, maybe you can add a clone method to it to deep clone the object yourself:
class Item {
somePrimitiveType: string;
someRefType: any = { someProperty: 0 };
clone(): Item {
let clone = new Item();
// Assignment will copy primitive types
clone.somePrimitiveType = this.somePrimitiveType;
// Explicitly deep copy the reference types
clone.someRefType = {
someProperty: this.someRefType.someProperty
};
return clone;
}
}
Then call clone() on each item:
this.backupData = this.genericItems.map(item => item.clone());
Array copy explained - Deep & Shallow
Below code might help you to copy the first level objects
let original = [{ a: 1 }, {b:1}]
const copy = [ ...original ].map(item=>({...item}))
so for below case, values remains intact
copy[0].a = 23
console.log(original[0].a) //logs 1 -- value didn't change voila :)
Fails for this case
let original = [{ a: {b:2} }, {b:1}]
const copy = [ ...original ].map(item=>({...item}))
copy[0].a.b = 23;
console.log(original[0].a) //logs 23 -- lost the original one :(
Try lodash separate ES module - cloneDeep:
I would say go for lodash cloneDeep API ( This can be
installed as a separate module, reduced code footprint for treeshaking ) which helps you to copy the objects
inside objects completely dereferencing from original one's.
As another option you can rely on JSON.stringify & JSON.parse
methods to dereference quickly and performant too.
Refer documentation: https://github.com/lodash/lodash
Individual Package : https://www.npmjs.com/package/lodash.clonedeep
you can use map function
toArray= fromArray.map(x => x);
Clone an object / array (without reference) in a very powerful way
You can get deep-copy of your object / array using #angular-devkit.
import { deepCopy } from '#angular-devkit/core/src/utils/object';
export class AppComponent {
object = { .. some object data .. }
array = [ .. some list data .. ]
constructor() {
const newObject = deepCopy(this.object);
const newArray = deepCopy(this.array);
}
}
I have the same issue with primeNg DataTable. After trying and crying, I've fixed the issue by using this code.
private deepArrayCopy(arr: SelectItem[]): SelectItem[] {
const result: SelectItem[] = [];
if (!arr) {
return result;
}
const arrayLength = arr.length;
for (let i = 0; i <= arrayLength; i++) {
const item = arr[i];
if (item) {
result.push({ label: item.label, value: item.value });
}
}
return result;
}
For initializing backup value
backupData = this.deepArrayCopy(genericItems);
For resetting changes
genericItems = this.deepArrayCopy(backupData);
The magic bullet is to recreate items by using {} instead of calling constructor.
I've tried new SelectItem(item.label, item.value) which doesn't work.
the easiest way to clone an array is
backUpData = genericItems.concat();
This will create a new memory for the array indexes
If your items in the array are not primitive you can use spread operator to do that.
this.plansCopy = this.plans.map(obj => ({...obj}));
Complete answer : https://stackoverflow.com/a/47776875/5775048
Try this:
[https://lodash.com/docs/4.17.4#clone][1]
var objects = [{ 'a': 1 }, { 'b': 2 }];
var shallow = _.clone(objects);
console.log(shallow[0] === objects[0]);
// => true
It looks like you may have made a mistake as to where you are doing the copy of an Array. Have a look at my explanation below and a slight modification to the code which should work in helping you reset the data to its previous state.
In your example i can see the following taking place:
you are doing a request to get generic items
after you get the data you set the results to the this.genericItems
directly after that you set the backupData as the result
Am i right in thinking you don't want the 3rd point to happen in that order?
Would this be better:
you do the data request
make a backup copy of what is current in this.genericItems
then set genericItems as the result of your request
Try this:
getGenericItems(selected: Item) {
this.itemService.getGenericItems(selected).subscribe(
result => {
// make a backup before you change the genericItems
this.backupData = this.genericItems.slice();
// now update genericItems with the results from your request
this.genericItems = result;
});
}
Looks like what you want is Deep Copy of the object. Why not use Object.assign()? No libaries needed, and its a one-liner :)
getGenericItems(selected: Item) {
this.itemService.getGenericItems(selected).subscribe(
result => {
this.genericItems = result;
this.backupDate = Object.assign({}, result);
//this.backupdate WILL NOT share the same memory locations as this.genericItems
//modifying this.genericItems WILL NOT modify this.backupdate
});
}
More on Object.assign(): https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
array level cloning solutions didn't work for me, added this clone utility in typescript which does a deeper clone that worked in my case
export function cloneArray<T>(arr: T[]): T[] {
return arr.map((x) => Object.assign({}, x))
}
by "didn't work" i meant, that if i passed an array to a function [...arr] or Object.assign([], arr) or arr.splice() it was still mutating the original array
Try this
const returnedTarget = Object.assign(target, source);
and pass empty array to target
in case complex objects this way works for me
$.extend(true, [], originalArray) in case of array
$.extend(true, {}, originalObject) in case of object

Resources